From c55f911ac214dbf0a12d9dd95ba20d09f6c98299 Mon Sep 17 00:00:00 2001 From: alyssagriesman Date: Tue, 18 Nov 2025 17:23:16 -0500 Subject: [PATCH 01/16] edited App.tsx, App.css, and index.css to create themes for the entire web --- frontend/src/App.css | 17 ++++++++++- frontend/src/components/SettingsMenu.css | 4 +-- frontend/src/components/SettingsMenu.tsx | 38 +++++++++++++++++++----- frontend/src/index.css | 8 +++-- 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 13e7e95..9b3a313 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -32,6 +32,22 @@ padding: 2em; } +.card.bCard, +.card { + background-color: var(--card-bg) !important; + color: var(--text-color) !important; +} + +.card-header { + background-color: var(--bg-color) !important; + color: var(--text-color) !important; +} + +.card-body { + background-color: var(--card-bg) !important; + color: var(--text-color) !important; +} + .read-the-docs { color: #888; } @@ -130,7 +146,6 @@ cursor: pointer } - .wrapper label::before { content: ""; position: absolute; diff --git a/frontend/src/components/SettingsMenu.css b/frontend/src/components/SettingsMenu.css index f4855a9..b70b266 100644 --- a/frontend/src/components/SettingsMenu.css +++ b/frontend/src/components/SettingsMenu.css @@ -6,5 +6,5 @@ border-radius: 15%; width: 50px; height: 50px; - background-color: white; -} \ No newline at end of file +} + diff --git a/frontend/src/components/SettingsMenu.tsx b/frontend/src/components/SettingsMenu.tsx index 1538022..2009ac9 100644 --- a/frontend/src/components/SettingsMenu.tsx +++ b/frontend/src/components/SettingsMenu.tsx @@ -1,11 +1,26 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import "./SettingsMenu.css"; import "bootstrap/dist/css/bootstrap.min.css"; export default function SettingsMenu() { + const themes = { + pink: {bg: "#F2B8CF", text: "#4A2B33", card: "#F6D1E6"}, + green: {bg: "#C8EABB", text: "#2F3A2D", card: "#D4F5D0"}, + lightblue: {bg: "#9bd0ec", text: "#0F2B3A", card:"#C9ECF6"}, + white: {bg: "#E8E8E8", text: "#1A1A1A", card: "#d8d6d6"}, + black: {bg: "#383838", text: "#F5F5F5", card: "#2A2A2A"} + }; + const [firstOption, setFirst] = useState(false); const [secondOption, setSecond] = useState(false); + const [theme, setTheme] = useState(themes.black); + useEffect(() => { + document.documentElement.style.setProperty("--bg-color", theme.bg); + document.documentElement.style.setProperty("--text-color", theme.text); + document.documentElement.style.setProperty("--card-bg", theme.card); + },[theme]) + return (
-
-
-
+ + + + +
Miscellaneous diff --git a/frontend/src/index.css b/frontend/src/index.css index 2a6dc22..b5fa5ae 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,12 +1,12 @@ - :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + --bg-color: #080909; + --text-color: rgba(255, 255, 255, 0.87); + --card-bg: #dd9ec7; font-synthesis: none; text-rendering: optimizeLegibility; @@ -24,6 +24,8 @@ a:hover { } body { + background-color: var(--bg-color); + color: var(--text-color); margin: 0; display: flex; place-items: center; From 887090ef625da2148528375ead601946d90cd04f Mon Sep 17 00:00:00 2001 From: alyssagriesman Date: Thu, 12 Feb 2026 18:18:22 -0500 Subject: [PATCH 02/16] "Edited user settings themes and fixed theme reset on Settings page" --- frontend/package-lock.json | 12 ----- frontend/src/App.tsx | 61 ++++++++++++---------- frontend/src/components/GooberFoodMenu.tsx | 6 ++- frontend/src/components/GooberGiftMenu.tsx | 6 ++- frontend/src/components/GooberInfo.css | 4 +- frontend/src/components/GooberInfo.tsx | 9 ++-- frontend/src/components/GooberMenu.css | 15 +++++- frontend/src/components/GooberMenu.tsx | 10 ++-- frontend/src/components/GooberPlayMenu.tsx | 6 ++- frontend/src/components/Home.css | 1 - frontend/src/components/SettingsMenu.css | 2 +- frontend/src/components/SettingsMenu.tsx | 45 ++++++++-------- frontend/src/components/ThemeProvider.tsx | 46 ++++++++++++++++ frontend/src/index.css | 11 +++- 14 files changed, 147 insertions(+), 87 deletions(-) create mode 100644 frontend/src/components/ThemeProvider.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c0ee928..4655864 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -79,7 +79,6 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1534,7 +1533,6 @@ "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1545,7 +1543,6 @@ "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1596,7 +1593,6 @@ "integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.41.0", "@typescript-eslint/types": "8.41.0", @@ -1823,7 +1819,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1977,7 +1972,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -2254,7 +2248,6 @@ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -3219,7 +3212,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3524,7 +3516,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3587,7 +3578,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3660,7 +3650,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -3774,7 +3763,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2aa6c1e..9adf127 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import Timer from "./components/Timer"; import ToDoList from "./ToDoList"; import GooberMenu from "./components/GooberMenu"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import { ThemeProvider } from './components/ThemeProvider'; function App() { // had to add because bootstrap defaults to light mode @@ -19,36 +20,38 @@ function App() { const [currentHealth, setHealth] = useState(50); return ( - -
-
- + + +
+
+ +
+ + + } /> + } /> + } /> + } /> + } /> +
- - - } /> - } /> - } /> - } /> - } /> - -
- + + ); } diff --git a/frontend/src/components/GooberFoodMenu.tsx b/frontend/src/components/GooberFoodMenu.tsx index 4bae68d..5b16850 100644 --- a/frontend/src/components/GooberFoodMenu.tsx +++ b/frontend/src/components/GooberFoodMenu.tsx @@ -5,8 +5,10 @@ interface Props { export default function GooberFoodMenu({ pageSetter }: Props) { return ( <> - place gober food menu here - +
+ place gober food menu here + +
); } diff --git a/frontend/src/components/GooberGiftMenu.tsx b/frontend/src/components/GooberGiftMenu.tsx index 862bc28..86b5861 100644 --- a/frontend/src/components/GooberGiftMenu.tsx +++ b/frontend/src/components/GooberGiftMenu.tsx @@ -5,8 +5,10 @@ interface Props { export default function GooberGiftMenu({ pageSetter }: Props) { return ( <> - place gober gift menu here - +
+ place gober gift menu here + +
); } diff --git a/frontend/src/components/GooberInfo.css b/frontend/src/components/GooberInfo.css index 07ff5fe..df0d92b 100644 --- a/frontend/src/components/GooberInfo.css +++ b/frontend/src/components/GooberInfo.css @@ -1,7 +1,9 @@ .infoBackground { - background-color: "grey"; display: "grid"; grid-template-columns: "1fr 1fr 1fr"; margin: "-16px"; padding: "10px"; +} +.gooberInfo{ + background-color: var(--bg-color); } \ No newline at end of file diff --git a/frontend/src/components/GooberInfo.tsx b/frontend/src/components/GooberInfo.tsx index 9e846db..28d95f0 100644 --- a/frontend/src/components/GooberInfo.tsx +++ b/frontend/src/components/GooberInfo.tsx @@ -42,9 +42,8 @@ export default function GooberInfo({ return ( <> -
Cash money: ${money}
-
Shop
-
@@ -88,9 +89,8 @@ export default function GooberMenu({ > {gooberName}
-
setPage("food")} @@ -111,7 +111,7 @@ export default function GooberMenu({ +
+ place gober play menu here + +
); } diff --git a/frontend/src/components/Home.css b/frontend/src/components/Home.css index 13e7e95..a35b313 100644 --- a/frontend/src/components/Home.css +++ b/frontend/src/components/Home.css @@ -130,7 +130,6 @@ cursor: pointer } - .wrapper label::before { content: ""; position: absolute; diff --git a/frontend/src/components/SettingsMenu.css b/frontend/src/components/SettingsMenu.css index b70b266..042abce 100644 --- a/frontend/src/components/SettingsMenu.css +++ b/frontend/src/components/SettingsMenu.css @@ -5,6 +5,6 @@ .themeChoice{ border-radius: 15%; width: 50px; - height: 50px; + height: 45px; } diff --git a/frontend/src/components/SettingsMenu.tsx b/frontend/src/components/SettingsMenu.tsx index 1d6d54c..cc692ec 100644 --- a/frontend/src/components/SettingsMenu.tsx +++ b/frontend/src/components/SettingsMenu.tsx @@ -1,26 +1,18 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useContext } from "react"; import "./SettingsMenu.css"; import "bootstrap/dist/css/bootstrap.min.css"; +import {ThemeContext} from "./ThemeProvider.tsx"; export default function SettingsMenu() { - const themes = { - pink: {bg: "#F2B8CF", text: "#4A2B33", card: "#F6D1E6"}, - green: {bg: "#C8EABB", text: "#2F3A2D", card: "#D4F5D0"}, - lightblue: {bg: "#9bd0ec", text: "#0F2B3A", card:"#C9ECF6"}, - white: {bg: "#E8E8E8", text: "#1A1A1A", card: "#d8d6d6"}, - black: {bg: "#383838", text: "#F5F5F5", card: "#2A2A2A"} - }; - + const context = useContext(ThemeContext); + if (!context) { + throw new Error("SettingsMenu must be used within a ThemeProvider"); + } + const {theme, setTheme, themes} = context; + const [firstOption, setFirst] = useState(false); const [secondOption, setSecond] = useState(false); - const [theme, setTheme] = useState(themes.black); - useEffect(() => { - document.documentElement.style.setProperty("--bg-color", theme.bg); - document.documentElement.style.setProperty("--text-color", theme.text); - document.documentElement.style.setProperty("--card-bg", theme.card); - },[theme]) - return (
-
-
-
+ + + + +
Miscellaneous diff --git a/frontend/src/components/ThemeProvider.tsx b/frontend/src/components/ThemeProvider.tsx new file mode 100644 index 0000000..31fa955 --- /dev/null +++ b/frontend/src/components/ThemeProvider.tsx @@ -0,0 +1,46 @@ +import { useState, useEffect, createContext } from 'react'; + +export type Theme = { + bg: string; + text: string; + card: string; + button: string; +} + +export const themes: Record = { + pink: {bg: "#F2B8CF", text: "#4A2B33", card: "#F6D1E6", button: "#4A2B33"}, + green: {bg: "#B7E0A6", text: "#2F3A2D", card: "#D4F5D0", button: "#2F3A2D"}, + lightblue: {bg: "#9bd0ec", text: "#0F2B3A", card:"#C9ECF6", button: "#0F2B3A"}, + white: {bg: "#E8E8E8", text: "#1A1A1A", card: "#d8d6d6", button: "#3A3A3A"}, + black: {bg: "#383838", text: "#F5F5F5", card: "#2A2A2A", button: "#8C8C8C"} +}; + +type ThemeContextType = { + theme: Theme; + setTheme: React.Dispatch>; + themes: typeof themes; +} + +export const ThemeContext = createContext(undefined); + +type ThemeProviderProps = { + children: React.ReactNode; +} + +export function ThemeProvider({children}: ThemeProviderProps){ + const [theme, setTheme] = useState(themes.black); + + useEffect(() => { + console.log("Theme changed:", theme); + document.documentElement.style.setProperty("--bg-color", theme.bg); + document.documentElement.style.setProperty("--text-color", theme.text); + document.documentElement.style.setProperty("--card-bg", theme.card); + document.documentElement.style.setProperty("--button-bg", theme.button); + }, [theme]); + + return ( + + {children} + + ); +} diff --git a/frontend/src/index.css b/frontend/src/index.css index b5fa5ae..be9e8ce 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -39,18 +39,25 @@ h1 { } button { + border: none; + border: 2px solid var(--text-color); border-radius: 8px; border: 1px solid transparent; + padding: 0.6em 1.2em; + margin: 3px; + font-size: 1em; font-weight: 500; font-family: inherit; - background-color: #1a1a1a; + background-color: var(--button-bg); cursor: pointer; transition: border-color 0.25s; } button:hover { - border-color: #646cff; + background-color: var(--bg-bg); + color: var(--text-color); + border: 2px solid var(--text-color); } button:focus, button:focus-visible { From 24d8eaa58422f0ccd185acc815c9f2d95ff028d0 Mon Sep 17 00:00:00 2001 From: Kaydence Rao Date: Thu, 2 Apr 2026 17:58:17 -0400 Subject: [PATCH 03/16] Add goober assets --- frontend/src/assets/goober-doggo.png | Bin 0 -> 6756 bytes frontend/src/assets/goobers/goober-bear.png | Bin 0 -> 6266 bytes frontend/src/assets/goobers/goober-cat.png | Bin 0 -> 6701 bytes frontend/src/assets/goobers/goober-doggo.png | Bin 0 -> 6756 bytes frontend/src/assets/goobers/goober-fox.png | Bin 0 -> 6580 bytes frontend/src/assets/goobers/goober-panda.png | Bin 0 -> 6575 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 frontend/src/assets/goober-doggo.png create mode 100644 frontend/src/assets/goobers/goober-bear.png create mode 100644 frontend/src/assets/goobers/goober-cat.png create mode 100644 frontend/src/assets/goobers/goober-doggo.png create mode 100644 frontend/src/assets/goobers/goober-fox.png create mode 100644 frontend/src/assets/goobers/goober-panda.png diff --git a/frontend/src/assets/goober-doggo.png b/frontend/src/assets/goober-doggo.png new file mode 100644 index 0000000000000000000000000000000000000000..562bac8f0de1ab8fcf89184b9d1fc65a38659456 GIT binary patch literal 6756 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 I4>@1?7lsk}RR910 literal 0 HcmV?d00001 diff --git a/frontend/src/assets/goobers/goober-bear.png b/frontend/src/assets/goobers/goober-bear.png new file mode 100644 index 0000000000000000000000000000000000000000..84bb1ed106ff8293255b9882e81401af688452a9 GIT binary patch literal 6266 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 QtPo6$EOtHL;d17G0BTyo{r~^~ literal 0 HcmV?d00001 diff --git a/frontend/src/assets/goobers/goober-cat.png b/frontend/src/assets/goobers/goober-cat.png new file mode 100644 index 0000000000000000000000000000000000000000..3e0ef9fae57ccce2bf2f6230dfe61896930a46db GIT binary patch literal 6701 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 AJpcdz literal 0 HcmV?d00001 diff --git a/frontend/src/assets/goobers/goober-doggo.png b/frontend/src/assets/goobers/goober-doggo.png new file mode 100644 index 0000000000000000000000000000000000000000..562bac8f0de1ab8fcf89184b9d1fc65a38659456 GIT binary patch literal 6756 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 I4>@1?7lsk}RR910 literal 0 HcmV?d00001 diff --git a/frontend/src/assets/goobers/goober-fox.png b/frontend/src/assets/goobers/goober-fox.png new file mode 100644 index 0000000000000000000000000000000000000000..c628b756bce452c7c05bb637c4279a61230d7d1b GIT binary patch literal 6580 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≪SWPj4m_}q2;)9q-wA+(iqr}J(C zBGjS7i^mAS3k3|QmQe~&X=6p~Ja%JBnnjYhXQHo4| z3*R1T@NQ+iB??D*^=qgqOj zFJLQ`3wUMp57wGW5L@`)gLhmw3;ck0r@>J+2p}{LC9u3aO}B`%Gr)2X&hbvfDn4? zLJnxjhPr-#N`QuO!cOLfdg1@|ppO-~ELbd^YmJs@z9t1}yI!qh!*qf*{EKnOh*LW0RL(*O#WkfJJy^4YCbmUUOhPPOdH$Azlx>hcMu+G#djjq9B zJ#;v(<_s)t<1k}m;&Q#Z&4i~wdOdOP4ujmY07~P(hp+SsWXPZw6TdC5Tg4&B>gs{B itTOn&mtU|!iwk-DBKOwW?BNd}L5}I09LYaSx%yv)nUY@s literal 0 HcmV?d00001 diff --git a/frontend/src/assets/goobers/goober-panda.png b/frontend/src/assets/goobers/goober-panda.png new file mode 100644 index 0000000000000000000000000000000000000000..85364969f79ad8e9d9194fbfb81eeecfd0b8cbe6 GIT binary patch literal 6575 zcmZu$2{_c-`=6Oc4Z_${mT93vD3!A0+itI@hzadv%NCL`y6t6Yu~Zm0Eu=w<8ARi9 zGfC1d`zSQBZ$s9>{NCf<=lL)9cc14z&p7vc-t#%1_w#VW6 z3G)~ctv0-gP~9j+`ju<-oPd*dAy(;MnD!rdfe!8j( zb|g}qyocUWjE;-qZl~ua zX*PJg{mH{05x94-^B>m7FQE7iX;*No9lk$@E$%I#<1&lrPxe4~DuJ*fvB+Qi7sWl= z7CwJc>E#cf=i6a)>Zz<#u+aK76zsZQP6ZH^MuphEJ|AyF?49uO!^&d;h<1Yk79H2P zFhauw5ZGe$=3PE%&cu&EdIW)RoN?zBfXCdmDA-BLSwyTm1DnD%8fDLorv(7G zF#KG~3xG7eFut6He%fvVTY?nlQXsVp);$a3CV8*r0A{l`1zYC(09K;px?%J_c7PT_ zzXSLv&9wwvczzuu^z`%ln}Omq90bQ?ZK43=28g#fYfP9Nu^mG4#E9?v+vl%<@&Y<+A<9Qs zHW0t??~LTc=wPmjL1uu*SdmOgQTqN8c02Tpjte{Rd2~j?X>-&kP<^Kn{U<6TypE6EBtW`?2N&!XVg&u{P^*2l9@Mz~Y18 zJZ;!370O9(Mu}L9gDb{qC<98sd`h8Anxlkxv?kmZ+?HCsb1fNrv+@#6p_(u+3 zW0sAzw~CpxnOQWKFFn|4q(1Ixfzj9Rx3LC~DoKH)+xZ-m&9E&wFa8{ALQ(Y!>;-Fu z;tTV2=)F~8*flX3)L>VfF_av^REuETI!9xUhSUTyH?}#QW}JzA=hGJ%U>+mfA_W{< z-^dIEn6GsxSQC>O57pIb52m#`=aV?NntO9E$Z)jWG1efgT-7{MnwPmirn-vH8nxLn zIx%IUm3x4ZpKwIzG{`%3CEUQ5uXslo+&rG?gwgj?O&vg_m>nK}+kw;mqAD6V03{!v z_x=b{gA-U$rJiP_N!Vpob2kXQQa$ws*JswoY&3N0jibA3b$;CulTq(dv&HNeqKX;Q z0imgS?IKd#X{!?tVg4#>Ft!6SuYeP50~E-8`+Hrk_)b~MnOMjww{aCx0=Uh3M|;U- zz?&I?v*5iMfAJ-h3T8t%`a=c(m%!9fW`n2m}Ndb%>n}>UON?NyHop zO1yMX>x-T`8_+NjG>m_!hcpbMtJ8ZLPs-9*=Ds5>^WJYOQ45$Lc+rc^7vrDS@R~X-7dDETKeyK5S+VV*)`hsEwmX1qI_uK#Z{2cg0 z@xkwf8Zz@C3~1Z+N8nlrGX>+LwX{V~ryENHJM*Kt=Sd1W;mKW{#nWEl)0Iz^ zqLc}b5;X6xgzrgIIN!4O)}r6WWHLofp;3+}pSz5p3Cfjw7I#Lvtzg*sfk-v7^!Lc1 z(S9DA8{f~RNQwL(AMP^Q@NJsr{ARrLMGe1KKL-w@UcVrSxnJk=Df)gFJ$aenF7HXYhr-VquyXCJOxcNRQk)wi8y(7XN!LODCIE=DV4{oQIoFEvdLhcH#q85VOph>~buHAeM zsjma7K<{YeGBOf;z)(ZXD;@GxFEy4<(#zE+!JbqEQpmgS1E{K6!w{I>C!61;iVu)| z?u#-H?&O(Ms=>d#g*Pp#H&Az(x!C#L-^Hx)j}EaP;Y+>!)`Ygvghk3+ud=a;#656K zJc_q+=3XKcYGLk{7+p7WA_n0An|iNxOCGt0yJc$(?$F};kQ7o8hhrY|+Rn*Nf8^?k zP2;~Xy%o9J;UL2TBw!9{8VrqQB#?qL@A<`&OQ6SX)Whf`I{`;fK(OKSHKrl4ygG#r zjuF{y35%WWHxp`bmDlX$H6zG765e~BE3PU_rerxw1FiHDLuhAg%Ey5rN-ofSFidp? z>O1@^p+bMwXLPGbmklktoKyGfsJyQ4qPNd4AF=qHu189S|J>P6wFH{7fu^jVY(2n6 z)djPiUxyAjXAmm$AnQ(WglKj~md!jNQ)Up>xo)B7IJ-Qyb1{qkDWu3M%2#U*R=7Iv z5?Sd8&>PKcz3Yy0F zc^CU9r+?ep+`(m!@qzHV$r$-f%P3fk$L2&J*;Z23B^kH5<4AogRx zeUG_TG1OAaxc#+aS2_oFH=Qp69fi}TyJr6qG0G2$Ud^)XT1?gJ7O^MAcI%cEjP%A1 za0A78yK_QlJV zRW83E12IXez&y7qA-k*N%|w{+UjLW0w-Lgzt2~Zqgkx6ceBQS~&&TZ4X*oBCrgI;k zdPCWqjrfS<_72*cqt?fcf|}*d*l&bmA^Lj>aFXGI(Q6KK9yiMH6B#~>E^`ge=Lwt~ z8AGW@nN=+z-fbVN-Fi(_c|AFK{Ia3o9H}yLg7WNDmkB$s9boVF??)=nXacfNz-ORu zHh4WY4%7%;;PwCw(`gMydX4MW5jVxFk=!Gm;iEs)79>vH}}A!X47J2u}ZScL(){uGjor4!5(G%=dN#BXy6 zg$<@3{RF0ji_!q@7W`&TYSwXIPgnz9r}QlU?d6G?)bEw75cMu#-PB>nfhu&KO|SkOoleKmY~IO#^{ffYBd0hF;r}dLen~QUe1j& zIh+c^fi9JaScWJkGVEZkR_@-xikaNA%wscQrq40-cxc+7qb%5VfC1sVFpM6w_qa9m$R&z|a6m3XSvPwb|fF+NbHz z3p103!xgjB_X=m)xP#Sqe;Cw7HhA!lXb^;OQ`oZd)ZA^t2BUw(`s7wwl^iE20J{=; zF`4LAavYHs+%dR{HRhJ`y-4IJH_N0KOEPEav7bl(CrT+|vo{-tL7COqLOky`T>;1`sOM)rs{!%5U85^-k7W!Vb{ zcXU`!FXBvikBqDF*D@5N#B%mj3SQ=#&Hmipst!P6U+qJ!Lo&1ud5<~}iKRedo7g%t zSne(`w9ZkBrXk02OR<}NyWCs!vqdzz+;?PFGIZrS#_b3~f!Ixs1K9Y*fW@ecZNU3$ z%22-o#ZIP}{GQBmE4c^ig7aD;D=<_>j?L`8G_LA@qb1~GKeM-!s~@^jj%L*MYR$BI z*?EaE8Io=&%{lhl#eRXl2B97#?nNE70$2+Pq_Iu2W9Iv@d(=@#YC(LdQy;;EUX&YCt z-*YJPR>_6&Q)|kqM@bw>Ikx2P z`ruxGG$rAJTwxf9HXYc7Zn&dpRL&Vj3y9~y<&M9d*D<)M{jZSPoD!Qe&Efeu5Ocw2WLeZ`R) z)Q(NTIy1G+*O6&}Q^TmGAEC~gUcz6B`M#Ab4HP{eQxA*wdXJ)J z`H9#t(cp6#HTIBFpejfL$3BLh@4Z|Rwyv?$L_f|2VYO1 z;q~<-blPcyeB%{9tcNY^y$_YgTvlIo%CA~5h&z8%WIG$F5pLq|ngXdxZI%hn{_s5K zpWYwBN5f*}lHOrNk^<#(mjclu{ZgJTfS|&&N48QO$eg$sdX#7-7W2d*DLg?FP7k@P zqjumO8Q^RC_pWe2YSfF$^A~%6Kjs*WQ-=0qbkoQF2tBb5iUbY2DxNWmx(rrO_AYNd zioY7-*D%uuD>%Wghy0e-zL0oTw8OoPmej1DxbWH6(?kiGxYMe(cOl3(Abh~e_Ya6| z0X@%`O+A;eK!VZ^ceoRIi*L3=3*CTb_13#Yw0y261!GQHJjG8=xh0-pRZIVT_QV(!3UzQ-RCe-C#ufPWL8Pa8CRh8ymao z;$oe5zlILN8ZR ztzYMD#4@ccfcr9aVbQl@Ltu07Q+U34<$Drc0s6qB!o^WR!0TWHyj&G^)X&tm6@)bx zE?BKf$v)7acfRn6V?IDE!k(aFu?Hn&#%CUJ?XaL@^}to{unQ{aPawyUv)NQKsk)ZW zI&(e8SK+FcEXz-|X>YxR!e4Lx*B*%$5gQDT0VN7>7F=KTn`QGFcT)n<6%wAar02*^ z3Tp5_WJ+t2uQJQdUeF8P+L{S(4_166OVV@kWXZTZb+zTF`1GK3l~1rNVEhqG@3yrb z^k1}cqVao6i-!M_*uzue)j#=XyWM@xZaH+oTZOf9fquia!T4M=X+fC&Oq053xO*o? z|7Gvli)!I0G-ZqShXBJ}JJa6ax*zA(p$kA36mo6pl$#={x}VQF_wu*c25s-)|PS`jx8ybH{$zJrT9SvZ1NqQYZV4&4Al^;XbDbjNoKA^q;3Ic0|C%7;z zemHtmSj3it4&U;3n>#S|G1T}M?W0>@Y6d{tZ=bRJYY?ir0k}hjtXhJ)WGRR<|Hf%c zw7)JdD*lUOF*5P1sllBaC(9oi`(#tWvv0~huh^g zt^0Xt)L;Z>xzlb(_BQtm{{bZcYr6md literal 0 HcmV?d00001 From b6a830dfe9794c67397e3a0bea23c74b2155f520 Mon Sep 17 00:00:00 2001 From: Andres <84483578+fmcubium@users.noreply.github.com> Date: Thu, 2 Apr 2026 18:12:51 -0400 Subject: [PATCH 04/16] Add contributing section to README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 3f02ec5..b6dd2b4 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,12 @@ This will generate the build files in the `build` directory. - `tsconfig.json`: TypeScript configuration file. - `package.json`: Contains the project dependencies and scripts. +### Contributing + +When making a pull request to the frontend, you *must* follow these rules to ensure your PR is not automatically rejected: +- Pull requests must be made to the `dev` branch, *NOT* `main`. +- Pull requests must include an image of the changes made to the frontend. + ## Backend (Windows/Linux) ### Prerequisites From e327c2be3ca5bee3ef2e1961c609a56c18643b9a Mon Sep 17 00:00:00 2001 From: fmcubium Date: Thu, 7 May 2026 15:32:37 -0400 Subject: [PATCH 05/16] Added task serializer --- backend/quickstart/serializers.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 backend/quickstart/serializers.py diff --git a/backend/quickstart/serializers.py b/backend/quickstart/serializers.py new file mode 100644 index 0000000..81aac9d --- /dev/null +++ b/backend/quickstart/serializers.py @@ -0,0 +1,7 @@ +from .models import Task +from rest_framework import serializers + +class TaskSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Task + fields = ["reward", "name", "category", "due_date", "description", "username"] \ No newline at end of file From 2d656abbb6254f9b4e3d2dbf86ebff90315f1648 Mon Sep 17 00:00:00 2001 From: fmcubium Date: Thu, 7 May 2026 15:52:00 -0400 Subject: [PATCH 06/16] Created serializer and a simple post request in preparation for test writing --- backend/quickstart/serializers.py | 2 ++ backend/quickstart/views.py | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/backend/quickstart/serializers.py b/backend/quickstart/serializers.py index 81aac9d..7ace51a 100644 --- a/backend/quickstart/serializers.py +++ b/backend/quickstart/serializers.py @@ -1,6 +1,8 @@ from .models import Task from rest_framework import serializers +# serializers are cool because they allow you to skip having to deal with the model constructors + class TaskSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Task diff --git a/backend/quickstart/views.py b/backend/quickstart/views.py index 4c9a04b..5b171f1 100644 --- a/backend/quickstart/views.py +++ b/backend/quickstart/views.py @@ -2,12 +2,24 @@ from rest_framework.response import Response from rest_framework.decorators import api_view from rest_framework import status -from .models import StudyUser + +from .models import StudyUser # remove, should be serializer +from .serializers import TaskSerializer # Create your views here. -@api_view(['GET',]) +@api_view(['GET']) def ping(request): - if request.method == 'GET': - print(type(StudyUser)) - return Response(b"pong") \ No newline at end of file + print(type(StudyUser)) + return Response("pong") + +@api_view(['POST']) +def create_task(request): + serializer = TaskSerializer(data=request.data) + + if serializer.is_valid(): + # save and return + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) From 09973e548731929a584d64a8710858585bd6dabe Mon Sep 17 00:00:00 2001 From: fmcubium Date: Fri, 8 May 2026 13:31:07 -0400 Subject: [PATCH 07/16] added url for post request --- backend/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/api.py b/backend/api.py index a427e2f..782d4d7 100644 --- a/backend/api.py +++ b/backend/api.py @@ -3,4 +3,5 @@ urlpatterns = [ path("ping/", ping), + path("create_task/", create_task), ] From 09e8931e0674e4f569bd55b38c8d82a6e4254e8d Mon Sep 17 00:00:00 2001 From: fmcubium Date: Fri, 8 May 2026 18:18:24 -0400 Subject: [PATCH 08/16] Set up tests with help from Gemini --- .gitignore | 4 +++ backend/quickstart/admin.py | 4 +++ backend/quickstart/models.py | 6 ++-- backend/quickstart/serializers.py | 4 ++- backend/quickstart/tests.py | 54 +++++++++++++++++++++++++++++- backend/quickstart/views.py | 8 +++-- backend/settings_template.txt | 2 ++ db.sqlite3 | Bin 131072 -> 0 bytes pytest.ini | 3 ++ 9 files changed, 77 insertions(+), 8 deletions(-) delete mode 100644 db.sqlite3 create mode 100644 pytest.ini diff --git a/.gitignore b/.gitignore index cabf345..acf3a81 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ backend/__pycache/* # personal settings backend/settings.py +# pytest +.pytest_cache/ +*.py[cod] + # Personal Migrations backend/quickstart/migrations/* diff --git a/backend/quickstart/admin.py b/backend/quickstart/admin.py index 8c38f3f..723d07a 100644 --- a/backend/quickstart/admin.py +++ b/backend/quickstart/admin.py @@ -1,3 +1,7 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import StudyUser + +admin.site.register(StudyUser, UserAdmin) # Register your models here. diff --git a/backend/quickstart/models.py b/backend/quickstart/models.py index 03b1a92..ac31acb 100644 --- a/backend/quickstart/models.py +++ b/backend/quickstart/models.py @@ -1,9 +1,9 @@ from django.db import models -from django.contrib.auth.models import User +from django.contrib.auth.models import AbstractUser # Create your models here. -class StudyUser(User): +class StudyUser(AbstractUser): # inherits: username, password, email, ... money = models.IntegerField(default=100) @@ -14,4 +14,4 @@ class Task(models.Model): category = models.TextField(null=True) due_date = models.DateTimeField() description = models.TextField() - username = models.ForeignKey(StudyUser, on_delete=models.CASCADE) + user = models.ForeignKey(StudyUser, on_delete=models.CASCADE) diff --git a/backend/quickstart/serializers.py b/backend/quickstart/serializers.py index 7ace51a..c34aed4 100644 --- a/backend/quickstart/serializers.py +++ b/backend/quickstart/serializers.py @@ -4,6 +4,8 @@ # serializers are cool because they allow you to skip having to deal with the model constructors class TaskSerializer(serializers.HyperlinkedModelSerializer): + user = serializers.HiddenField(default=serializers.CurrentUserDefault()) + class Meta: model = Task - fields = ["reward", "name", "category", "due_date", "description", "username"] \ No newline at end of file + fields = ["reward", "name", "category", "due_date", "description", "user"] \ No newline at end of file diff --git a/backend/quickstart/tests.py b/backend/quickstart/tests.py index 7ce503c..876257f 100644 --- a/backend/quickstart/tests.py +++ b/backend/quickstart/tests.py @@ -1,3 +1,55 @@ -from django.test import TestCase +# Some tests created with aid from Gemini +import pytest +from rest_framework import status +from .models import StudyUser, Task + +@pytest.fixture +def api_client(): + from rest_framework.test import APIClient + return APIClient() + +@pytest.fixture +def test_user(db): + """Creates a StudyUser for testing.""" + return StudyUser.objects.create_user( + username="andres", + password="password123", + money=500 + ) + +@pytest.mark.django_db +class TestTaskCreation: + def test_create_task_authenticated(self, api_client, test_user): + # 1. Authenticate + api_client.force_authenticate(user=test_user) + + # 2. Prepare Data (No user info in JSON, handled by CurrentUserDefault) + url = "/api/create_task/" # Ensure this matches your urls.py + data = { + "name": "Math Homework", + "reward": 50, + "description": "Finish algebra 1", + "due_date": "2026-12-31" + } + + # 3. Request + response = api_client.post(url, data, format='json') + + # 4. Assertions + assert response.status_code == status.HTTP_201_CREATED + + # Verify DB entry + task = Task.objects.get(name="Math Homework") + assert task.user == test_user + assert task.user.money == 500 + + def test_create_task_unauthenticated(self, api_client): + """Ensure logged-out users can't create tasks.""" + url = "/api/create_task/" + data = {"name": "Ghost Task"} + + response = api_client.post(url, data, format='json') + + assert response.status_code == status.HTTP_403_FORBIDDEN # Create your tests here. diff --git a/backend/quickstart/views.py b/backend/quickstart/views.py index 5b171f1..c2cfcc9 100644 --- a/backend/quickstart/views.py +++ b/backend/quickstart/views.py @@ -1,6 +1,7 @@ from django.shortcuts import render from rest_framework.response import Response -from rest_framework.decorators import api_view +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated from rest_framework import status from .models import StudyUser # remove, should be serializer @@ -13,9 +14,10 @@ def ping(request): print(type(StudyUser)) return Response("pong") -@api_view(['POST']) +@api_view(['POST']) # Some of this function was made with the help of Gemini +@permission_classes([IsAuthenticated]) def create_task(request): - serializer = TaskSerializer(data=request.data) + serializer = TaskSerializer(data=request.data, context={"request": request}) if serializer.is_valid(): # save and return diff --git a/backend/settings_template.txt b/backend/settings_template.txt index 216e5e9..51d9aee 100644 --- a/backend/settings_template.txt +++ b/backend/settings_template.txt @@ -85,6 +85,8 @@ DATABASES = { } } +AUTH_USER_MODEL = "quickstart.StudyUser" + # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators diff --git a/db.sqlite3 b/db.sqlite3 deleted file mode 100644 index de798d60932ed7f25dd5de70cbd8b009de805fdf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131072 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 Date: Sat, 9 May 2026 12:44:13 -0400 Subject: [PATCH 09/16] Create GitHub Workflow Added a GitHub Action for CI that will automatically run tests whenever someone submits a PR --- .github/workflows/python-app.yml | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..0d67b76 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,39 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.12.3" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest From 484b8020542f9db8a37f60ab6f2bcbcf993e8123 Mon Sep 17 00:00:00 2001 From: Andres <84483578+fmcubium@users.noreply.github.com> Date: Sat, 9 May 2026 12:49:02 -0400 Subject: [PATCH 10/16] Add djangorestframework and psycopg2-binary to dependencies --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 0d67b76..0937fc3 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest + pip install flake8 pytest djangorestframework psycopg2-binary if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | From aa96e4c452ccfca50096df14f61876a66f62de4a Mon Sep 17 00:00:00 2001 From: Andres <84483578+fmcubium@users.noreply.github.com> Date: Sat, 9 May 2026 12:52:19 -0400 Subject: [PATCH 11/16] Fixing errors #2 --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 0937fc3..9fb9761 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest djangorestframework psycopg2-binary + pip install flake8 djangorestframework psycopg2-binary pytest-django if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | From 69dd8f8db1355891d46554a347e186049d077ea7 Mon Sep 17 00:00:00 2001 From: fmcubium Date: Sat, 9 May 2026 13:27:02 -0400 Subject: [PATCH 12/16] Updated the ini and requirements.txt Updated yml, requirements.txt, and ini --- .github/workflows/python-app.yml | 6 ++++++ backend/requirements.txt | 2 +- pytest.ini | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 9fb9761..f48317a 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -34,6 +34,12 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Create test settings.py + run: | + cp backend/settings_template.txt backend/settings.py + # use sed to use SQLite for testing + sed -i "s/'ENGINE': 'django.db.backends.postgresql'/'ENGINE': 'django.db.backends.sqlite3'/g" backend/settings.py + sed -i "s/'NAME': 'your_database_name'/'NAME': ':memory:'/g" backend/settings.py - name: Test with pytest run: | pytest diff --git a/backend/requirements.txt b/backend/requirements.txt index f646522..d5da976 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,4 +1,4 @@ django djangorestframework psycopg2-binary - +pytest-django diff --git a/pytest.ini b/pytest.ini index f150109..9600dcb 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] DJANGO_SETTINGS_MODULE = backend.settings +pythonpath = . python_files = tests.py test_*.py *_tests.py \ No newline at end of file From 3d3cd196478517bf53408ec383f5da857ac6e3de Mon Sep 17 00:00:00 2001 From: Andres <84483578+fmcubium@users.noreply.github.com> Date: Sat, 9 May 2026 13:52:26 -0400 Subject: [PATCH 13/16] Add migration step to CI workflow Added steps to make and run database migrations before testing. --- .github/workflows/python-app.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index f48317a..cda6db4 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -40,6 +40,10 @@ jobs: # use sed to use SQLite for testing sed -i "s/'ENGINE': 'django.db.backends.postgresql'/'ENGINE': 'django.db.backends.sqlite3'/g" backend/settings.py sed -i "s/'NAME': 'your_database_name'/'NAME': ':memory:'/g" backend/settings.py + - name: Make and run migrations + run: | + python manage.py makemigrations + python manage.py migrate - name: Test with pytest run: | pytest From 8955922ed34559ec96f960216c9cd32252ff16fa Mon Sep 17 00:00:00 2001 From: anthisse <128951499+anthisse@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:51:53 -0700 Subject: [PATCH 14/16] docs: Add Arch Linux setup instructions The Linux part of the guide isn't accurate to Arch Linux, since the AUR does some of the configuration for us. We also need to start the Postgres service ourselves. --- README.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b6dd2b4..96c08d5 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ When making a pull request to the frontend, you *must* follow these rules to ens - Pull requests must be made to the `dev` branch, *NOT* `main`. - Pull requests must include an image of the changes made to the frontend. -## Backend (Windows/Linux) +## Backend (Windows/ Generic Linux) ### Prerequisites This guide expects that you have [Python](https://www.python.org/downloads/) (At least 3.12.0) installed. @@ -239,3 +239,98 @@ If Python was installed via either Homebrew or the official Python installer, yo ``` If it all goes well, cool stuff, it's working! + +### Setup (Arch Linux) + +- This guide assumes an installation using `systemd`. If you're using something else like OpenRC or runit, tweak the `systemctl` commands accordingly. + +1. **Create the venv** + + ```sh + cd Studygatchi + python3 -m venv env + ``` + + ```sh + source ./env/bin/activate + ``` + + +2. **Run the following command to install Django and the Django Rest Framework:** + + ```sh + pip install django djangorestframework + ``` + +3. **Install PostgreSQL (version 17)** + + ```sh + pacman -Syu postgresql + ``` + + +4. **Activate the venv (if it's not already active) using the corresponding command and install psycopg2** + + ```sh + pip install psycopg2-binary + ``` + + We will use this to be able to connect Django with Postgres! + +5. **Initialize the database cluster** + + ```sh + sudo -u postgres initdb -D /var/lib/postgres/data + ``` + +6. **Start the PostgreSQL service** + + ```sh + sudo systemctl start postgresql + ``` + + - It's optional, but recommended that you have PostgreSQL run at startup: + + ```sh + sudo systemctl enable postgresql + ``` + +7. **Access the PostgreSQL shell, logged in as the superuser:** + + ```sh + psql -U postgres + ``` + +8. **Run the following SQL commands:** + + ```sql + CREATE USER WITH PASSWORD ''; + CREATE DATABASE studygatchi_db OWNER ; + GRANT ALL PRIVILEGES ON DATABASE studygatchi_db TO ; + \q + ``` + Replace `` with whatever username you want, and likewise for the password. + +9. **In the backend directory, create a file called `settings.py` with the contents of `settings_template.txt`.** + + ```sh + cd backend/ + cp settings_template.txt settings.py + ``` + +10. **In `settings.py`, go to where it says `DATABASES`, and insert your info from step 6 into the corresponding places.** + +11. **At the root of the project, run the following commands** ***with the venv active*** **to apply migrations:** + + ```sh + python3 manage.py makemigrations + python3 manage.py migrate + ``` + +12. **Test the connection by running this command:** + + ```sh + python3 manage.py runserver + ``` + + If it all goes well, cool stuff; it's working! From 102e0f680003f82cf7a1a85edd18557b2f0cc49f Mon Sep 17 00:00:00 2001 From: Anthony Thisse Date: Sat, 6 Jun 2026 23:26:09 -0700 Subject: [PATCH 15/16] chore: bump package-lock.json npm is complaining about vulnerabilities. A simple npm audit fix generated the new package-lock.json in this commit. --- frontend/package-lock.json | 423 +++++++++++++++++++++++-------------- 1 file changed, 268 insertions(+), 155 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4655864..425aeda 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -837,9 +837,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -848,9 +848,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -908,9 +908,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -929,9 +929,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1140,9 +1140,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz", - "integrity": "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", "cpu": [ "arm" ], @@ -1154,9 +1154,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.0.tgz", - "integrity": "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", "cpu": [ "arm64" ], @@ -1168,9 +1168,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz", - "integrity": "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", "cpu": [ "arm64" ], @@ -1182,9 +1182,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz", - "integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", "cpu": [ "x64" ], @@ -1196,9 +1196,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.0.tgz", - "integrity": "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", "cpu": [ "arm64" ], @@ -1210,9 +1210,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.0.tgz", - "integrity": "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", "cpu": [ "x64" ], @@ -1224,13 +1224,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.0.tgz", - "integrity": "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1238,13 +1241,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.0.tgz", - "integrity": "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1252,13 +1258,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz", - "integrity": "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1266,27 +1275,50 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz", - "integrity": "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.0.tgz", - "integrity": "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1294,13 +1326,33 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.0.tgz", - "integrity": "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1308,13 +1360,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.0.tgz", - "integrity": "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1322,13 +1377,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.0.tgz", - "integrity": "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1336,13 +1394,16 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.0.tgz", - "integrity": "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1350,13 +1411,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz", - "integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1364,23 +1428,40 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz", - "integrity": "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.0.tgz", - "integrity": "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", "cpu": [ "arm64" ], @@ -1392,9 +1473,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz", - "integrity": "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", "cpu": [ "arm64" ], @@ -1406,9 +1487,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.0.tgz", - "integrity": "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", "cpu": [ "ia32" ], @@ -1419,10 +1500,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz", - "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", "cpu": [ "x64" ], @@ -1490,9 +1585,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1837,9 +1932,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1930,9 +2025,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -2115,12 +2210,16 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cross-spawn": { @@ -2357,9 +2456,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -2391,9 +2490,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -2599,9 +2698,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -2793,10 +2892,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2947,13 +3056,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2970,9 +3079,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -3125,9 +3234,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -3138,9 +3247,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -3158,7 +3267,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3239,9 +3348,9 @@ } }, "node_modules/react-router": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz", - "integrity": "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz", + "integrity": "sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -3261,12 +3370,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.5.tgz", - "integrity": "sha512-mkEmq/K8tKN63Ae2M7Xgz3c9l9YNbY+NHH6NNeUmLA3kDkhKXRsNb/ZpxaEunvGo2/3YXdk5EJU3Hxp3ocaBPw==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.17.0.tgz", + "integrity": "sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==", "license": "MIT", "dependencies": { - "react-router": "7.9.5" + "react-router": "7.17.0" }, "engines": { "node": ">=20.0.0" @@ -3311,13 +3420,13 @@ } }, "node_modules/rollup": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", - "integrity": "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -3327,27 +3436,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.50.0", - "@rollup/rollup-android-arm64": "4.50.0", - "@rollup/rollup-darwin-arm64": "4.50.0", - "@rollup/rollup-darwin-x64": "4.50.0", - "@rollup/rollup-freebsd-arm64": "4.50.0", - "@rollup/rollup-freebsd-x64": "4.50.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", - "@rollup/rollup-linux-arm-musleabihf": "4.50.0", - "@rollup/rollup-linux-arm64-gnu": "4.50.0", - "@rollup/rollup-linux-arm64-musl": "4.50.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", - "@rollup/rollup-linux-ppc64-gnu": "4.50.0", - "@rollup/rollup-linux-riscv64-gnu": "4.50.0", - "@rollup/rollup-linux-riscv64-musl": "4.50.0", - "@rollup/rollup-linux-s390x-gnu": "4.50.0", - "@rollup/rollup-linux-x64-gnu": "4.50.0", - "@rollup/rollup-linux-x64-musl": "4.50.0", - "@rollup/rollup-openharmony-arm64": "4.50.0", - "@rollup/rollup-win32-arm64-msvc": "4.50.0", - "@rollup/rollup-win32-ia32-msvc": "4.50.0", - "@rollup/rollup-win32-x64-msvc": "4.50.0", + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", "fsevents": "~2.3.2" } }, @@ -3511,9 +3624,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -3645,9 +3758,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", "dependencies": { @@ -3758,9 +3871,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { From 4eb2dec4a57853bb15e6260d7f290c81a70d464c Mon Sep 17 00:00:00 2001 From: Anthony Thisse Date: Sun, 7 Jun 2026 10:57:31 -0700 Subject: [PATCH 16/16] feat: interface with Canvas calendar We can't use the Canvas API due to issues with IT. This hacks around that by using content scripts to reuse the session cookies from the end user already being logged into Canvas. --- frontend/package-lock.json | 16 +++-- frontend/package.json | 3 +- frontend/public/manifest.json | 37 ++++++----- frontend/src/App.tsx | 8 +-- frontend/src/components/CanvasFetcher.tsx | 76 +++++++++++++++++++++++ frontend/src/components/Home.tsx | 30 +++++---- frontend/src/components/SettingsMenu.tsx | 26 ++++---- frontend/src/content.ts | 15 +++++ frontend/src/lib/browser.ts | 1 + frontend/vite.config.ts | 21 ++++++- 10 files changed, 181 insertions(+), 52 deletions(-) create mode 100644 frontend/src/components/CanvasFetcher.tsx create mode 100644 frontend/src/content.ts create mode 100644 frontend/src/lib/browser.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 425aeda..f5d5e4c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,7 +18,8 @@ "tailwindcss": "^4.1.14" }, "devDependencies": { - "@types/chrome": "^0.1.4", + "@types/chrome": "^0.1.43", + "@types/firefox-webext-browser": "^143.0.0", "@types/node": "^24.9.2", "@types/react": "^19.1.12", "@types/react-dom": "^19.1.9", @@ -1574,9 +1575,9 @@ } }, "node_modules/@types/chrome": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.4.tgz", - "integrity": "sha512-vfISO7SPppN3OKVUqWujtZ4vux3nhDqKaHYEHgfQuPARHuWJ3jjyc1s13H0ckzEc86/neTkCl1TeW72UK6jYKA==", + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.43.tgz", + "integrity": "sha512-ukH/HhmR6ht+UTX3PLUWJxgJ/RQcK2Foj4lBzsF24SIWsXgqhGuXqjd8FFuwioPP7d/JUKLM4g8GZxw3F4HTcA==", "dev": true, "license": "MIT", "dependencies": { @@ -1608,6 +1609,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/firefox-webext-browser": { + "version": "143.0.0", + "resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-143.0.0.tgz", + "integrity": "sha512-865dYKMOP0CllFyHmgXV4IQgVL51OSQQCwSoihQ17EwugePKFSAZRc0EI+y7Ly4q7j5KyURlA7LgRpFieO4JOw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/har-format": { "version": "1.2.16", "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", diff --git a/frontend/package.json b/frontend/package.json index ea1997a..4487ade 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,7 +20,8 @@ "tailwindcss": "^4.1.14" }, "devDependencies": { - "@types/chrome": "^0.1.4", + "@types/chrome": "^0.1.43", + "@types/firefox-webext-browser": "^143.0.0", "@types/node": "^24.9.2", "@types/react": "^19.1.12", "@types/react-dom": "^19.1.9", diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json index c085dd2..002172f 100644 --- a/frontend/public/manifest.json +++ b/frontend/public/manifest.json @@ -1,16 +1,25 @@ { - "manifest_version": 3, - "name": "Studygatchi", - "version": "1.0.0", - "description": "Virtual pet & study assistant", - "action": { - "default_popup": "index.html" - }, - "background": { - "service_worker": "background.js" - }, - "permissions": ["sidePanel"], - "side_panel": { - "default_path": "index.html" + "manifest_version": 3, + "name": "Studygatchi", + "version": "1.0.0", + "description": "Virtual pet & study assistant", + "action": { + "default_popup": "index.html" + }, + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": ["https://*.instructure.com/*"], + "js": ["content.js"] } - } \ No newline at end of file + ], + "permissions": ["sidePanel", "storage", "tabs"], + "host_permissions": [ + "https://*.instructure.com/*" + ], + "side_panel": { + "default_path": "index.html" + } +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9adf127..c902b68 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,19 +1,18 @@ import "./App.css"; import { useState } from "react"; import SettingsMenu from "./components/SettingsMenu"; -import NavBar from "./components/NavBar"; // +import NavBar from "./components/NavBar"; import Home from "./components/Home"; import Timer from "./components/Timer"; import ToDoList from "./ToDoList"; import GooberMenu from "./components/GooberMenu"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import { ThemeProvider } from './components/ThemeProvider'; +import CanvasFetcher from './components/CanvasFetcher.tsx'; function App() { - // had to add because bootstrap defaults to light mode document.documentElement.setAttribute("data-bs-theme", "dark"); - // Current Players data const [currentXP, setXP] = useState(50); const [level, setLevel] = useState(9); const [money, setMoney] = useState(0); @@ -47,6 +46,7 @@ function App() { } /> } /> } /> + } /> } />
@@ -55,4 +55,4 @@ function App() { ); } -export default App; +export default App; \ No newline at end of file diff --git a/frontend/src/components/CanvasFetcher.tsx b/frontend/src/components/CanvasFetcher.tsx new file mode 100644 index 0000000..dfb532d --- /dev/null +++ b/frontend/src/components/CanvasFetcher.tsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import { browser } from '../lib/browser'; + +const CanvasFetcher: React.FC = () => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const fetchCanvasData = async () => { + setLoading(true); + setError(null); + + try { + const startDate = '2026-06-07'; + const endDate = '2026-06-13'; + + // Get the active Canvas tab + const [tab] = await browser.tabs.query({ active: true, currentWindow: true }); + + if (!tab?.id) { + throw new Error('No active tab found'); + } + + // Send message to content script + browser.tabs.sendMessage( + tab.id, + { action: 'fetchCanvasData', startDate, endDate }, + (response: any) => { + if (chrome.runtime.lastError) { + setError(chrome.runtime.lastError.message || 'Failed to communicate with Canvas'); + setLoading(false); + return; + } + + if (response?.success) { + setData(response.data); + } else { + setError(response?.error || 'Failed to fetch'); + } + setLoading(false); + } + ); + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + setLoading(false); + } + }; + + return ( +
+

Canvas Data Fetcher

+ + + {error &&

Error: {error}

} + + {data && ( +
+

Found {data.length} items:

+
+            {JSON.stringify(data, null, 2)}
+          
+
+ )} +
+ ); +}; + +export default CanvasFetcher; \ No newline at end of file diff --git a/frontend/src/components/Home.tsx b/frontend/src/components/Home.tsx index aa58ebf..73eecfc 100644 --- a/frontend/src/components/Home.tsx +++ b/frontend/src/components/Home.tsx @@ -1,38 +1,36 @@ -import { useState } from "react"; +import { useState as _useState } from "react"; import oscLogo from "../assets/osc-logo.png"; import githubLogo from "../assets/github-mark-white.svg"; +import CanvasFetcher from "./CanvasFetcher"; import "./Home.css"; import "bootstrap/dist/css/bootstrap.min.css"; - const Home = () => { - const [count, setCount] = useState(0); - - // had to add because bootstrap defaults to light mode - return ( -
+

Welcome to StudyGatchi!

+
-

Welcome to the Studygatchi Project!

+ {/* We don't really need the counter so I just overwrote it + to show the JSON fetching working */} +
+

Click on the OSC link to get to OSC!

Click on the Github Link to get to this project's GitHub repository!

- ) -} + ); +}; export default Home; \ No newline at end of file diff --git a/frontend/src/components/SettingsMenu.tsx b/frontend/src/components/SettingsMenu.tsx index cc692ec..097f168 100644 --- a/frontend/src/components/SettingsMenu.tsx +++ b/frontend/src/components/SettingsMenu.tsx @@ -1,14 +1,16 @@ -import { useState, useEffect, useContext } from "react"; +// FIXME useEffect is unused, preventing build +import { useState, useEffect as _useEffect, useContext } from "react"; import "./SettingsMenu.css"; import "bootstrap/dist/css/bootstrap.min.css"; -import {ThemeContext} from "./ThemeProvider.tsx"; +import { ThemeContext } from "./ThemeProvider.tsx"; export default function SettingsMenu() { const context = useContext(ThemeContext); if (!context) { throw new Error("SettingsMenu must be used within a ThemeProvider"); } - const {theme, setTheme, themes} = context; + // FIXME theme unused, preventing build + const { theme: _theme, setTheme, themes } = context; const [firstOption, setFirst] = useState(false); const [secondOption, setSecond] = useState(false); @@ -72,20 +74,20 @@ export default function SettingsMenu() { Themes
- - - - -
diff --git a/frontend/src/content.ts b/frontend/src/content.ts new file mode 100644 index 0000000..411e88d --- /dev/null +++ b/frontend/src/content.ts @@ -0,0 +1,15 @@ +// Manifest v3 requires that content scripts be self-contained, so we have to inline it +const browser = (typeof chrome !== 'undefined' ? chrome : (window as any).browser); + +browser.runtime.onMessage.addListener((request: any, _sender: any, sendResponse: any) => { + if (request.action === 'fetchCanvasData') { + const { startDate, endDate } = request; + + fetch(`/api/v1/planner/items?per_page=100&start_date=${startDate}&end_date=${endDate}`) + .then(r => r.json()) + .then(data => sendResponse({ success: true, data })) + .catch(error => sendResponse({ success: false, error: error.message })); + + return true; + } +}); \ No newline at end of file diff --git a/frontend/src/lib/browser.ts b/frontend/src/lib/browser.ts new file mode 100644 index 0000000..58fec33 --- /dev/null +++ b/frontend/src/lib/browser.ts @@ -0,0 +1 @@ +export const browser = (typeof chrome !== 'undefined' ? chrome : (window as any).browser); \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 7629dc7..9feaa6c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -15,17 +15,36 @@ export default defineConfig({ ], }), ], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + server: { + proxy: { + '/api': { + target: 'https://ufl.instructure.com', + changeOrigin: true, + secure: false, + } + } + }, build: { outDir: 'build', rollupOptions: { input: { main: './index.html', background: './src/background.ts', + content: './src/content.ts', }, output: { entryFileNames: (chunkInfo) => { - return chunkInfo.name === 'background' ? 'background.js' : 'assets/[name]-[hash].js'; + if (chunkInfo.name === 'background') return 'background.js'; + if (chunkInfo.name === 'content') return 'content.js'; + return 'assets/[name]-[hash].js'; }, + inlineDynamicImports: false, + manualChunks: undefined, }, }, },