From 66e1717b5565e037223251b5419d5c9229a40c40 Mon Sep 17 00:00:00 2001 From: Struck713 Date: Thu, 7 Mar 2024 04:02:48 -0500 Subject: [PATCH 01/17] Added Users tab to staff dashboard - You can approve a user's application - Approved users can be checked in --- global-includes/users.ts | 6 +- ...vite.config.js.timestamp-1709797256080.mjs | 40 ++++++ staff-frontend/layouts/layout.jsx | 18 +-- staff-frontend/pages/emailLists.jsx | 6 +- staff-frontend/pages/users.jsx | 118 ++++++++++++++++++ staff-frontend/pages/users.module.css | 11 ++ 6 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 public-frontend/vite.config.js.timestamp-1709797256080.mjs create mode 100644 staff-frontend/pages/users.jsx create mode 100644 staff-frontend/pages/users.module.css diff --git a/global-includes/users.ts b/global-includes/users.ts index 0c4b015..0859782 100644 --- a/global-includes/users.ts +++ b/global-includes/users.ts @@ -197,10 +197,8 @@ const noUpdate = { allowApiUpdate: false }; @Entity("users", { allowApiCrud: true, apiPrefilter: () => ( - remult.isAllowed([UserRole.Admin, UserRole.Staff]) ? - {} : - { id: remult.user?.id } - ), + remult.isAllowed([UserRole.Admin, UserRole.Staff]) ? {} : { id: remult.user?.id } + ), }) export class User extends EntityBase { @Fields.uuid() diff --git a/public-frontend/vite.config.js.timestamp-1709797256080.mjs b/public-frontend/vite.config.js.timestamp-1709797256080.mjs new file mode 100644 index 0000000..fa285b0 --- /dev/null +++ b/public-frontend/vite.config.js.timestamp-1709797256080.mjs @@ -0,0 +1,40 @@ +// public-frontend/vite.config.js +import { defineConfig } from "file:///C:/Users/Noah/Programming/JavaScript/khe-revengeance/node_modules/vite/dist/node/index.js"; +import path from "path"; +import vue from "file:///C:/Users/Noah/Programming/JavaScript/khe-revengeance/node_modules/@vitejs/plugin-vue/dist/index.mjs"; +import svgLoader from "file:///C:/Users/Noah/Programming/JavaScript/khe-revengeance/node_modules/vite-svg-loader/index.js"; +import { fileURLToPath } from "url"; +var __vite_injected_original_dirname = "C:\\Users\\Noah\\Programming\\JavaScript\\khe-revengeance\\public-frontend"; +var __vite_injected_original_import_meta_url = "file:///C:/Users/Noah/Programming/JavaScript/khe-revengeance/public-frontend/vite.config.js"; +var localRoot = path.resolve(path.dirname(fileURLToPath(__vite_injected_original_import_meta_url))); +var includesRoot = path.resolve(localRoot, "../global-includes/"); +var vite_config_default = defineConfig({ + root: localRoot, + publicDir: "public", + resolve: { + alias: [ + { + find: /^includes/, + replacement: includesRoot + }, + { + find: "@", + replacement: path.resolve(__vite_injected_original_dirname, "src") + } + ], + extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"] + }, + plugins: [vue(), svgLoader({ defaultImport: "url" })], + build: { outDir: "dist", transpile: ["primevue"] }, + ssgOptions: { + entry: "src/main.js", + format: "cjs" + }, + ssr: { + noExternal: ["primevue"] + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsicHVibGljLWZyb250ZW5kL3ZpdGUuY29uZmlnLmpzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiQzpcXFxcVXNlcnNcXFxcTm9haFxcXFxQcm9ncmFtbWluZ1xcXFxKYXZhU2NyaXB0XFxcXGtoZS1yZXZlbmdlYW5jZVxcXFxwdWJsaWMtZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFVzZXJzXFxcXE5vYWhcXFxcUHJvZ3JhbW1pbmdcXFxcSmF2YVNjcmlwdFxcXFxraGUtcmV2ZW5nZWFuY2VcXFxccHVibGljLWZyb250ZW5kXFxcXHZpdGUuY29uZmlnLmpzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9DOi9Vc2Vycy9Ob2FoL1Byb2dyYW1taW5nL0phdmFTY3JpcHQva2hlLXJldmVuZ2VhbmNlL3B1YmxpYy1mcm9udGVuZC92aXRlLmNvbmZpZy5qc1wiO2ltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gXCJ2aXRlXCI7XHJcbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XHJcbmltcG9ydCB2dWUgZnJvbSBcIkB2aXRlanMvcGx1Z2luLXZ1ZVwiO1xyXG5pbXBvcnQgc3ZnTG9hZGVyIGZyb20gJ3ZpdGUtc3ZnLWxvYWRlcidcclxuaW1wb3J0IHsgZmlsZVVSTFRvUGF0aCB9IGZyb20gXCJ1cmxcIjtcclxuXHJcbmNvbnN0IGxvY2FsUm9vdCA9IHBhdGgucmVzb2x2ZShwYXRoLmRpcm5hbWUoZmlsZVVSTFRvUGF0aChpbXBvcnQubWV0YS51cmwpKSk7XHJcbmNvbnN0IGluY2x1ZGVzUm9vdCA9IHBhdGgucmVzb2x2ZShsb2NhbFJvb3QsIFwiLi4vZ2xvYmFsLWluY2x1ZGVzL1wiKTtcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcm9vdDogbG9jYWxSb290LFxyXG4gIHB1YmxpY0RpcjogXCJwdWJsaWNcIixcclxuICByZXNvbHZlOiB7XHJcbiAgICBhbGlhczogW1xyXG4gICAgICB7XHJcbiAgICAgICAgZmluZDogL15pbmNsdWRlcy8sXHJcbiAgICAgICAgcmVwbGFjZW1lbnQ6IGluY2x1ZGVzUm9vdCxcclxuICAgICAgfSxcclxuICAgICAge1xyXG4gICAgICAgIGZpbmQ6IFwiQFwiLFxyXG4gICAgICAgIHJlcGxhY2VtZW50OiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCBcInNyY1wiKSxcclxuICAgICAgfSxcclxuICAgIF0sXHJcbiAgICBleHRlbnNpb25zOiBbXCIubWpzXCIsIFwiLmpzXCIsIFwiLnRzXCIsIFwiLmpzeFwiLCBcIi50c3hcIiwgXCIuanNvblwiLCBcIi52dWVcIl0sXHJcbiAgfSxcclxuICBwbHVnaW5zOiBbdnVlKCksIHN2Z0xvYWRlcih7ZGVmYXVsdEltcG9ydDogXCJ1cmxcIn0pXSxcclxuICBidWlsZDogeyBvdXREaXI6IFwiZGlzdFwiLCB0cmFuc3BpbGU6IFtcInByaW1ldnVlXCJdIH0sXHJcbiAgc3NnT3B0aW9uczoge1xyXG4gICAgZW50cnk6IFwic3JjL21haW4uanNcIixcclxuICAgIGZvcm1hdDogXCJjanNcIlxyXG4gIH0sXHJcbiAgc3NyOiB7XHJcbiAgICBub0V4dGVybmFsOiBbXCJwcmltZXZ1ZVwiXSxcclxuICB9LFxyXG59KTtcclxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUE0WSxTQUFTLG9CQUFvQjtBQUN6YSxPQUFPLFVBQVU7QUFDakIsT0FBTyxTQUFTO0FBQ2hCLE9BQU8sZUFBZTtBQUN0QixTQUFTLHFCQUFxQjtBQUo5QixJQUFNLG1DQUFtQztBQUFvTixJQUFNLDJDQUEyQztBQU05UyxJQUFNLFlBQVksS0FBSyxRQUFRLEtBQUssUUFBUSxjQUFjLHdDQUFlLENBQUMsQ0FBQztBQUMzRSxJQUFNLGVBQWUsS0FBSyxRQUFRLFdBQVcscUJBQXFCO0FBR2xFLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLE1BQU07QUFBQSxFQUNOLFdBQVc7QUFBQSxFQUNYLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMO0FBQUEsUUFDRSxNQUFNO0FBQUEsUUFDTixhQUFhO0FBQUEsTUFDZjtBQUFBLE1BQ0E7QUFBQSxRQUNFLE1BQU07QUFBQSxRQUNOLGFBQWEsS0FBSyxRQUFRLGtDQUFXLEtBQUs7QUFBQSxNQUM1QztBQUFBLElBQ0Y7QUFBQSxJQUNBLFlBQVksQ0FBQyxRQUFRLE9BQU8sT0FBTyxRQUFRLFFBQVEsU0FBUyxNQUFNO0FBQUEsRUFDcEU7QUFBQSxFQUNBLFNBQVMsQ0FBQyxJQUFJLEdBQUcsVUFBVSxFQUFDLGVBQWUsTUFBSyxDQUFDLENBQUM7QUFBQSxFQUNsRCxPQUFPLEVBQUUsUUFBUSxRQUFRLFdBQVcsQ0FBQyxVQUFVLEVBQUU7QUFBQSxFQUNqRCxZQUFZO0FBQUEsSUFDVixPQUFPO0FBQUEsSUFDUCxRQUFRO0FBQUEsRUFDVjtBQUFBLEVBQ0EsS0FBSztBQUFBLElBQ0gsWUFBWSxDQUFDLFVBQVU7QUFBQSxFQUN6QjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/staff-frontend/layouts/layout.jsx b/staff-frontend/layouts/layout.jsx index b9c65bc..e2d2f90 100644 --- a/staff-frontend/layouts/layout.jsx +++ b/staff-frontend/layouts/layout.jsx @@ -1,7 +1,7 @@ import React from "react"; import { Badge, Layout, Menu, ConfigProvider, theme } from "antd"; -import { ContactsOutlined, LockOutlined, MessageOutlined, UnlockOutlined, LinkOutlined, FileImageOutlined } from "@ant-design/icons"; +import { ContactsOutlined, LockOutlined, MessageOutlined, UnlockOutlined, LinkOutlined, FileImageOutlined, UserOutlined } from "@ant-design/icons"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -16,19 +16,11 @@ export default function KHEStaffLayout({ children }) { const unreadMail = useUnreadMailCount(); const pages = [ { key: "/", label: "KHE 2024 Dashboard" }, + { icon: , key: "/users", label: "Users" }, { icon: , key: "/emailLists", label: "Email Lists" }, - { - icon: , key: "/tickets", - label: Support Tickets - }, - { - icon: , key: "/redirects", - label: "Redirect Links" - }, - { - icon: , key: "/imageLayout", - label: "Layout Images" - } + { icon: , key: "/tickets", label: Support Tickets }, + { icon: , key: "/redirects", label: "Redirect Links" }, + { icon: , key: "/imageLayout", label: "Layout Images" } ].map(page => ({ ...page, label: ({page.label}) })); const user = useUser()[0]; diff --git a/staff-frontend/pages/emailLists.jsx b/staff-frontend/pages/emailLists.jsx index 59b3510..15f453e 100644 --- a/staff-frontend/pages/emailLists.jsx +++ b/staff-frontend/pages/emailLists.jsx @@ -6,7 +6,6 @@ import { Email, EmailListNotes, EmailSource, SentListMail, isEmailRegex } from " import { Card, Layout, Button, Input, Upload, Popconfirm, Select, Modal } from "antd"; const { Sider, Footer, Content } = Layout; const { confirm, error } = Modal; -const { Meta } = Card; import { PlusOutlined, UploadOutlined, DeleteOutlined, FileAddFilled, SaveOutlined } from "@ant-design/icons"; import layoutStyle from "../layouts/layout.module.css"; import style from "./emailLists.module.css"; @@ -222,10 +221,7 @@ export default function EmailLists() { keyForAddButton={menuKeys.add} enclose={editableSection => [ { key: menuKeys.compose, label: "Write a mail" }, - { - key: menuKeys.list, label: "Email Lists", - children: editableSection - }, + { key: menuKeys.list, label: "Email Lists", children: editableSection }, { key: menuKeys.sent, label: "Mail what was sent" } ]} /> diff --git a/staff-frontend/pages/users.jsx b/staff-frontend/pages/users.jsx new file mode 100644 index 0000000..0ac6315 --- /dev/null +++ b/staff-frontend/pages/users.jsx @@ -0,0 +1,118 @@ +import { useEffect, useState } from "react"; +import { remult } from "remult"; +import KHELayout from "../layouts/layout"; +import { User } from "../../global-includes/users"; + +import style from "./users.module.css"; + +import { Button, Card, Layout, Modal, Row, Col, Divider } from "antd"; +const { Content } = Layout; + +export default function UsersManager() { + + const [users, setUsers] = useState([]); + const [viewing, setViewing] = useState(null); + const [loading, setLoading] = useState(false); + const repo = remult.repo(User); + + const showReview = (user) => setViewing(user); + const closeReview = () => setViewing(null); + + const approveUser = async (user) => { + setLoading(true); + await repo.save({ id: user.id, applicationApproved: true }); + closeReview(); + setLoading(false); + update() + } + + const checkInUser = async (user) => { + setLoading(true); + await repo.save({ id: user.id, checkedIn: true }); + closeReview(); + setLoading(false); + update() + } + + const update = () => { + repo.find().then(users => setUsers(users)); + } + + const getActions = (user) => { + const checkedIn = user.checkedIn; + const actions = []; + if (user.applicationApproved) + actions.push() + return actions; + } + + useEffect(update, []); + + const cardStyle = { + width: 350, + margin: 6 + } + + return + + + {users.map((user, i) => +
+ {user.roles.join(", ")}} + actions={getActions(user)} + style={cardStyle}> + {user.submittedApplication && !user.applicationApproved && This user is awaiting approval!} +

This account is registered with {user.method}. It was created on {user.createdAt.toLocaleDateString()}.

+
+
+ )} +
+
+ + {/* TODO: really do not like this really long JSON access syntax (viewing.registration.someotherlongname), + this should become it's own component at some point + */} + Application for {viewing?.registration.name}

} + open={viewing !== null} + onCancel={closeReview} + onOk={() => approveUser(viewing)} + okButtonProps={viewing?.applicationApproved ? { disabled: true } : { loading }} + okText="Approve" + > + {viewing && + <> + Personal + + Age: {viewing.registration.age} + School: {viewing.registration.school} + Phone: {viewing.registration.phone} + Class Standing: {viewing.registration.schoolStatus} + Gender: {viewing.registration.gender} + Major: {viewing.registration.major} + Website: {viewing.registration.link} + Attended KHE: {viewing.registration.attendedKhe ? "Yes" : "No"} + Pronouns: {viewing.registration.pronouns} + Ethnicity: {viewing.registration.ethnicity} + Sexuality: {viewing.registration.sexuality} + Shirt Size: {viewing.registration.shirtSize} + State: {viewing.registration.state} + Country: {viewing.registration.country} + + Dietary Restrictions + + {viewing.registration.dietaryRestrictions.join(", ") || "This user has no dietary restrictions."} + + MLH + + {viewing.registration.name} has {viewing.registration.mlhConduct ? "accepted" : "not accepted"} the MLH Code of Conduct. + {viewing.registration.name} has {viewing.registration.mlhShare ? "accepted" : "not accepted"} the MLH Share. + + + } +
+
+} \ No newline at end of file diff --git a/staff-frontend/pages/users.module.css b/staff-frontend/pages/users.module.css new file mode 100644 index 0000000..66a03a8 --- /dev/null +++ b/staff-frontend/pages/users.module.css @@ -0,0 +1,11 @@ +.cardContainer { + display: flex; +} + +.cardContainer :global .anticon { + visibility: hidden; +} + +.cardContainer:hover :global .anticon { + visibility: visible; +} \ No newline at end of file From 658a90c268cd47d6fe7ac8e21c8d2491ccb11588 Mon Sep 17 00:00:00 2001 From: Struck713 Date: Thu, 7 Mar 2024 05:23:22 -0500 Subject: [PATCH 02/17] Added a hacky resume view to the modal, didn't like it --- global-includes/rpc-declarations.ts | 5 +-- global-includes/users.ts | 5 +-- ...vite.config.js.timestamp-1709797256080.mjs | 40 ------------------- server/index.ts | 16 ++++++++ staff-frontend/pages/users.jsx | 8 ++-- staff-frontend/pages/users.module.css | 11 ----- 6 files changed, 22 insertions(+), 63 deletions(-) delete mode 100644 public-frontend/vite.config.js.timestamp-1709797256080.mjs delete mode 100644 staff-frontend/pages/users.module.css diff --git a/global-includes/rpc-declarations.ts b/global-includes/rpc-declarations.ts index d5f25b3..c209709 100644 --- a/global-includes/rpc-declarations.ts +++ b/global-includes/rpc-declarations.ts @@ -41,8 +41,5 @@ export class RemoteProcedures { static sanitizeMessage: (message: TicketMessage) => TicketMessage; static deleteGridImages: (paths: string[]) => Promise; static getUserResumeFilename: (userID: string) => Promise; - /** - * If `base64Resume` is empty, any existing resume will be deleted - */ - static setUserResume: (userID: string, base64Resume: string | "", resumeFilename: string) => Promise; + static setUserResume: (userID: string, base64Resume: string | "", resumeFilename: string) => Promise; // If `base64Resume` is empty, any existing resume will be deleted } diff --git a/global-includes/users.ts b/global-includes/users.ts index 0859782..f7ad313 100644 --- a/global-includes/users.ts +++ b/global-includes/users.ts @@ -400,14 +400,13 @@ export class User extends EntityBase { } } - @BackendMethod({allowed: true}) + @BackendMethod({ allowed: true }) static async getExistingResumeName() { const user = remult.user as User; if (!user) { throw "Not logged in"; - } else { - return await RemoteProcedures.getUserResumeFilename(user.id); } + return await RemoteProcedures.getUserResumeFilename(user.id); } @BackendMethod({ allowed: true }) diff --git a/public-frontend/vite.config.js.timestamp-1709797256080.mjs b/public-frontend/vite.config.js.timestamp-1709797256080.mjs deleted file mode 100644 index fa285b0..0000000 --- a/public-frontend/vite.config.js.timestamp-1709797256080.mjs +++ /dev/null @@ -1,40 +0,0 @@ -// public-frontend/vite.config.js -import { defineConfig } from "file:///C:/Users/Noah/Programming/JavaScript/khe-revengeance/node_modules/vite/dist/node/index.js"; -import path from "path"; -import vue from "file:///C:/Users/Noah/Programming/JavaScript/khe-revengeance/node_modules/@vitejs/plugin-vue/dist/index.mjs"; -import svgLoader from "file:///C:/Users/Noah/Programming/JavaScript/khe-revengeance/node_modules/vite-svg-loader/index.js"; -import { fileURLToPath } from "url"; -var __vite_injected_original_dirname = "C:\\Users\\Noah\\Programming\\JavaScript\\khe-revengeance\\public-frontend"; -var __vite_injected_original_import_meta_url = "file:///C:/Users/Noah/Programming/JavaScript/khe-revengeance/public-frontend/vite.config.js"; -var localRoot = path.resolve(path.dirname(fileURLToPath(__vite_injected_original_import_meta_url))); -var includesRoot = path.resolve(localRoot, "../global-includes/"); -var vite_config_default = defineConfig({ - root: localRoot, - publicDir: "public", - resolve: { - alias: [ - { - find: /^includes/, - replacement: includesRoot - }, - { - find: "@", - replacement: path.resolve(__vite_injected_original_dirname, "src") - } - ], - extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"] - }, - plugins: [vue(), svgLoader({ defaultImport: "url" })], - build: { outDir: "dist", transpile: ["primevue"] }, - ssgOptions: { - entry: "src/main.js", - format: "cjs" - }, - ssr: { - noExternal: ["primevue"] - } -}); -export { - vite_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsicHVibGljLWZyb250ZW5kL3ZpdGUuY29uZmlnLmpzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiQzpcXFxcVXNlcnNcXFxcTm9haFxcXFxQcm9ncmFtbWluZ1xcXFxKYXZhU2NyaXB0XFxcXGtoZS1yZXZlbmdlYW5jZVxcXFxwdWJsaWMtZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFVzZXJzXFxcXE5vYWhcXFxcUHJvZ3JhbW1pbmdcXFxcSmF2YVNjcmlwdFxcXFxraGUtcmV2ZW5nZWFuY2VcXFxccHVibGljLWZyb250ZW5kXFxcXHZpdGUuY29uZmlnLmpzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9DOi9Vc2Vycy9Ob2FoL1Byb2dyYW1taW5nL0phdmFTY3JpcHQva2hlLXJldmVuZ2VhbmNlL3B1YmxpYy1mcm9udGVuZC92aXRlLmNvbmZpZy5qc1wiO2ltcG9ydCB7IGRlZmluZUNvbmZpZyB9IGZyb20gXCJ2aXRlXCI7XHJcbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XHJcbmltcG9ydCB2dWUgZnJvbSBcIkB2aXRlanMvcGx1Z2luLXZ1ZVwiO1xyXG5pbXBvcnQgc3ZnTG9hZGVyIGZyb20gJ3ZpdGUtc3ZnLWxvYWRlcidcclxuaW1wb3J0IHsgZmlsZVVSTFRvUGF0aCB9IGZyb20gXCJ1cmxcIjtcclxuXHJcbmNvbnN0IGxvY2FsUm9vdCA9IHBhdGgucmVzb2x2ZShwYXRoLmRpcm5hbWUoZmlsZVVSTFRvUGF0aChpbXBvcnQubWV0YS51cmwpKSk7XHJcbmNvbnN0IGluY2x1ZGVzUm9vdCA9IHBhdGgucmVzb2x2ZShsb2NhbFJvb3QsIFwiLi4vZ2xvYmFsLWluY2x1ZGVzL1wiKTtcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcm9vdDogbG9jYWxSb290LFxyXG4gIHB1YmxpY0RpcjogXCJwdWJsaWNcIixcclxuICByZXNvbHZlOiB7XHJcbiAgICBhbGlhczogW1xyXG4gICAgICB7XHJcbiAgICAgICAgZmluZDogL15pbmNsdWRlcy8sXHJcbiAgICAgICAgcmVwbGFjZW1lbnQ6IGluY2x1ZGVzUm9vdCxcclxuICAgICAgfSxcclxuICAgICAge1xyXG4gICAgICAgIGZpbmQ6IFwiQFwiLFxyXG4gICAgICAgIHJlcGxhY2VtZW50OiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCBcInNyY1wiKSxcclxuICAgICAgfSxcclxuICAgIF0sXHJcbiAgICBleHRlbnNpb25zOiBbXCIubWpzXCIsIFwiLmpzXCIsIFwiLnRzXCIsIFwiLmpzeFwiLCBcIi50c3hcIiwgXCIuanNvblwiLCBcIi52dWVcIl0sXHJcbiAgfSxcclxuICBwbHVnaW5zOiBbdnVlKCksIHN2Z0xvYWRlcih7ZGVmYXVsdEltcG9ydDogXCJ1cmxcIn0pXSxcclxuICBidWlsZDogeyBvdXREaXI6IFwiZGlzdFwiLCB0cmFuc3BpbGU6IFtcInByaW1ldnVlXCJdIH0sXHJcbiAgc3NnT3B0aW9uczoge1xyXG4gICAgZW50cnk6IFwic3JjL21haW4uanNcIixcclxuICAgIGZvcm1hdDogXCJjanNcIlxyXG4gIH0sXHJcbiAgc3NyOiB7XHJcbiAgICBub0V4dGVybmFsOiBbXCJwcmltZXZ1ZVwiXSxcclxuICB9LFxyXG59KTtcclxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUE0WSxTQUFTLG9CQUFvQjtBQUN6YSxPQUFPLFVBQVU7QUFDakIsT0FBTyxTQUFTO0FBQ2hCLE9BQU8sZUFBZTtBQUN0QixTQUFTLHFCQUFxQjtBQUo5QixJQUFNLG1DQUFtQztBQUFvTixJQUFNLDJDQUEyQztBQU05UyxJQUFNLFlBQVksS0FBSyxRQUFRLEtBQUssUUFBUSxjQUFjLHdDQUFlLENBQUMsQ0FBQztBQUMzRSxJQUFNLGVBQWUsS0FBSyxRQUFRLFdBQVcscUJBQXFCO0FBR2xFLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLE1BQU07QUFBQSxFQUNOLFdBQVc7QUFBQSxFQUNYLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMO0FBQUEsUUFDRSxNQUFNO0FBQUEsUUFDTixhQUFhO0FBQUEsTUFDZjtBQUFBLE1BQ0E7QUFBQSxRQUNFLE1BQU07QUFBQSxRQUNOLGFBQWEsS0FBSyxRQUFRLGtDQUFXLEtBQUs7QUFBQSxNQUM1QztBQUFBLElBQ0Y7QUFBQSxJQUNBLFlBQVksQ0FBQyxRQUFRLE9BQU8sT0FBTyxRQUFRLFFBQVEsU0FBUyxNQUFNO0FBQUEsRUFDcEU7QUFBQSxFQUNBLFNBQVMsQ0FBQyxJQUFJLEdBQUcsVUFBVSxFQUFDLGVBQWUsTUFBSyxDQUFDLENBQUM7QUFBQSxFQUNsRCxPQUFPLEVBQUUsUUFBUSxRQUFRLFdBQVcsQ0FBQyxVQUFVLEVBQUU7QUFBQSxFQUNqRCxZQUFZO0FBQUEsSUFDVixPQUFPO0FBQUEsSUFDUCxRQUFRO0FBQUEsRUFDVjtBQUFBLEVBQ0EsS0FBSztBQUFBLElBQ0gsWUFBWSxDQUFDLFVBQVU7QUFBQSxFQUN6QjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/server/index.ts b/server/index.ts index 82b2c64..78333b9 100644 --- a/server/index.ts +++ b/server/index.ts @@ -91,6 +91,22 @@ async function createServer() { next(); } }); + // TODO: Proper way to server a resume file on an + // app.get("/api/resume/:id", remultConfig.withRemult, async (req, res) => { + // const user = remult.user; + // if (!user) { + // res.sendStatus(403); + // return; + // } + + // let id = req.params.id ?? user.id; + // if (!remult.isAllowed([ UserRole.Admin, UserRole.Staff ])) { + // id = user.id; + // } + + // console.log(projectRoot); + // res.sendFile(getUserResumePath(id), { root: projectRoot }); + // }); if (dev) { // in development mode, we create and use the vite and next.js development // servers and route traffic to them based on the subdomain for the request diff --git a/staff-frontend/pages/users.jsx b/staff-frontend/pages/users.jsx index 0ac6315..ece9cfa 100644 --- a/staff-frontend/pages/users.jsx +++ b/staff-frontend/pages/users.jsx @@ -3,8 +3,6 @@ import { remult } from "remult"; import KHELayout from "../layouts/layout"; import { User } from "../../global-includes/users"; -import style from "./users.module.css"; - import { Button, Card, Layout, Modal, Row, Col, Divider } from "antd"; const { Content } = Layout; @@ -34,7 +32,7 @@ export default function UsersManager() { update() } - const update = () => { + const loadUsers = () => { repo.find().then(users => setUsers(users)); } @@ -46,7 +44,7 @@ export default function UsersManager() { return actions; } - useEffect(update, []); + useEffect(loadUsers, []); const cardStyle = { width: 350, @@ -57,7 +55,7 @@ export default function UsersManager() { {users.map((user, i) => -
+
{user.roles.join(", ")}} diff --git a/staff-frontend/pages/users.module.css b/staff-frontend/pages/users.module.css deleted file mode 100644 index 66a03a8..0000000 --- a/staff-frontend/pages/users.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.cardContainer { - display: flex; -} - -.cardContainer :global .anticon { - visibility: hidden; -} - -.cardContainer:hover :global .anticon { - visibility: visible; -} \ No newline at end of file From 250adbadb614a96e6075a22f7d8bd047331536b7 Mon Sep 17 00:00:00 2001 From: Mitch J Date: Fri, 8 Mar 2024 16:53:16 -0500 Subject: [PATCH 03/17] fixed transactions bug? --- server/db.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/db.ts b/server/db.ts index dee6b86..31a4476 100644 --- a/server/db.ts +++ b/server/db.ts @@ -42,7 +42,7 @@ export const remultConfig = remultExpress({ options.remult.context.incomingIP = request.ip; }, dataProvider: async () => { - return new MongoDataProvider(await getDB(), await getDBClient()); + return new MongoDataProvider(await getDB(), await getDBClient(), {disableTransactions: true}); }, entities: [ Email, From 707830d723e69c00658932a156ca696c97cc60d6 Mon Sep 17 00:00:00 2001 From: Struck713 Date: Sat, 9 Mar 2024 02:52:35 -0500 Subject: [PATCH 04/17] Added a simple chart to staff dashboard --- package.json | 3 +- staff-frontend/pages/index.jsx | 113 +++++++++++---- yarn.lock | 244 ++++++++++++++++++++++++++++++++- 3 files changed, 330 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 37ae254..3e48bab 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "html-to-text": "^9.0.5", "mongodb": "^5.1.0", "multer": "^1.4.5-lts.1", - "nanoid": "^4.0.2", + "nanoid": "^5.0.6", "next": "^13.2.4", "next-usequerystate": "^1.7.2", "octokit": "^2.0.14", @@ -52,6 +52,7 @@ "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-quill": "^2.0.0", + "recharts": "^2.12.2", "remult": "0.24.1", "sass": "^1.60.0", "serve-static": "^1.15.0", diff --git a/staff-frontend/pages/index.jsx b/staff-frontend/pages/index.jsx index 2b33c32..4650212 100644 --- a/staff-frontend/pages/index.jsx +++ b/staff-frontend/pages/index.jsx @@ -1,43 +1,110 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo } from "react"; import KHELayout from "../layouts/layout.jsx"; import { remult } from "remult"; import { Email, EmailSource } from "../../global-includes/email-address.ts"; -import { Card, Statistic } from "antd"; +import { Card, Col, Layout, Row, Skeleton, Statistic } from "antd"; import { User } from "../../global-includes/users.ts"; import Link from "next/link.js"; +import { Bar, BarChart, CartesianGrid, Legend, Tooltip, XAxis, YAxis } from "recharts"; -function DBCard({ children }) { - return {children} +const { Sider, Content } = Layout; + +const DBCard = ({ children }) => {children}; + +const UsersBarChart = () => { + + const [loading, setLoading] = useState(false); + const [chart, setChart] = useState({}); + + useEffect(() => { + + const repo = remult.repo(User); + + const loadChart = async () => { + setChart([ + { + name: 'Registered', + amount: await repo.count({ submittedApplication: true }), + }, + { + name: 'Approved', + amount: await repo.count({ applicationApproved: true }), + }, + { + name: 'Checked in', + amount: await repo.count({ checkedIn: true }), + } + ]); + setLoading(false); + } + + setLoading(true); + loadChart(); + }, []); + + return ( + + + + + + + + + + ) } function HomePage() { - const [emailCount, setEmailCount] = useState(null); - const [count2023, setCount2023] = useState(null); - const [userCount, setUserCount] = useState(null); + const [loading, setLoading] = useState(false); + const [counts, setCounts] = useState(null); + const [chart, setChart] = useState([]); useEffect(() => { - remult.repo(Email).count().then(count => setEmailCount(count)); - Email.getEmailList(EmailSource.Early2023).then(l => setCount2023(l.length)); - remult.repo(User).count().then(count => setUserCount(count)); + + const loadCounts = async () => { + setCounts({ + emails: await remult.repo(Email).count(), + early: await Email.getEmailList(EmailSource.Early2023).then(l => l.length), + users: await remult.repo(User).count() + }); + + setLoading(false); + } + + setLoading(true); + loadCounts(); + }, []); // TODO: make better links return -
- - - - - Total Email Addresses In DB - } value={emailCount ?? '...'} /> - - - - -
+ + + + + + + + Total Email Addresses In DB} value={counts?.emails} /> + + + User Accounts} value={counts?.users} /> + + + + + + + + + + + + +
; }; diff --git a/yarn.lock b/yarn.lock index e47565a..7f8a423 100644 --- a/yarn.lock +++ b/yarn.lock @@ -61,6 +61,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + dependencies: + regenerator-runtime "^0.14.0" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -831,6 +838,57 @@ dependencies: "@types/node" "*" +"@types/d3-array@^3.0.3": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-ease@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-interpolate@^3.0.1": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" + integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== + +"@types/d3-scale@^4.0.2": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" + integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-shape@^3.1.0": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" + integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time@*", "@types/d3-time@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" + integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== + +"@types/d3-timer@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + "@types/express-serve-static-core@^4.17.33": version "4.17.33" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" @@ -1553,6 +1611,11 @@ clone@^2.1.1: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== +clsx@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" + integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -1741,6 +1804,77 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== +"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-ease@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-format@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +d3-shape@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +d3-timer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + data-urls@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" @@ -1776,6 +1910,11 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +decimal.js-light@^2.4.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -1841,6 +1980,14 @@ dom-align@^1.7.0: resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511" integrity sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw== +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" @@ -2019,6 +2166,11 @@ eventemitter3@^2.0.3: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg== +eventemitter3@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + express-session@^1.17.3: version "1.17.3" resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.3.tgz#14b997a15ed43e5949cb1d073725675dd2777f36" @@ -2080,6 +2232,11 @@ fast-diff@1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154" integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig== +fast-equals@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d" + integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== + fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -2383,6 +2540,11 @@ inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -2736,10 +2898,10 @@ nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== -nanoid@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e" - integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw== +nanoid@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.6.tgz#7f99a033aa843e4dcf9778bdaec5eb02f4dc44d5" + integrity sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA== negotiator@0.6.3: version "0.6.3" @@ -3098,7 +3260,7 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -3583,7 +3745,7 @@ react-dropzone@^14.2.3: file-selector "^0.6.0" prop-types "^15.8.1" -react-is@^16.12.0, react-is@^16.13.1: +react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -3597,6 +3759,25 @@ react-quill@^2.0.0: lodash "^4.17.4" quill "^1.3.7" +react-smooth@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.0.tgz#69e560ab69b69a066187d70cb92c1a664f7f046a" + integrity sha512-2NMXOBY1uVUQx1jBeENGA497HK20y6CPGYL1ZnJLeoQ8rrc3UfmOM82sRxtzpcoCkUMy4CS0RGylfuVhuFjBgg== + dependencies: + fast-equals "^5.0.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -3624,6 +3805,27 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +recharts-scale@^0.4.4: + version "0.4.5" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" + integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== + dependencies: + decimal.js-light "^2.4.1" + +recharts@^2.12.2: + version "2.12.2" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.2.tgz#e2ee5ecad884ff3cb2192e615add75e20c3a276a" + integrity sha512-9bpxjXSF5g81YsKkTSlaX7mM4b6oYI1mIYck6YkUcWuL3tomADccI51/6thY4LmvhYuRTwpfrOvE80Zc3oBRfQ== + dependencies: + clsx "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.21" + react-is "^16.10.2" + react-smooth "^4.0.0" + recharts-scale "^0.4.4" + tiny-invariant "^1.3.1" + victory-vendor "^36.6.8" + reflect-metadata@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" @@ -3634,6 +3836,11 @@ regenerator-runtime@^0.13.11: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.2.0: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -3971,6 +4178,11 @@ through@2, through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tiny-invariant@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -4179,6 +4391,26 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +victory-vendor@^36.6.8: + version "36.9.1" + resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.1.tgz#a7536766ca9725711c7dc1a36dd1d1d248cfa22d" + integrity sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA== + dependencies: + "@types/d3-array" "^3.0.3" + "@types/d3-ease" "^3.0.0" + "@types/d3-interpolate" "^3.0.1" + "@types/d3-scale" "^4.0.2" + "@types/d3-shape" "^3.1.0" + "@types/d3-time" "^3.0.0" + "@types/d3-timer" "^3.0.0" + d3-array "^3.1.6" + d3-ease "^3.0.1" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-shape "^3.1.0" + d3-time "^3.0.0" + d3-timer "^3.0.1" + vite-ssg@^0.22.1: version "0.22.1" resolved "https://registry.yarnpkg.com/vite-ssg/-/vite-ssg-0.22.1.tgz#03f9d0615e39d47bda1c25f98bc7e34b66e3bdd9" From 9bf0a1cf1aa918a457b01a5c261d8ea0f5be327b Mon Sep 17 00:00:00 2001 From: Struck713 Date: Sat, 9 Mar 2024 03:30:06 -0500 Subject: [PATCH 05/17] Added approval email and refactored some emailing code --- global-includes/email-address.ts | 14 ++- global-includes/rpc-declarations.ts | 7 +- global-includes/users.ts | 5 +- server/auth.ts | 4 +- server/emails/approved.html | 165 ++++++++++++++++++++++++++++ server/rpc-definitions.ts | 11 +- staff-frontend/pages/users.jsx | 6 +- 7 files changed, 192 insertions(+), 20 deletions(-) create mode 100644 server/emails/approved.html diff --git a/global-includes/email-address.ts b/global-includes/email-address.ts index d30af82..90350ca 100644 --- a/global-includes/email-address.ts +++ b/global-includes/email-address.ts @@ -25,6 +25,12 @@ export enum EmailSource { Early2023 = "2023EarlySignup", } +export enum EmailTemplates { + Application = "application", + Approved = "approved", + Welcome = "welcome" +} + // TODO: maybe replace with zod's email validation, for consistency export const isEmailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; @@ -61,7 +67,13 @@ export class Email { @BackendMethod({ allowed: true }) static async addEmailAndSendWelcome(email: Partial) { - RemoteProcedures.sendWelcome((await remult.repo(Email).insert(email)).address); + RemoteProcedures.sendTemplateEmail(EmailTemplates.Welcome, (await remult.repo(Email).insert(email)).address); + } + + // TODO: should this be here????? + @BackendMethod({ allowed: [ UserRole.Admin, UserRole.Staff ] }) + static async sendTemplateEmail(template: EmailTemplates, email: string) { + RemoteProcedures.sendTemplateEmail(template, email); } @BackendMethod({ allowed: [UserRole.Admin, UserRole.Staff] }) diff --git a/global-includes/rpc-declarations.ts b/global-includes/rpc-declarations.ts index c209709..5de68e8 100644 --- a/global-includes/rpc-declarations.ts +++ b/global-includes/rpc-declarations.ts @@ -1,6 +1,6 @@ import { MailDataRequired } from "@sendgrid/mail"; import { AttachmentData } from "@sendgrid/helpers/classes/attachment"; -import { Email } from "./email-address.ts"; +import { Email, EmailTemplates } from "./email-address.ts"; import { SupportTicket, TicketMessage } from "./support-ticket.ts"; /** @@ -22,10 +22,7 @@ export class RemoteProcedures { /** General function to send KHE emails based on files, with the "KHE 2024 * Updates" unsubscribe group */ static sendUpdate: (emailHtmlFile: string, address: string) => Promise; - /** Specifically sends an email sign-up welcome email */ - static sendWelcome: (address: string) => Promise; - /** Specifically sends an application acknowledgement email */ - static sendApplicationAcknowledgement: (address: string) => Promise; + static sendTemplateEmail: (template: EmailTemplates, address: string) => Promise; static getDistinct: (collection: string, field: string) => Promise; static bulkDelete: (collection: string, filter: any) => Promise; static sendTo: ( diff --git a/global-includes/users.ts b/global-includes/users.ts index f7ad313..b85e275 100644 --- a/global-includes/users.ts +++ b/global-includes/users.ts @@ -2,7 +2,7 @@ import { BackendMethod, Entity, Fields, EntityBase, remult } from "remult"; import { VFields } from "./adaptations.ts"; import { UserRole } from "./common.ts"; import { z } from "zod"; -import { isEmailRegex } from "./email-address.ts"; +import { EmailTemplates, isEmailRegex } from "./email-address.ts"; import crypto from "crypto"; import { RemoteProcedures } from "./rpc-declarations.ts"; @@ -385,7 +385,8 @@ export class User extends EntityBase { user.submittedApplication = true; user.applicationApproved = false; await remult.repo(User).save(user); - RemoteProcedures.sendApplicationAcknowledgement( + RemoteProcedures.sendTemplateEmail( + EmailTemplates.Application, user.registration.email || user.email ); } diff --git a/server/auth.ts b/server/auth.ts index eb88a10..263c606 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -1,4 +1,4 @@ -import { ErrorRequestHandler, Express, application } from "express"; +import { Express } from "express"; import passport from "passport"; import { Strategy as GitHubStrategy, @@ -188,7 +188,7 @@ passport.deserializeUser(function ( export function registerAuthMiddleware( app: Express, - remultConfig: RemultServer + remultConfig: any // TODO: weird error with RemultServer type ) { app.use( session({ diff --git a/server/emails/approved.html b/server/emails/approved.html new file mode 100644 index 0000000..d3ee976 --- /dev/null +++ b/server/emails/approved.html @@ -0,0 +1,165 @@ + + + + + + + KHE + + + + + + + + + + + + + + diff --git a/server/rpc-definitions.ts b/server/rpc-definitions.ts index 72a564c..eabcbe4 100644 --- a/server/rpc-definitions.ts +++ b/server/rpc-definitions.ts @@ -13,7 +13,7 @@ import { MongoDataProvider } from "remult/remult-mongo"; import { RemoteProcedures } from "../global-includes/rpc-declarations.ts"; import { config } from "./config.ts"; import { TicketMessage } from "../global-includes/support-ticket.ts"; -import { Email, ImportedEmail } from "../global-includes/email-address.ts"; +import { Email, EmailTemplates, ImportedEmail } from "../global-includes/email-address.ts"; import { gridImagePath } from "../server/file-upload.ts"; import path from "path"; @@ -257,13 +257,8 @@ export function defineRemoteProcedures() { }; // TODO: better system for grabbing email content than raw relative file paths - - RemoteProcedures.sendWelcome = async function (address: string) { - await RemoteProcedures.sendUpdate("./server/emails/welcome.html", address); - } - - RemoteProcedures.sendApplicationAcknowledgement = async function (address: string) { - await RemoteProcedures.sendUpdate("./server/emails/application.html", address); + RemoteProcedures.sendTemplateEmail = async function (template: EmailTemplates, address: string) { + await RemoteProcedures.sendUpdate(`./server/emails/${template}.html`, address); } RemoteProcedures.getDistinct = async function (collection, field) { diff --git a/staff-frontend/pages/users.jsx b/staff-frontend/pages/users.jsx index ece9cfa..a873f13 100644 --- a/staff-frontend/pages/users.jsx +++ b/staff-frontend/pages/users.jsx @@ -4,6 +4,7 @@ import KHELayout from "../layouts/layout"; import { User } from "../../global-includes/users"; import { Button, Card, Layout, Modal, Row, Col, Divider } from "antd"; +import { Email, EmailTemplates } from "../../global-includes/email-address"; const { Content } = Layout; export default function UsersManager() { @@ -19,9 +20,10 @@ export default function UsersManager() { const approveUser = async (user) => { setLoading(true); await repo.save({ id: user.id, applicationApproved: true }); + await Email.sendTemplateEmail(EmailTemplates.Approved, user.email || user.registration.email) closeReview(); setLoading(false); - update() + loadUsers(); } const checkInUser = async (user) => { @@ -29,7 +31,7 @@ export default function UsersManager() { await repo.save({ id: user.id, checkedIn: true }); closeReview(); setLoading(false); - update() + loadUsers(); } const loadUsers = () => { From 85fdd44eadd459e98ff05177f1ce71a1412d8da1 Mon Sep 17 00:00:00 2001 From: Struck713 Date: Sat, 9 Mar 2024 03:36:19 -0500 Subject: [PATCH 06/17] Highlighted portfolio links and added a tooltip --- staff-frontend/pages/users.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/staff-frontend/pages/users.jsx b/staff-frontend/pages/users.jsx index a873f13..731a6c7 100644 --- a/staff-frontend/pages/users.jsx +++ b/staff-frontend/pages/users.jsx @@ -3,7 +3,7 @@ import { remult } from "remult"; import KHELayout from "../layouts/layout"; import { User } from "../../global-includes/users"; -import { Button, Card, Layout, Modal, Row, Col, Divider } from "antd"; +import { Button, Card, Layout, Modal, Row, Col, Divider, Tooltip } from "antd"; import { Email, EmailTemplates } from "../../global-includes/email-address"; const { Content } = Layout; @@ -93,7 +93,12 @@ export default function UsersManager() { Class Standing: {viewing.registration.schoolStatus} Gender: {viewing.registration.gender} Major: {viewing.registration.major} - Website: {viewing.registration.link} + + Website: + + {viewing.registration.link} + + Attended KHE: {viewing.registration.attendedKhe ? "Yes" : "No"} Pronouns: {viewing.registration.pronouns} Ethnicity: {viewing.registration.ethnicity} From ab8ae9db259db778580cc56fe2de1cae07d29493 Mon Sep 17 00:00:00 2001 From: Struck713 Date: Sat, 9 Mar 2024 03:43:55 -0500 Subject: [PATCH 07/17] Don't show buttons if user has no submitted application --- staff-frontend/pages/users.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/staff-frontend/pages/users.jsx b/staff-frontend/pages/users.jsx index 731a6c7..282d834 100644 --- a/staff-frontend/pages/users.jsx +++ b/staff-frontend/pages/users.jsx @@ -40,7 +40,9 @@ export default function UsersManager() { const getActions = (user) => { const checkedIn = user.checkedIn; - const actions = []; + const actions = []; + if (user.submittedApplication) + actions.push(); if (user.applicationApproved) actions.push() return actions; From cbe6c1be702a602660b00cb1c6b56a925c78c763 Mon Sep 17 00:00:00 2001 From: Struck713 Date: Sat, 9 Mar 2024 20:47:54 -0500 Subject: [PATCH 08/17] Added new sponsors to sponsor list and fixed some styling --- public-frontend/src/assets/bawls.webp | Bin 0 -> 10274 bytes public-frontend/src/assets/kentdisplays.webp | Bin 0 -> 27222 bytes public-frontend/src/assets/ksu-logo.svg | 118 +++++++++++++++++++ public-frontend/src/components/sponsors.vue | 12 +- public-frontend/src/views/Home.vue | 24 ++-- 5 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 public-frontend/src/assets/bawls.webp create mode 100644 public-frontend/src/assets/kentdisplays.webp create mode 100644 public-frontend/src/assets/ksu-logo.svg diff --git a/public-frontend/src/assets/bawls.webp b/public-frontend/src/assets/bawls.webp new file mode 100644 index 0000000000000000000000000000000000000000..f09366b991b5c4d7b4fb1556d3eb3b7fc36ca27e GIT binary patch literal 10274 zcmdUUWmFx@y6wWv%0=n)uTs`R+W*GBA@{Pv?RrpHI;d^5di=I$@@_Y4WNer$cT$87eKyW z0w4?Q%^cjISpWbBM|W3MX)zLQ9bFQ*KL98IA^-va2cR)Ab8}WulT!aH`#;CS(tGB6 zEHM5x)_>0VM;680!p#f-fFOC#5HWLhb$`dk@0iQW-T5#4?HyyAS(})@G?>vds|G=jI1Djd9I=ts_yysAuJ2<|N52^hx zZ2lMa`wKhRd%Vx>@A#`iWD7?PwfC0d{U8KL17rXS0OfaEO#vPN8-P8)9l-eBI=(Y* z0M++?v47AL{H<4hSDC)6Yyf8O3JHJ{zyV-^q0{}qL0RXUO0RTk3fBE*__g6olco_gtfA>m$ z8UV=50syG2-pAJehq~e41^?H#|25~o{d?cJBwZQD1n5?u7a`S5rF@e&MbeHFftenD-VWcbp;U(iW;UfQc?;{8dba@p7hCT2BbHE^!X+=WOU+R3Y?;cDTP0sPu~H^Aj(c-|Oa6Z&B=AC# zx4}*acOHo$Z<`tVM^wa2ByS=Rp{}PIEN<(}6#HSB&u`L>jY42%3tUKO{}Sj%g54j% z0BH5lU19ult&x_|zcbt>>8c9GNo$&;OKXg1ei8+bdEh~**gG9;yGzYQ?=b|nG3O$5 zneo$+wO(u%EOh;|F9nnJ#TE_*5?)hASdpIeI7hp}OnEKDd>%S(<}nidEMZ!N32rxtwu)SFuPjs);AO3Q?mca0N9!J^TVSb+7ZpLy`Eh zEUlK-ZQm)cVH^}<1;wE57k;Mup*t>9?aOpfPFc<_9R!9oi243;gxk%-qoFgtf6lschd=p~v;EWN z8@QWvmXuSrV3)mnHBEpIg=>8=QYyQgKhYO5a-wi+Ldr(zk%1?8@VGCENgpwSQlZ^j#Zu0JRpGPUSW?1FH0L1UP?%!mKWl10XJ!}oX#5}%7LM6xb6 z)d7D*<@Lh_{fQ~XnHKYS0X9OgOJS(jz&67{*rhKGb0fj6cP_5Q{D1T;{%fk}000Pl zCIALCW!!)7t)~1U2)_MohBvMo^w;a-Uy*~Y|8x0&F2#lL`T)Y#n195 zUV=2CWcyqN!;99i0|9|_Jak9UMD#Kdy+=g!P-KtVmX|tb5wW5aA~=GOu;q2VM@oC6PE>l?QdCmA*zM?$;RuJp;h8I5q0`3uSiDQIFSvzJ z!$l|l^0o(9=oLo4b^-19fEh0w!rkZ=PFqEx<1rgyk)owXst&?EN1h5ep6fsyMT@yO znHWfC(5QmKS7s-IcSVibzE0>k57YYDjX01zgLZuaj$9<3`dTOfMKfXUklG2L=k9|r*(5gzbS;nLcl_WcT?fu~O1x3pZ2o7} zkZ|kV=+04+U+Czp)4WpB)0`I{sugbNY1w>4f>WXK&~7@raqbo znf1yL$%h46CfwlQ7pVto-CgO$_S`vfhzd$0@0cgsKiiw*{ZMcuxG1Tn^Vu*KgsEhg zvYSc6Jt+04rG>Ly`so&wFus@Oz&VdI0U8T+tEi`a_N zxwAG(Ps>te@e~9;?`dcWI(5#_Cb;#(SCiU$aQeVxgP5nZD5(Yvb{nP9XU*6wu|C`M(aARqizJ>@XdL13A5juS#>Wj zJ5nVK{*%&~g7!Bar`?tt_DVKba^W(Cp9En}3t8HVl3bjLbaNo?tAb(2AD%eIu6n}t zdroQnmt^&OaE9}~#nwNy+q-=_U^T9tLV7vhuqv zO!O5QEi;q5IjLw%!JoG~Y&3WLoTt*+$fe~5kEM94iqMjBNeNxdwtVnf0RUsS&Fjdz zxPOkQyTWj*4|89>Z(n(y{W_D9#`;8yuu?J&rUF&rGY&E+@X8N%k8jCI|Z+g zy-(APy}#EnmIMV`mSE5?V$;IvsBV;=^e?a@GiJ59 z8hu3&GnP3WqPQTdqt?P`)s>sRqCuQ7u&D<-r;5eq1Kc#1w&N1}FM}mmd;b&^HDjJZ zq1Ua+@Z=)dX2wV35^42M59)@SJ0^((#Gsn=jj s*U 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­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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public-frontend/src/components/sponsors.vue b/public-frontend/src/components/sponsors.vue index 0880f50..d1ddf18 100644 --- a/public-frontend/src/components/sponsors.vue +++ b/public-frontend/src/components/sponsors.vue @@ -1,6 +1,6 @@