From e2ed1a66bfe26c0c2c68ae8d653feb5f8a4ee5fd Mon Sep 17 00:00:00 2001 From: Sofie Date: Mon, 4 Aug 2025 12:33:26 +0200 Subject: [PATCH 001/371] installed --- frontend/src/index.css | 3 +++ package.json | 7 ++++++- postcss.config.js | 6 ++++++ tailwind.config.js | 12 ++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 postcss.config.js create mode 100644 tailwind.config.js diff --git a/frontend/src/index.css b/frontend/src/index.css index e69de29bb2..bd6213e1df 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/package.json b/package.json index 680d190772..77ffceca08 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,10 @@ "version": "1.0.0", "scripts": { "postinstall": "npm install --prefix backend" + }, + "devDependencies": { + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17" } -} \ No newline at end of file +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000..33ad091d26 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000000..1ec9c51c2d --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,12 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} + From ba38187e3ae0cfce72914f78f0e5724842ebb07d Mon Sep 17 00:00:00 2001 From: Sofie Date: Mon, 4 Aug 2025 14:52:55 +0200 Subject: [PATCH 002/371] added folders and pages --- frontend/package.json | 8 ++++++-- frontend/postcss.config.js | 6 ++++++ frontend/src/App.jsx | 5 ++++- frontend/src/pages/About.jsx | 0 frontend/src/pages/Admin.jsx | 0 frontend/src/pages/Home.jsx | 0 frontend/src/pages/Login.jsx | 0 frontend/src/pages/Signup.jsx | 0 frontend/src/pages/comp/Error.jsx | 0 frontend/src/pages/comp/Loader.jsx | 0 frontend/src/pages/comp/layout/Dashboard.jsx | 0 frontend/src/pages/comp/layout/Footer.jsx | 0 frontend/src/pages/comp/layout/Header.jsx | 0 frontend/src/pages/comp/layout/Navbar.jsx | 0 frontend/src/pages/comp/stores/useSubscriptionStore.jsx | 0 frontend/src/pages/comp/stores/useUserStore.jsx | 0 frontend/src/pages/comp/user/Form.jsx | 0 frontend/src/pages/comp/user/Subscription.jsx | 0 frontend/src/pages/comp/user/Userlogin.jsx | 0 frontend/src/pages/comp/user/Usersignup.jsx | 0 tailwind.config.js => frontend/tailwind.config.js | 3 +-- postcss.config.js | 6 ------ 22 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 frontend/postcss.config.js create mode 100644 frontend/src/pages/About.jsx create mode 100644 frontend/src/pages/Admin.jsx create mode 100644 frontend/src/pages/Home.jsx create mode 100644 frontend/src/pages/Login.jsx create mode 100644 frontend/src/pages/Signup.jsx create mode 100644 frontend/src/pages/comp/Error.jsx create mode 100644 frontend/src/pages/comp/Loader.jsx create mode 100644 frontend/src/pages/comp/layout/Dashboard.jsx create mode 100644 frontend/src/pages/comp/layout/Footer.jsx create mode 100644 frontend/src/pages/comp/layout/Header.jsx create mode 100644 frontend/src/pages/comp/layout/Navbar.jsx create mode 100644 frontend/src/pages/comp/stores/useSubscriptionStore.jsx create mode 100644 frontend/src/pages/comp/stores/useUserStore.jsx create mode 100644 frontend/src/pages/comp/user/Form.jsx create mode 100644 frontend/src/pages/comp/user/Subscription.jsx create mode 100644 frontend/src/pages/comp/user/Userlogin.jsx create mode 100644 frontend/src/pages/comp/user/Usersignup.jsx rename tailwind.config.js => frontend/tailwind.config.js (98%) delete mode 100644 postcss.config.js diff --git a/frontend/package.json b/frontend/package.json index 7b2747e949..ffbdd23eae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" @@ -14,13 +14,17 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.11", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.0.3", + "autoprefixer": "^10.4.21", "eslint": "^8.45.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.11", "vite": "^6.3.5" } -} +} \ No newline at end of file diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000000..af9d8dc352 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0a24275e6e..3c6bd9af6f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,7 +2,10 @@ export const App = () => { return ( <> -

Welcome to Final Project!

+

+ Subscribee +

+

funkar det nu?

); }; diff --git a/frontend/src/pages/About.jsx b/frontend/src/pages/About.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/Error.jsx b/frontend/src/pages/comp/Error.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/Loader.jsx b/frontend/src/pages/comp/Loader.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/layout/Dashboard.jsx b/frontend/src/pages/comp/layout/Dashboard.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/layout/Footer.jsx b/frontend/src/pages/comp/layout/Footer.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/layout/Header.jsx b/frontend/src/pages/comp/layout/Header.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/layout/Navbar.jsx b/frontend/src/pages/comp/layout/Navbar.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/stores/useSubscriptionStore.jsx b/frontend/src/pages/comp/stores/useSubscriptionStore.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/stores/useUserStore.jsx b/frontend/src/pages/comp/stores/useUserStore.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/user/Form.jsx b/frontend/src/pages/comp/user/Form.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/user/Subscription.jsx b/frontend/src/pages/comp/user/Subscription.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/user/Userlogin.jsx b/frontend/src/pages/comp/user/Userlogin.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/pages/comp/user/Usersignup.jsx b/frontend/src/pages/comp/user/Usersignup.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tailwind.config.js b/frontend/tailwind.config.js similarity index 98% rename from tailwind.config.js rename to frontend/tailwind.config.js index 1ec9c51c2d..dc1013c5af 100644 --- a/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -8,5 +8,4 @@ module.exports = { extend: {}, }, plugins: [], -} - +} \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 33ad091d26..0000000000 --- a/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} From ae7504ffe9f13f938f2895868e510fc756e92d65 Mon Sep 17 00:00:00 2001 From: Sofie Date: Mon, 4 Aug 2025 15:20:52 +0200 Subject: [PATCH 003/371] added folders backend --- backend/authMiddleware.js | 0 backend/models/subscription.js | 0 backend/models/user.js | 0 backend/routes/subscriptionRoutes.js | 0 backend/routes/userRoutes.js | 0 backend/server.js | 10 ++++++++-- frontend/src/App.jsx | 2 +- 7 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 backend/authMiddleware.js create mode 100644 backend/models/subscription.js create mode 100644 backend/models/user.js create mode 100644 backend/routes/subscriptionRoutes.js create mode 100644 backend/routes/userRoutes.js diff --git a/backend/authMiddleware.js b/backend/authMiddleware.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/models/subscription.js b/backend/models/subscription.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/models/user.js b/backend/models/user.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/routes/subscriptionRoutes.js b/backend/routes/subscriptionRoutes.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/server.js b/backend/server.js index 070c875189..6ea4c5108f 100644 --- a/backend/server.js +++ b/backend/server.js @@ -12,9 +12,15 @@ const app = express(); app.use(cors()); app.use(express.json()); +// Endpoints with listEndpoints app.get("/", (req, res) => { - res.send("Hello Technigo!"); -}); + const endpoints = listEndpoints(app) + res.json({ + message: "Welcome to Subscribee", + endpoints: endpoints + }) + +}) // Start the server app.listen(port, () => { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3c6bd9af6f..1430742143 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5,7 +5,7 @@ export const App = () => {

Subscribee

-

funkar det nu?

+ ); }; From 83bf9d9918895aef9e75ba3c81c0d6c59e60a9fd Mon Sep 17 00:00:00 2001 From: solen80a Date: Tue, 5 Aug 2025 13:19:45 +0200 Subject: [PATCH 004/371] Created a user model. --- backend/models/user.js | 28 ++++++++++++++++++++++++++++ backend/package.json | 6 ++++-- frontend/src/index.css | 2 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/backend/models/user.js b/backend/models/user.js index e69de29bb2..4e7233620a 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -0,0 +1,28 @@ +import crypto from "crypto" +import mongoose from "mongoose" + +const userSchema = new mongoose.Schema({ + email: { + type: String, + required: true, + unique: true, + minlenght: 5, + maxlenght: 100, + }, + password: { + type: String, + required: true, + minlenght: 3, + maxlenght: 100, + }, + accesToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex") + }, + createdAt: { + type: Date, + default: Date.now + } +}) + +export const user = mongoose.model("User", userSchema) diff --git a/backend/package.json b/backend/package.json index 08f29f2448..c865452454 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,9 +12,11 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^6.0.0", "cors": "^2.8.5", + "dotenv": "^17.2.1", "express": "^4.17.3", - "mongoose": "^8.4.0", + "mongoose": "^8.17.0", "nodemon": "^3.0.1" } -} \ No newline at end of file +} diff --git a/frontend/src/index.css b/frontend/src/index.css index bd6213e1df..b5c61c9567 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,3 +1,3 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; From 7a4d55c8bc30ecb2f361bd833f72d8583810c314 Mon Sep 17 00:00:00 2001 From: Sofie Date: Tue, 5 Aug 2025 13:31:56 +0200 Subject: [PATCH 005/371] added routes frontend --- frontend/package.json | 6 ++++-- frontend/src/App.jsx | 28 ++++++++++++++++++++-------- frontend/src/pages/About.jsx | 9 +++++++++ frontend/src/pages/Admin.jsx | 11 +++++++++++ frontend/src/pages/Home.jsx | 11 +++++++++++ frontend/src/pages/Login.jsx | 11 +++++++++++ frontend/src/pages/Signup.jsx | 11 +++++++++++ 7 files changed, 77 insertions(+), 10 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index ffbdd23eae..9283c4652a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,9 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^7.7.1", + "zustand": "^5.0.7" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.11", @@ -27,4 +29,4 @@ "tailwindcss": "^4.1.11", "vite": "^6.3.5" } -} \ No newline at end of file +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1430742143..c70fc6d28a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,11 +1,23 @@ -export const App = () => { +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; - return ( - <> -

- Subscribee -

+import { Home } from './pages/Home'; +import { About } from './pages/About'; +import { Admin } from './pages/Admin'; +import { Login } from './pages/Login'; +import { Signup } from './pages/Signup'; - +export const App = () => { + return ( + +
+ + } /> + } /> + } /> + } /> + } /> + +
+
); -}; +}; \ No newline at end of file diff --git a/frontend/src/pages/About.jsx b/frontend/src/pages/About.jsx index e69de29bb2..5680b20092 100644 --- a/frontend/src/pages/About.jsx +++ b/frontend/src/pages/About.jsx @@ -0,0 +1,9 @@ +export const About = () => { + return ( + <> +
+

About Page

+
+ + ); +} \ No newline at end of file diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index e69de29bb2..5418819116 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -0,0 +1,11 @@ + + +export const Admin = () => { + return ( + <> +
+

Admin Page

+
+ + ); +} \ No newline at end of file diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index e69de29bb2..9f88f15b40 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -0,0 +1,11 @@ + + +export const Home = () => { + return ( + <> +
+

Home Page

+
+ + ); +} \ No newline at end of file diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index e69de29bb2..dbafbc5bf9 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -0,0 +1,11 @@ + + +export const Login = () => { + return ( + <> +
+

Login Page

+
+ + ); +} \ No newline at end of file diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index e69de29bb2..7aee98661f 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -0,0 +1,11 @@ + + +export const Signup = () => { + return ( + <> +
+

Signup Page

+
+ + ); +} \ No newline at end of file From 8c9ae19f57f4c1fd2686a8f71fb53759e3b556d5 Mon Sep 17 00:00:00 2001 From: solen80a Date: Tue, 5 Aug 2025 13:50:20 +0200 Subject: [PATCH 006/371] Created user model --- backend/models/user.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/models/user.js b/backend/models/user.js index 4e7233620a..366bd8d7e5 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -2,6 +2,12 @@ import crypto from "crypto" import mongoose from "mongoose" const userSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + minlenght: 3, + maxlenght: 100 + }, email: { type: String, required: true, From 5f9cc008d7cb7a4d21a899a71a621c34d3566140 Mon Sep 17 00:00:00 2001 From: Sofie Date: Tue, 5 Aug 2025 13:52:54 +0200 Subject: [PATCH 007/371] backend --- backend/package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/package.json b/backend/package.json index 08f29f2448..6c350f46ff 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,9 +12,12 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^6.0.0", "cors": "^2.8.5", + "dotenv": "^17.2.1", "express": "^4.17.3", - "mongoose": "^8.4.0", + "express-list-endpoints": "^7.1.1", + "mongoose": "^8.17.0", "nodemon": "^3.0.1" } -} \ No newline at end of file +} From 9037057f7c7c4928fad0893a205d11846670edc1 Mon Sep 17 00:00:00 2001 From: Sofie Date: Tue, 5 Aug 2025 14:46:37 +0200 Subject: [PATCH 008/371] added .env --- backend/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/package.json b/backend/package.json index 874618223d..6e888fdc3d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,9 +15,9 @@ "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^17.2.1", - "express": "^4.17.3", + "express": "^4.21.2", "express-list-endpoints": "^7.1.1", "mongoose": "^8.17.0", "nodemon": "^3.0.1" } -} \ No newline at end of file +} From 0763a46a57723d316aeeba533b7bc6f0854eeeb5 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Tue, 5 Aug 2025 14:50:16 +0200 Subject: [PATCH 009/371] Created simple form Created simple form --- frontend/src/pages/comp/user/Form.jsx | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/frontend/src/pages/comp/user/Form.jsx b/frontend/src/pages/comp/user/Form.jsx index e69de29bb2..c5f878e447 100644 --- a/frontend/src/pages/comp/user/Form.jsx +++ b/frontend/src/pages/comp/user/Form.jsx @@ -0,0 +1,75 @@ +import React, { useState } from 'react'; + +const Form = () => { + const [formData, setFormData] = useState({ + name: '', + email: '', + password: '', + }); + + const handleChange = (e) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + console.log('Form submitted:', formData); + alert('Account created!'); + }; + + return ( +
+

Sign Up

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+ ); +}; + +export default Form; From 666cf8c7e5588ec1cd81ac68bdbd2a12ad98aebf Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Tue, 5 Aug 2025 14:50:27 +0200 Subject: [PATCH 010/371] Created simple login Created simple login --- frontend/src/pages/comp/user/Userlogin.jsx | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/frontend/src/pages/comp/user/Userlogin.jsx b/frontend/src/pages/comp/user/Userlogin.jsx index e69de29bb2..6749777b36 100644 --- a/frontend/src/pages/comp/user/Userlogin.jsx +++ b/frontend/src/pages/comp/user/Userlogin.jsx @@ -0,0 +1,45 @@ +export const Userlogin = () => { + const handleSubmit = (e) => { + e.preventDefault(); + console.log('Login form submitted'); + }; + + return ( + <> +
+

User Login

+

This is a test to see if the page loads

+ +
+
+ +
+ +
+ +
+ + +
+
+ + ); +}; From 0465c0da3d6fae8db1a270a24bfbd8342760f370 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Tue, 5 Aug 2025 14:50:36 +0200 Subject: [PATCH 011/371] Created simple login Created simple login --- frontend/src/pages/Login.jsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index dbafbc5bf9..e04fbbad64 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -1,11 +1,9 @@ - +import { Userlogin } from './comp/user/Userlogin'; export const Login = () => { return ( <> -
-

Login Page

-
+ ); -} \ No newline at end of file +}; From e73ede35fbed652eb7a719a8228d65d6f3d3924a Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Tue, 5 Aug 2025 14:50:48 +0200 Subject: [PATCH 012/371] Created simple sign up Created simple sign up --- frontend/src/pages/Signup.jsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index 7aee98661f..d182288915 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -1,11 +1,9 @@ - +import Form from './comp/user/Form'; export const Signup = () => { return ( <> -
-

Signup Page

-
+
); -} \ No newline at end of file +}; From e0fc9925b5952aaa8282feca8eb36dd43357d121 Mon Sep 17 00:00:00 2001 From: solen80a Date: Tue, 5 Aug 2025 14:50:52 +0200 Subject: [PATCH 013/371] Created and connected user routes. --- backend/routes/userRoutes.js | 104 +++++++++++++++++++++++++++++++++++ backend/server.js | 7 ++- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index e69de29bb2..ff96a5a9bf 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -0,0 +1,104 @@ +import bcrypt from "bcrypt" +import express from "express" + +import { User } from "../models/User"; + +const router = express.Router(); + +// To register a new user +router.post("/", async (req, res) => { + try { + const { email, password } = req.body + const salt = bcrypt.genSaltSync() + + const user = new User({ email, password: bcrypt.hashSync(password, salt) }) + await user.save() + + res.status(200).json({ + success: true, + message: "User created successfully", + response: { + id: user._id, + accessToken: user.accessToken + } + }) + } catch (error) { + res.status(400).json({ + success: false, + message: "Failed to create user", + response: error + }) + } +}) + +// To log in an existing user +router.post("/login", async (req, res) => { + try { + const { email, password } = req.body; + + // Find user by email in the database + const user = await User.findOne({ email: email.toLowerCase() }); + + if (user && bcrypt.compareSync(password, user.password)) { + res.json({ + success: true, + message: "Login successful", + id: user.id, + accessToken: user.accessToken, + }); + } else { + res.status(401).json({ + success: false, + message: "Invalid email or password", + }); + } + } catch (error) { + res.status(500).json({ + success: false, + message: "Something went wrong", + error, + }); + } +}); + +// KOLLA Skillnad??? +// app.get("/users", async (req, res) => { +// const { email } = req.params + +// try{ +// const user = await User.find(email) + +// if(!user) { +// return res.status(404).json({ +// success: false, +// response: null, +// message: "No matching user" +// }) +// } + +// res.status(200).json({ +// success: true, +// response: user +// }) + +// } catch (error) { +// res.status(500).json({ +// success: false, +// response: error, +// message: "Failed to fetch user" +// }) +// } +// }) + +// app.post("/users", postUser) + +// app.post("/login", async (req, res) => { +// const user = await User.findOne({eamil: req.body.email}) + +// if(user && bcrypt.compareSync(req.body.password, user.password)){ +// res.status(200).json({userId: user._id, accessToken: user.accessToken}) +// } else { +// res.status(401).json({ error: "Invalid email or password"}) +// } +// }) + diff --git a/backend/server.js b/backend/server.js index 6ea4c5108f..28f2a7c207 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,7 +1,9 @@ -import express from "express"; import cors from "cors"; +import express from "express"; import mongoose from "mongoose"; +import userRoutes from "./routes/userRoutes.js"; + const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; @@ -22,6 +24,9 @@ app.get("/", (req, res) => { }) +//Connection to routes +app.use("/users", userRoutes); + // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); From 02443b7744b69905cbaa1a7bdc52c67fc50df95c Mon Sep 17 00:00:00 2001 From: solen80a Date: Tue, 5 Aug 2025 14:58:45 +0200 Subject: [PATCH 014/371] Fix filename case issue for User.js --- backend/models/{subscription.js => Subscription.js} | 0 backend/models/{user.js => User.js} | 0 backend/routes/userRoutes.js | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename backend/models/{subscription.js => Subscription.js} (100%) rename backend/models/{user.js => User.js} (100%) diff --git a/backend/models/subscription.js b/backend/models/Subscription.js similarity index 100% rename from backend/models/subscription.js rename to backend/models/Subscription.js diff --git a/backend/models/user.js b/backend/models/User.js similarity index 100% rename from backend/models/user.js rename to backend/models/User.js diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index ff96a5a9bf..e4f46bfd2c 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -31,7 +31,7 @@ router.post("/", async (req, res) => { } }) -// To log in an existing user +// To login an existing user router.post("/login", async (req, res) => { try { const { email, password } = req.body; From 86081f7032ce05b8323feb3f62139fe737c1e101 Mon Sep 17 00:00:00 2001 From: Sofie Date: Tue, 5 Aug 2025 15:06:18 +0200 Subject: [PATCH 015/371] added navbar --- frontend/src/App.jsx | 4 +++- .../src/{pages/comp/Error.jsx => comp/Button.jsx} | 0 .../src/{pages/comp/Loader.jsx => comp/Cta.jsx} | 0 .../comp/layout/Dashboard.jsx => comp/Error.jsx} | 0 .../comp/layout/Footer.jsx => comp/Loader.jsx} | 0 .../Header.jsx => comp/layout/Dashboard.jsx} | 0 .../layout/Navbar.jsx => comp/layout/Footer.jsx} | 0 .../layout/Header.jsx} | 0 frontend/src/comp/layout/Navbar.jsx | 15 +++++++++++++++ .../stores/useSubscriptionStore.jsx} | 0 .../stores/useUserStore.jsx} | 0 .../user/Usersignup.jsx => comp/user/Form.jsx} | 0 frontend/src/comp/user/Subscription.jsx | 0 frontend/src/comp/user/Userlogin.jsx | 0 frontend/src/comp/user/Usersignup.jsx | 0 15 files changed, 18 insertions(+), 1 deletion(-) rename frontend/src/{pages/comp/Error.jsx => comp/Button.jsx} (100%) rename frontend/src/{pages/comp/Loader.jsx => comp/Cta.jsx} (100%) rename frontend/src/{pages/comp/layout/Dashboard.jsx => comp/Error.jsx} (100%) rename frontend/src/{pages/comp/layout/Footer.jsx => comp/Loader.jsx} (100%) rename frontend/src/{pages/comp/layout/Header.jsx => comp/layout/Dashboard.jsx} (100%) rename frontend/src/{pages/comp/layout/Navbar.jsx => comp/layout/Footer.jsx} (100%) rename frontend/src/{pages/comp/stores/useSubscriptionStore.jsx => comp/layout/Header.jsx} (100%) create mode 100644 frontend/src/comp/layout/Navbar.jsx rename frontend/src/{pages/comp/stores/useUserStore.jsx => comp/stores/useSubscriptionStore.jsx} (100%) rename frontend/src/{pages/comp/user/Subscription.jsx => comp/stores/useUserStore.jsx} (100%) rename frontend/src/{pages/comp/user/Usersignup.jsx => comp/user/Form.jsx} (100%) create mode 100644 frontend/src/comp/user/Subscription.jsx create mode 100644 frontend/src/comp/user/Userlogin.jsx create mode 100644 frontend/src/comp/user/Usersignup.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c70fc6d28a..892a3f6020 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5,11 +5,13 @@ import { About } from './pages/About'; import { Admin } from './pages/Admin'; import { Login } from './pages/Login'; import { Signup } from './pages/Signup'; +import { Navbar } from './comp/layout/Navbar'; export const App = () => { return ( -
+ +
} /> } /> diff --git a/frontend/src/pages/comp/Error.jsx b/frontend/src/comp/Button.jsx similarity index 100% rename from frontend/src/pages/comp/Error.jsx rename to frontend/src/comp/Button.jsx diff --git a/frontend/src/pages/comp/Loader.jsx b/frontend/src/comp/Cta.jsx similarity index 100% rename from frontend/src/pages/comp/Loader.jsx rename to frontend/src/comp/Cta.jsx diff --git a/frontend/src/pages/comp/layout/Dashboard.jsx b/frontend/src/comp/Error.jsx similarity index 100% rename from frontend/src/pages/comp/layout/Dashboard.jsx rename to frontend/src/comp/Error.jsx diff --git a/frontend/src/pages/comp/layout/Footer.jsx b/frontend/src/comp/Loader.jsx similarity index 100% rename from frontend/src/pages/comp/layout/Footer.jsx rename to frontend/src/comp/Loader.jsx diff --git a/frontend/src/pages/comp/layout/Header.jsx b/frontend/src/comp/layout/Dashboard.jsx similarity index 100% rename from frontend/src/pages/comp/layout/Header.jsx rename to frontend/src/comp/layout/Dashboard.jsx diff --git a/frontend/src/pages/comp/layout/Navbar.jsx b/frontend/src/comp/layout/Footer.jsx similarity index 100% rename from frontend/src/pages/comp/layout/Navbar.jsx rename to frontend/src/comp/layout/Footer.jsx diff --git a/frontend/src/pages/comp/stores/useSubscriptionStore.jsx b/frontend/src/comp/layout/Header.jsx similarity index 100% rename from frontend/src/pages/comp/stores/useSubscriptionStore.jsx rename to frontend/src/comp/layout/Header.jsx diff --git a/frontend/src/comp/layout/Navbar.jsx b/frontend/src/comp/layout/Navbar.jsx new file mode 100644 index 0000000000..a5c9f72782 --- /dev/null +++ b/frontend/src/comp/layout/Navbar.jsx @@ -0,0 +1,15 @@ +import { Link, useLocation } from 'react-router-dom'; + +export const Navbar = () => { + const location = useLocation(); + const isHome = location.pathname === '/'; + + return ( + + ); +}; \ No newline at end of file diff --git a/frontend/src/pages/comp/stores/useUserStore.jsx b/frontend/src/comp/stores/useSubscriptionStore.jsx similarity index 100% rename from frontend/src/pages/comp/stores/useUserStore.jsx rename to frontend/src/comp/stores/useSubscriptionStore.jsx diff --git a/frontend/src/pages/comp/user/Subscription.jsx b/frontend/src/comp/stores/useUserStore.jsx similarity index 100% rename from frontend/src/pages/comp/user/Subscription.jsx rename to frontend/src/comp/stores/useUserStore.jsx diff --git a/frontend/src/pages/comp/user/Usersignup.jsx b/frontend/src/comp/user/Form.jsx similarity index 100% rename from frontend/src/pages/comp/user/Usersignup.jsx rename to frontend/src/comp/user/Form.jsx diff --git a/frontend/src/comp/user/Subscription.jsx b/frontend/src/comp/user/Subscription.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/comp/user/Userlogin.jsx b/frontend/src/comp/user/Userlogin.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/comp/user/Usersignup.jsx b/frontend/src/comp/user/Usersignup.jsx new file mode 100644 index 0000000000..e69de29bb2 From cc988d1da0c6106c99613cf5d96d0d1074454e1e Mon Sep 17 00:00:00 2001 From: solen80a Date: Wed, 6 Aug 2025 09:58:48 +0200 Subject: [PATCH 016/371] Created authMiddleware. Installed Express-list-endpoints. Removed createdAt from user model, not needed. Added GET /users. Fixed a typo in export User. --- backend/authMiddleware.js | 23 ++++++++++++++++++++++ backend/models/User.js | 10 +++------- backend/package.json | 1 + backend/routes/userRoutes.js | 37 +++++++++++++++++++++++++++++++++--- backend/server.js | 6 +++--- 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/backend/authMiddleware.js b/backend/authMiddleware.js index e69de29bb2..a4655f54f9 100644 --- a/backend/authMiddleware.js +++ b/backend/authMiddleware.js @@ -0,0 +1,23 @@ +import { User } from "./models/User" + +export const authenticateUser = async (req, res, next) => { + try { + const user = await User.findOne({ + accessToken: req.header("Authorization"), + }) + if (user) { + req.user = user + next(); + } else { + res.status(401).json ({ + message: "Authentication missing or invalid", + loggedOut: true, + }) + } + } catch (error) { + res.status(500).json({ + message: "Internal server error", error: err.message + }) + } +} + diff --git a/backend/models/User.js b/backend/models/User.js index 366bd8d7e5..40a41b9049 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -18,17 +18,13 @@ const userSchema = new mongoose.Schema({ password: { type: String, required: true, - minlenght: 3, - maxlenght: 100, + //minlenght: 3, + //maxlenght: 100, }, accesToken: { type: String, default: () => crypto.randomBytes(128).toString("hex") - }, - createdAt: { - type: Date, - default: Date.now } }) -export const user = mongoose.model("User", userSchema) +export const User = mongoose.model("User", userSchema) diff --git a/backend/package.json b/backend/package.json index c865452454..6c350f46ff 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,6 +16,7 @@ "cors": "^2.8.5", "dotenv": "^17.2.1", "express": "^4.17.3", + "express-list-endpoints": "^7.1.1", "mongoose": "^8.17.0", "nodemon": "^3.0.1" } diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index e4f46bfd2c..a814b99fd1 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -5,13 +5,41 @@ import { User } from "../models/User"; const router = express.Router(); +router.get("/", async (req, res) => { + const { email } = req.params + + try{ + const user = await User.find(email) + + if(!user) { + return res.status(404).json({ + success: false, + response: null, + message: "No matching user" + }) + } + + res.status(200).json({ + success: true, + response: user + }) + + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Failed to fetch user" + }) + } +}) + // To register a new user router.post("/", async (req, res) => { try { - const { email, password } = req.body + const { name, email, password } = req.body const salt = bcrypt.genSaltSync() - const user = new User({ email, password: bcrypt.hashSync(password, salt) }) + const user = new User({ name, email, password: bcrypt.hashSync(password, salt) }) await user.save() res.status(200).json({ @@ -26,7 +54,8 @@ router.post("/", async (req, res) => { res.status(400).json({ success: false, message: "Failed to create user", - response: error + error: error.message + //response: error }) } }) @@ -61,6 +90,8 @@ router.post("/login", async (req, res) => { } }); +export default router; + // KOLLA Skillnad??? // app.get("/users", async (req, res) => { // const { email } = req.params diff --git a/backend/server.js b/backend/server.js index 28f2a7c207..631d289af9 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,8 +1,9 @@ import cors from "cors"; import express from "express"; +import expressListEndpoints from "express-list-endpoints"; import mongoose from "mongoose"; -import userRoutes from "./routes/userRoutes.js"; +import userRoutes from "./routes/userRoutes"; const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; mongoose.connect(mongoUrl); @@ -16,12 +17,11 @@ app.use(express.json()); // Endpoints with listEndpoints app.get("/", (req, res) => { - const endpoints = listEndpoints(app) + const endpoints = expressListEndpoints(app) res.json({ message: "Welcome to Subscribee", endpoints: endpoints }) - }) //Connection to routes From d8269358493b7a90f304dc1e445eb9a532664d0d Mon Sep 17 00:00:00 2001 From: solen80a Date: Wed, 6 Aug 2025 10:01:20 +0200 Subject: [PATCH 017/371] Made comments and added back some features. --- backend/models/User.js | 4 ++-- backend/routes/userRoutes.js | 46 ++++-------------------------------- 2 files changed, 6 insertions(+), 44 deletions(-) diff --git a/backend/models/User.js b/backend/models/User.js index 40a41b9049..8f101a69a3 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -18,8 +18,8 @@ const userSchema = new mongoose.Schema({ password: { type: String, required: true, - //minlenght: 3, - //maxlenght: 100, + minlenght: 3, + maxlenght: 100, }, accesToken: { type: String, diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index a814b99fd1..9a000ba6f7 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -5,6 +5,7 @@ import { User } from "../models/User"; const router = express.Router(); +// To get all users router.get("/", async (req, res) => { const { email } = req.params @@ -54,8 +55,8 @@ router.post("/", async (req, res) => { res.status(400).json({ success: false, message: "Failed to create user", - error: error.message - //response: error + error: error.message, + response: error }) } }) @@ -92,44 +93,5 @@ router.post("/login", async (req, res) => { export default router; -// KOLLA Skillnad??? -// app.get("/users", async (req, res) => { -// const { email } = req.params - -// try{ -// const user = await User.find(email) - -// if(!user) { -// return res.status(404).json({ -// success: false, -// response: null, -// message: "No matching user" -// }) -// } - -// res.status(200).json({ -// success: true, -// response: user -// }) - -// } catch (error) { -// res.status(500).json({ -// success: false, -// response: error, -// message: "Failed to fetch user" -// }) -// } -// }) - -// app.post("/users", postUser) - -// app.post("/login", async (req, res) => { -// const user = await User.findOne({eamil: req.body.email}) - -// if(user && bcrypt.compareSync(req.body.password, user.password)){ -// res.status(200).json({userId: user._id, accessToken: user.accessToken}) -// } else { -// res.status(401).json({ error: "Invalid email or password"}) -// } -// }) + From 480e28938ca826ba35d06b92fec43cf18ec93a3e Mon Sep 17 00:00:00 2001 From: solen80a Date: Wed, 6 Aug 2025 10:23:45 +0200 Subject: [PATCH 018/371] rename --- backend/models/{Subscription.js => subscription-temp.js} | 0 backend/models/{User.js => user-temp.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename backend/models/{Subscription.js => subscription-temp.js} (100%) rename backend/models/{User.js => user-temp.js} (100%) diff --git a/backend/models/Subscription.js b/backend/models/subscription-temp.js similarity index 100% rename from backend/models/Subscription.js rename to backend/models/subscription-temp.js diff --git a/backend/models/User.js b/backend/models/user-temp.js similarity index 100% rename from backend/models/User.js rename to backend/models/user-temp.js From 5128589ef5b3ea6dbee2e2f4522ea2783d6cd256 Mon Sep 17 00:00:00 2001 From: solen80a Date: Wed, 6 Aug 2025 10:30:32 +0200 Subject: [PATCH 019/371] rename --- backend/models/{subscription-temp.js => Subscription.js} | 0 backend/models/{user-temp.js => User.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename backend/models/{subscription-temp.js => Subscription.js} (100%) rename backend/models/{user-temp.js => User.js} (100%) diff --git a/backend/models/subscription-temp.js b/backend/models/Subscription.js similarity index 100% rename from backend/models/subscription-temp.js rename to backend/models/Subscription.js diff --git a/backend/models/user-temp.js b/backend/models/User.js similarity index 100% rename from backend/models/user-temp.js rename to backend/models/User.js From 71afb97dc042cb96836b9f162e34baba13e1db12 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Wed, 6 Aug 2025 10:32:38 +0200 Subject: [PATCH 020/371] Subscriptions schema Subscriptions schema --- backend/models/Subscription.js | 25 +++++++++++++++++ backend/models/subscription-temp.js | 0 backend/models/subscription.js | 25 +++++++++++++++++ backend/models/user-temp.js | 42 +++++++++++++++++++++++++++++ backend/server.js | 26 ++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 backend/models/subscription-temp.js create mode 100644 backend/models/subscription.js create mode 100644 backend/models/user-temp.js diff --git a/backend/models/Subscription.js b/backend/models/Subscription.js index e69de29bb2..7bd36bc809 100644 --- a/backend/models/Subscription.js +++ b/backend/models/Subscription.js @@ -0,0 +1,25 @@ +import mongoose from 'mongoose'; + +const subscriptionSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + minlength: 3, + maxlength: 100, + }, + cost: { + type: Number, + required: true, + min: 0, + }, + expirationDate: { + type: Date, + required: true, + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +export const Subscription = mongoose.model('Subscription', subscriptionSchema); diff --git a/backend/models/subscription-temp.js b/backend/models/subscription-temp.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/models/subscription.js b/backend/models/subscription.js new file mode 100644 index 0000000000..7bd36bc809 --- /dev/null +++ b/backend/models/subscription.js @@ -0,0 +1,25 @@ +import mongoose from 'mongoose'; + +const subscriptionSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + minlength: 3, + maxlength: 100, + }, + cost: { + type: Number, + required: true, + min: 0, + }, + expirationDate: { + type: Date, + required: true, + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +export const Subscription = mongoose.model('Subscription', subscriptionSchema); diff --git a/backend/models/user-temp.js b/backend/models/user-temp.js new file mode 100644 index 0000000000..3020eda3a0 --- /dev/null +++ b/backend/models/user-temp.js @@ -0,0 +1,42 @@ +import crypto from 'crypto'; +import mongoose from 'mongoose'; + +const userSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + minlength: 3, + maxlength: 100, + }, + email: { + type: String, + required: true, + unique: true, + minlength: 5, + maxlength: 100, + }, + password: { + type: String, + required: true, + minlength: 3, + maxlength: 100, + }, + accessToken: { + type: String, +<<<<<<< HEAD:backend/models/user.js + default: () => crypto.randomBytes(128).toString('hex'), + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +export const User = mongoose.model('User', userSchema); +======= + default: () => crypto.randomBytes(128).toString("hex") + } +}) + +export const User = mongoose.model("User", userSchema) +>>>>>>> 094fc6a22273daeb63887a83968a365d9e0769b6:backend/models/user-temp.js diff --git a/backend/server.js b/backend/server.js index 631d289af9..a512db8670 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,3 +1,15 @@ +<<<<<<< Updated upstream +======= +<<<<<<< HEAD +import express from 'express'; +import cors from 'cors'; +import mongoose from 'mongoose'; + +const mongoUrl = + 'mongodb+srv://sofieannamatilda:rQ58C4zpa5QANMx9@cluster0.mongodb.net/?retryWrites=true&w=majority'; +const port = 5000; +======= +>>>>>>> Stashed changes import cors from "cors"; import express from "express"; import expressListEndpoints from "express-list-endpoints"; @@ -6,6 +18,7 @@ import mongoose from "mongoose"; import userRoutes from "./routes/userRoutes"; const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; +>>>>>>> 094fc6a22273daeb63887a83968a365d9e0769b6 mongoose.connect(mongoUrl); mongoose.Promise = Promise; @@ -16,6 +29,15 @@ app.use(cors()); app.use(express.json()); // Endpoints with listEndpoints +<<<<<<< HEAD +app.get('/', (req, res) => { + const endpoints = listEndpoints(app); + res.json({ + message: 'Welcome to Subscribee', + endpoints: endpoints, + }); +}); +======= app.get("/", (req, res) => { const endpoints = expressListEndpoints(app) res.json({ @@ -23,6 +45,10 @@ app.get("/", (req, res) => { endpoints: endpoints }) }) +>>>>>>> 094fc6a22273daeb63887a83968a365d9e0769b6 + +//Connection to routes +app.use("/users", userRoutes); //Connection to routes app.use("/users", userRoutes); From 3838b502b6a2bce5dee7f7aeedf4ae42a413abbc Mon Sep 17 00:00:00 2001 From: solen80a Date: Wed, 6 Aug 2025 12:15:58 +0200 Subject: [PATCH 021/371] Typo --- backend/models/Subscription.js | 25 ----------------- backend/models/User.js | 12 ++++----- backend/models/subscription-temp.js | 0 backend/models/user-temp.js | 42 ----------------------------- 4 files changed, 6 insertions(+), 73 deletions(-) delete mode 100644 backend/models/Subscription.js delete mode 100644 backend/models/subscription-temp.js delete mode 100644 backend/models/user-temp.js diff --git a/backend/models/Subscription.js b/backend/models/Subscription.js deleted file mode 100644 index 7bd36bc809..0000000000 --- a/backend/models/Subscription.js +++ /dev/null @@ -1,25 +0,0 @@ -import mongoose from 'mongoose'; - -const subscriptionSchema = new mongoose.Schema({ - name: { - type: String, - required: true, - minlength: 3, - maxlength: 100, - }, - cost: { - type: Number, - required: true, - min: 0, - }, - expirationDate: { - type: Date, - required: true, - }, - createdAt: { - type: Date, - default: Date.now, - }, -}); - -export const Subscription = mongoose.model('Subscription', subscriptionSchema); diff --git a/backend/models/User.js b/backend/models/User.js index 8f101a69a3..4b26872f23 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -5,21 +5,21 @@ const userSchema = new mongoose.Schema({ name: { type: String, required: true, - minlenght: 3, - maxlenght: 100 + minlength: 3, + maxlength: 100 }, email: { type: String, required: true, unique: true, - minlenght: 5, - maxlenght: 100, + minlength: 5, + maxlength: 100, }, password: { type: String, required: true, - minlenght: 3, - maxlenght: 100, + minlength: 3, + maxlength: 100, }, accesToken: { type: String, diff --git a/backend/models/subscription-temp.js b/backend/models/subscription-temp.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/backend/models/user-temp.js b/backend/models/user-temp.js deleted file mode 100644 index 3020eda3a0..0000000000 --- a/backend/models/user-temp.js +++ /dev/null @@ -1,42 +0,0 @@ -import crypto from 'crypto'; -import mongoose from 'mongoose'; - -const userSchema = new mongoose.Schema({ - name: { - type: String, - required: true, - minlength: 3, - maxlength: 100, - }, - email: { - type: String, - required: true, - unique: true, - minlength: 5, - maxlength: 100, - }, - password: { - type: String, - required: true, - minlength: 3, - maxlength: 100, - }, - accessToken: { - type: String, -<<<<<<< HEAD:backend/models/user.js - default: () => crypto.randomBytes(128).toString('hex'), - }, - createdAt: { - type: Date, - default: Date.now, - }, -}); - -export const User = mongoose.model('User', userSchema); -======= - default: () => crypto.randomBytes(128).toString("hex") - } -}) - -export const User = mongoose.model("User", userSchema) ->>>>>>> 094fc6a22273daeb63887a83968a365d9e0769b6:backend/models/user-temp.js From 69e2906c7b01d112f955de6655d2e74237a162ab Mon Sep 17 00:00:00 2001 From: solen80a Date: Wed, 6 Aug 2025 12:28:19 +0200 Subject: [PATCH 022/371] test --- backend/models/subscription.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/models/subscription.js b/backend/models/subscription.js index 7bd36bc809..00e8d8ba9c 100644 --- a/backend/models/subscription.js +++ b/backend/models/subscription.js @@ -22,4 +22,6 @@ const subscriptionSchema = new mongoose.Schema({ }, }); +//test + export const Subscription = mongoose.model('Subscription', subscriptionSchema); From 567b9a6981eb418f3c5b1f32e2b6ebf76b632de8 Mon Sep 17 00:00:00 2001 From: solen80a Date: Wed, 6 Aug 2025 12:46:58 +0200 Subject: [PATCH 023/371] merge fixes --- backend/server.js | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/backend/server.js b/backend/server.js index a512db8670..9c021cbc07 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,15 +1,3 @@ -<<<<<<< Updated upstream -======= -<<<<<<< HEAD -import express from 'express'; -import cors from 'cors'; -import mongoose from 'mongoose'; - -const mongoUrl = - 'mongodb+srv://sofieannamatilda:rQ58C4zpa5QANMx9@cluster0.mongodb.net/?retryWrites=true&w=majority'; -const port = 5000; -======= ->>>>>>> Stashed changes import cors from "cors"; import express from "express"; import expressListEndpoints from "express-list-endpoints"; @@ -18,7 +6,7 @@ import mongoose from "mongoose"; import userRoutes from "./routes/userRoutes"; const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; ->>>>>>> 094fc6a22273daeb63887a83968a365d9e0769b6 + mongoose.connect(mongoUrl); mongoose.Promise = Promise; @@ -29,15 +17,6 @@ app.use(cors()); app.use(express.json()); // Endpoints with listEndpoints -<<<<<<< HEAD -app.get('/', (req, res) => { - const endpoints = listEndpoints(app); - res.json({ - message: 'Welcome to Subscribee', - endpoints: endpoints, - }); -}); -======= app.get("/", (req, res) => { const endpoints = expressListEndpoints(app) res.json({ @@ -45,7 +24,6 @@ app.get("/", (req, res) => { endpoints: endpoints }) }) ->>>>>>> 094fc6a22273daeb63887a83968a365d9e0769b6 //Connection to routes app.use("/users", userRoutes); From cfb95ce5810a189a6a90beb68e9f88931e00252a Mon Sep 17 00:00:00 2001 From: Sofie Date: Wed, 6 Aug 2025 13:36:39 +0200 Subject: [PATCH 024/371] removed duplicate in server.js --- backend/models/subscription.js | 2 +- backend/server.js | 3 --- package.json | 3 +++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/models/subscription.js b/backend/models/subscription.js index 00e8d8ba9c..95b55d42f4 100644 --- a/backend/models/subscription.js +++ b/backend/models/subscription.js @@ -24,4 +24,4 @@ const subscriptionSchema = new mongoose.Schema({ //test -export const Subscription = mongoose.model('Subscription', subscriptionSchema); +export const Subscription = mongoose.model('Subscription', subscriptionSchema); \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 9c021cbc07..3a98e054da 100644 --- a/backend/server.js +++ b/backend/server.js @@ -28,9 +28,6 @@ app.get("/", (req, res) => { //Connection to routes app.use("/users", userRoutes); -//Connection to routes -app.use("/users", userRoutes); - // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); diff --git a/package.json b/package.json index 77ffceca08..b4d3e93ef1 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,8 @@ "autoprefixer": "^10.4.21", "postcss": "^8.5.6", "tailwindcss": "^3.4.17" + }, + "dependencies": { + "express-list-routes": "^1.3.1" } } From d31153e5a65c47d6bb5c7cded9c0075d92839b38 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Wed, 6 Aug 2025 13:59:57 +0200 Subject: [PATCH 025/371] Update server.js --- backend/server.js | 84 ++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 33 deletions(-) diff --git a/backend/server.js b/backend/server.js index a512db8670..a2ea31480c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,24 +1,12 @@ -<<<<<<< Updated upstream -======= -<<<<<<< HEAD -import express from 'express'; import cors from 'cors'; +import express from 'express'; +import expressListEndpoints from 'express-list-endpoints'; import mongoose from 'mongoose'; -const mongoUrl = - 'mongodb+srv://sofieannamatilda:rQ58C4zpa5QANMx9@cluster0.mongodb.net/?retryWrites=true&w=majority'; -const port = 5000; -======= ->>>>>>> Stashed changes -import cors from "cors"; -import express from "express"; -import expressListEndpoints from "express-list-endpoints"; -import mongoose from "mongoose"; - -import userRoutes from "./routes/userRoutes"; +import userRoutes from './routes/userRoutes'; +import subscriptionRoutes from './routes/subscriptionRoutes'; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/final-project"; ->>>>>>> 094fc6a22273daeb63887a83968a365d9e0769b6 +const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost/final-project'; mongoose.connect(mongoUrl); mongoose.Promise = Promise; @@ -28,30 +16,60 @@ const app = express(); app.use(cors()); app.use(express.json()); -// Endpoints with listEndpoints -<<<<<<< HEAD +// Root endpoint with API documentation app.get('/', (req, res) => { - const endpoints = listEndpoints(app); + const endpoints = expressListEndpoints(app); res.json({ - message: 'Welcome to Subscribee', + message: 'Welcome to Subscribee API', endpoints: endpoints, }); }); -======= -app.get("/", (req, res) => { - const endpoints = expressListEndpoints(app) + +// Page endpoints +app.get('/home', (req, res) => { res.json({ - message: "Welcome to Subscribee", - endpoints: endpoints - }) -}) ->>>>>>> 094fc6a22273daeb63887a83968a365d9e0769b6 + page: 'home', + message: 'Welcome to Subscribee - Your subscription management platform', + features: ['Manage subscriptions', 'Track expenses', 'Get insights'], + }); +}); -//Connection to routes -app.use("/users", userRoutes); +app.get('/about', (req, res) => { + res.json({ + page: 'about', + message: 'About Subscribee', + description: + 'Subscribee helps you manage and track all your subscriptions in one place', + }); +}); + +app.get('/login', (req, res) => { + res.json({ + page: 'login', + message: 'Login to your Subscribee account', + endpoint: '/users/login', + }); +}); + +app.get('/signup', (req, res) => { + res.json({ + page: 'signup', + message: 'Create a new Subscribee account', + endpoint: '/users/register', + }); +}); + +app.get('/admin', (req, res) => { + res.json({ + page: 'admin', + message: 'Admin dashboard', + description: 'Administrative functions and user management', + }); +}); -//Connection to routes -app.use("/users", userRoutes); +// Route connections +app.use('/users', userRoutes); +app.use('/subscriptions', subscriptionRoutes); // Start the server app.listen(port, () => { From c3c16bc955c2362a0e7e6dbfc60414e2d5b4be6e Mon Sep 17 00:00:00 2001 From: Sofie Date: Wed, 6 Aug 2025 14:09:26 +0200 Subject: [PATCH 026/371] added _redirects file --- frontend/_redirects | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/_redirects diff --git a/frontend/_redirects b/frontend/_redirects new file mode 100644 index 0000000000..50a463356b --- /dev/null +++ b/frontend/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file From 25a48afe6ce4e6fb4f2af16ac39d114d1209ed3f Mon Sep 17 00:00:00 2001 From: Sofie Date: Wed, 6 Aug 2025 14:19:43 +0200 Subject: [PATCH 027/371] deploying to netlify --- frontend/{ => dist}/_redirects | 0 frontend/dist/assets/index-PV6aK9pL.css | 1 + frontend/dist/assets/index-rF6rWOwD.js | 51 +++++++++++++++++++++++++ frontend/dist/index.html | 24 ++++++++++++ frontend/dist/vite.svg | 1 + frontend/index.html | 35 +++++++++++------ frontend/public/_redirects | 1 + frontend/vite.config.js | 4 +- 8 files changed, 104 insertions(+), 13 deletions(-) rename frontend/{ => dist}/_redirects (100%) create mode 100644 frontend/dist/assets/index-PV6aK9pL.css create mode 100644 frontend/dist/assets/index-rF6rWOwD.js create mode 100644 frontend/dist/index.html create mode 100644 frontend/dist/vite.svg create mode 100644 frontend/public/_redirects diff --git a/frontend/_redirects b/frontend/dist/_redirects similarity index 100% rename from frontend/_redirects rename to frontend/dist/_redirects diff --git a/frontend/dist/assets/index-PV6aK9pL.css b/frontend/dist/assets/index-PV6aK9pL.css new file mode 100644 index 0000000000..dd38bb63ff --- /dev/null +++ b/frontend/dist/assets/index-PV6aK9pL.css @@ -0,0 +1 @@ +/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-duration:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.static{position:static}.mx-auto{margin-inline:auto}.block{display:block}.hidden{display:none}.w-full{width:100%}.resize{resize:both}.border{border-style:var(--tw-border-style);border-width:1px}.text-center{text-align:center}.italic{font-style:italic}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition\!{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events!important;transition-timing-function:var(--tw-ease,ease)!important;transition-duration:var(--tw-duration,0s)!important}.duration-200{--tw-duration:.2s;transition-duration:.2s}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000} diff --git a/frontend/dist/assets/index-rF6rWOwD.js b/frontend/dist/assets/index-rF6rWOwD.js new file mode 100644 index 0000000000..1591760a2f --- /dev/null +++ b/frontend/dist/assets/index-rF6rWOwD.js @@ -0,0 +1,51 @@ +(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const f of document.querySelectorAll('link[rel="modulepreload"]'))p(f);new MutationObserver(f=>{for(const m of f)if(m.type==="childList")for(const x of m.addedNodes)x.tagName==="LINK"&&x.rel==="modulepreload"&&p(x)}).observe(document,{childList:!0,subtree:!0});function a(f){const m={};return f.integrity&&(m.integrity=f.integrity),f.referrerPolicy&&(m.referrerPolicy=f.referrerPolicy),f.crossOrigin==="use-credentials"?m.credentials="include":f.crossOrigin==="anonymous"?m.credentials="omit":m.credentials="same-origin",m}function p(f){if(f.ep)return;f.ep=!0;const m=a(f);fetch(f.href,m)}})();function mc(u){return u&&u.__esModule&&Object.prototype.hasOwnProperty.call(u,"default")?u.default:u}var Ku={exports:{}},Lr={},Yu={exports:{}},b={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var tc;function Ld(){if(tc)return b;tc=1;var u=Symbol.for("react.element"),s=Symbol.for("react.portal"),a=Symbol.for("react.fragment"),p=Symbol.for("react.strict_mode"),f=Symbol.for("react.profiler"),m=Symbol.for("react.provider"),x=Symbol.for("react.context"),C=Symbol.for("react.forward_ref"),S=Symbol.for("react.suspense"),k=Symbol.for("react.memo"),T=Symbol.for("react.lazy"),z=Symbol.iterator;function F(v){return v===null||typeof v!="object"?null:(v=z&&v[z]||v["@@iterator"],typeof v=="function"?v:null)}var J={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Y=Object.assign,M={};function O(v,_,q){this.props=v,this.context=_,this.refs=M,this.updater=q||J}O.prototype.isReactComponent={},O.prototype.setState=function(v,_){if(typeof v!="object"&&typeof v!="function"&&v!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,v,_,"setState")},O.prototype.forceUpdate=function(v){this.updater.enqueueForceUpdate(this,v,"forceUpdate")};function W(){}W.prototype=O.prototype;function V(v,_,q){this.props=v,this.context=_,this.refs=M,this.updater=q||J}var Z=V.prototype=new W;Z.constructor=V,Y(Z,O.prototype),Z.isPureReactComponent=!0;var ne=Array.isArray,he=Object.prototype.hasOwnProperty,xe={current:null},Ee={key:!0,ref:!0,__self:!0,__source:!0};function ze(v,_,q){var ee,re={},le=null,ae=null;if(_!=null)for(ee in _.ref!==void 0&&(ae=_.ref),_.key!==void 0&&(le=""+_.key),_)he.call(_,ee)&&!Ee.hasOwnProperty(ee)&&(re[ee]=_[ee]);var ue=arguments.length-2;if(ue===1)re.children=q;else if(1>>1,_=D[v];if(0>>1;vf(re,U))le<_&&0>f(ae,re)?(D[v]=ae,D[le]=U,v=le):(D[v]=re,D[ee]=U,v=ee);else if(le<_&&0>f(ae,U))D[v]=ae,D[le]=U,v=le;else break e}}return X}function f(D,X){var U=D.sortIndex-X.sortIndex;return U!==0?U:D.id-X.id}if(typeof performance=="object"&&typeof performance.now=="function"){var m=performance;u.unstable_now=function(){return m.now()}}else{var x=Date,C=x.now();u.unstable_now=function(){return x.now()-C}}var S=[],k=[],T=1,z=null,F=3,J=!1,Y=!1,M=!1,O=typeof setTimeout=="function"?setTimeout:null,W=typeof clearTimeout=="function"?clearTimeout:null,V=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function Z(D){for(var X=a(k);X!==null;){if(X.callback===null)p(k);else if(X.startTime<=D)p(k),X.sortIndex=X.expirationTime,s(S,X);else break;X=a(k)}}function ne(D){if(M=!1,Z(D),!Y)if(a(S)!==null)Y=!0,Be(he);else{var X=a(k);X!==null&&ge(ne,X.startTime-D)}}function he(D,X){Y=!1,M&&(M=!1,W(ze),ze=-1),J=!0;var U=F;try{for(Z(X),z=a(S);z!==null&&(!(z.expirationTime>X)||D&&!ht());){var v=z.callback;if(typeof v=="function"){z.callback=null,F=z.priorityLevel;var _=v(z.expirationTime<=X);X=u.unstable_now(),typeof _=="function"?z.callback=_:z===a(S)&&p(S),Z(X)}else p(S);z=a(S)}if(z!==null)var q=!0;else{var ee=a(k);ee!==null&&ge(ne,ee.startTime-X),q=!1}return q}finally{z=null,F=U,J=!1}}var xe=!1,Ee=null,ze=-1,Ce=5,je=-1;function ht(){return!(u.unstable_now()-jeD||125v?(D.sortIndex=U,s(k,D),a(S)===null&&D===a(k)&&(M?(W(ze),ze=-1):M=!0,ge(ne,U-v))):(D.sortIndex=_,s(S,D),Y||J||(Y=!0,Be(he))),D},u.unstable_shouldYield=ht,u.unstable_wrapCallback=function(D){var X=F;return function(){var U=F;F=X;try{return D.apply(this,arguments)}finally{F=U}}}}(Ju)),Ju}var uc;function jd(){return uc||(uc=1,Gu.exports=Dd()),Gu.exports}/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var ic;function Od(){if(ic)return Ye;ic=1;var u=ti(),s=jd();function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),S=Object.prototype.hasOwnProperty,k=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,T={},z={};function F(e){return S.call(z,e)?!0:S.call(T,e)?!1:k.test(e)?z[e]=!0:(T[e]=!0,!1)}function J(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Y(e,t,n,r){if(t===null||typeof t>"u"||J(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function M(e,t,n,r,l,o,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=o,this.removeEmptyString=i}var O={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){O[e]=new M(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];O[t]=new M(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){O[e]=new M(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){O[e]=new M(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){O[e]=new M(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){O[e]=new M(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){O[e]=new M(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){O[e]=new M(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){O[e]=new M(e,5,!1,e.toLowerCase(),null,!1,!1)});var W=/[\-:]([a-z])/g;function V(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(W,V);O[t]=new M(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(W,V);O[t]=new M(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(W,V);O[t]=new M(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){O[e]=new M(e,1,!1,e.toLowerCase(),null,!1,!1)}),O.xlinkHref=new M("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){O[e]=new M(e,1,!1,e.toLowerCase(),null,!0,!0)});function Z(e,t,n,r){var l=O.hasOwnProperty(t)?O[t]:null;(l!==null?l.type!==0:r||!(2c||l[i]!==o[c]){var d=` +`+l[i].replace(" at new "," at ");return e.displayName&&d.includes("")&&(d=d.replace("",e.displayName)),d}while(1<=i&&0<=c);break}}}finally{q=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?_(e):""}function re(e){switch(e.tag){case 5:return _(e.type);case 16:return _("Lazy");case 13:return _("Suspense");case 19:return _("SuspenseList");case 0:case 2:case 15:return e=ee(e.type,!1),e;case 11:return e=ee(e.type.render,!1),e;case 1:return e=ee(e.type,!0),e;default:return""}}function le(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ee:return"Fragment";case xe:return"Portal";case Ce:return"Profiler";case ze:return"StrictMode";case Xe:return"Suspense";case ut:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case ht:return(e.displayName||"Context")+".Consumer";case je:return(e._context.displayName||"Context")+".Provider";case mt:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case vt:return t=e.displayName||null,t!==null?t:le(e.type)||"Memo";case Be:t=e._payload,e=e._init;try{return le(e(t))}catch{}}return null}function ae(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return le(t);case 8:return t===ze?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function ue(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function de(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Ge(e){var t=de(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(i){r=""+i,o.call(this,i)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(i){r=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Or(e){e._valueTracker||(e._valueTracker=Ge(e))}function ii(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=de(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Mr(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function ql(e,t){var n=t.checked;return U({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function ai(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=ue(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function si(e,t){t=t.checked,t!=null&&Z(e,"checked",t,!1)}function bl(e,t){si(e,t);var n=ue(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?eo(e,t.type,n):t.hasOwnProperty("defaultValue")&&eo(e,t.type,ue(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function ci(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function eo(e,t,n){(t!=="number"||Mr(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Vn=Array.isArray;function mn(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Ir.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Qn(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Kn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Fc=["Webkit","ms","Moz","O"];Object.keys(Kn).forEach(function(e){Fc.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Kn[t]=Kn[e]})});function vi(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Kn.hasOwnProperty(e)&&Kn[e]?(""+t).trim():t+"px"}function yi(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=vi(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Dc=U({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function ro(e,t){if(t){if(Dc[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(a(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(a(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(a(61))}if(t.style!=null&&typeof t.style!="object")throw Error(a(62))}}function lo(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var oo=null;function uo(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var io=null,vn=null,yn=null;function gi(e){if(e=hr(e)){if(typeof io!="function")throw Error(a(280));var t=e.stateNode;t&&(t=ul(t),io(e.stateNode,e.type,t))}}function wi(e){vn?yn?yn.push(e):yn=[e]:vn=e}function Si(){if(vn){var e=vn,t=yn;if(yn=vn=null,gi(e),t)for(e=0;e>>=0,e===0?32:31-(Vc(e)/Qc|0)|0}var Hr=64,Wr=4194304;function Jn(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Vr(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,o=e.pingedLanes,i=n&268435455;if(i!==0){var c=i&~l;c!==0?r=Jn(c):(o&=i,o!==0&&(r=Jn(o)))}else i=n&~l,i!==0?r=Jn(i):o!==0&&(r=Jn(o));if(r===0)return 0;if(t!==0&&t!==r&&(t&l)===0&&(l=r&-r,o=t&-t,l>=o||l===16&&(o&4194240)!==0))return t;if((r&4)!==0&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Zn(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-it(t),e[t]=n}function Gc(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=or),Xi=" ",Gi=!1;function Ji(e,t){switch(e){case"keyup":return Cf.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Zi(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Sn=!1;function _f(e,t){switch(e){case"compositionend":return Zi(t);case"keypress":return t.which!==32?null:(Gi=!0,Xi);case"textInput":return e=t.data,e===Xi&&Gi?null:e;default:return null}}function Rf(e,t){if(Sn)return e==="compositionend"||!_o&&Ji(e,t)?(e=Hi(),Gr=So=$t=null,Sn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=la(n)}}function ua(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?ua(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ia(){for(var e=window,t=Mr();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Mr(e.document)}return t}function Lo(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Mf(e){var t=ia(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&ua(n.ownerDocument.documentElement,n)){if(r!==null&&Lo(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,o=Math.min(r.start,l);r=r.end===void 0?o:Math.min(r.end,l),!e.extend&&o>r&&(l=r,r=o,o=l),l=oa(n,o);var i=oa(n,r);l&&i&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==i.node||e.focusOffset!==i.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),o>r?(e.addRange(t),e.extend(i.node,i.offset)):(t.setEnd(i.node,i.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,kn=null,To=null,sr=null,zo=!1;function aa(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;zo||kn==null||kn!==Mr(r)||(r=kn,"selectionStart"in r&&Lo(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),sr&&ar(sr,r)||(sr=r,r=rl(To,"onSelect"),0_n||(e.current=Wo[_n],Wo[_n]=null,_n--)}function se(e,t){_n++,Wo[_n]=e.current,e.current=t}var Wt={},Oe=Ht(Wt),He=Ht(!1),rn=Wt;function Rn(e,t){var n=e.type.contextTypes;if(!n)return Wt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},o;for(o in n)l[o]=t[o];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function We(e){return e=e.childContextTypes,e!=null}function il(){fe(He),fe(Oe)}function Ea(e,t,n){if(Oe.current!==Wt)throw Error(a(168));se(Oe,t),se(He,n)}function Ca(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(a(108,ae(e)||"Unknown",l));return U({},n,r)}function al(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Wt,rn=Oe.current,se(Oe,e),se(He,He.current),!0}function Pa(e,t,n){var r=e.stateNode;if(!r)throw Error(a(169));n?(e=Ca(e,t,rn),r.__reactInternalMemoizedMergedChildContext=e,fe(He),fe(Oe),se(Oe,e)):fe(He),se(He,n)}var Pt=null,sl=!1,Vo=!1;function _a(e){Pt===null?Pt=[e]:Pt.push(e)}function Xf(e){sl=!0,_a(e)}function Vt(){if(!Vo&&Pt!==null){Vo=!0;var e=0,t=ie;try{var n=Pt;for(ie=1;e>=i,l-=i,_t=1<<32-it(t)+l|n<G?(Te=K,K=null):Te=K.sibling;var oe=P(y,K,g[G],L);if(oe===null){K===null&&(K=Te);break}e&&K&&oe.alternate===null&&t(y,K),h=o(oe,h,G),Q===null?H=oe:Q.sibling=oe,Q=oe,K=Te}if(G===g.length)return n(y,K),pe&&on(y,G),H;if(K===null){for(;GG?(Te=K,K=null):Te=K.sibling;var bt=P(y,K,oe.value,L);if(bt===null){K===null&&(K=Te);break}e&&K&&bt.alternate===null&&t(y,K),h=o(bt,h,G),Q===null?H=bt:Q.sibling=bt,Q=bt,K=Te}if(oe.done)return n(y,K),pe&&on(y,G),H;if(K===null){for(;!oe.done;G++,oe=g.next())oe=N(y,oe.value,L),oe!==null&&(h=o(oe,h,G),Q===null?H=oe:Q.sibling=oe,Q=oe);return pe&&on(y,G),H}for(K=r(y,K);!oe.done;G++,oe=g.next())oe=j(K,y,G,oe.value,L),oe!==null&&(e&&oe.alternate!==null&&K.delete(oe.key===null?G:oe.key),h=o(oe,h,G),Q===null?H=oe:Q.sibling=oe,Q=oe);return e&&K.forEach(function(Nd){return t(y,Nd)}),pe&&on(y,G),H}function ke(y,h,g,L){if(typeof g=="object"&&g!==null&&g.type===Ee&&g.key===null&&(g=g.props.children),typeof g=="object"&&g!==null){switch(g.$$typeof){case he:e:{for(var H=g.key,Q=h;Q!==null;){if(Q.key===H){if(H=g.type,H===Ee){if(Q.tag===7){n(y,Q.sibling),h=l(Q,g.props.children),h.return=y,y=h;break e}}else if(Q.elementType===H||typeof H=="object"&&H!==null&&H.$$typeof===Be&&Fa(H)===Q.type){n(y,Q.sibling),h=l(Q,g.props),h.ref=mr(y,Q,g),h.return=y,y=h;break e}n(y,Q);break}else t(y,Q);Q=Q.sibling}g.type===Ee?(h=hn(g.props.children,y.mode,L,g.key),h.return=y,y=h):(L=Il(g.type,g.key,g.props,null,y.mode,L),L.ref=mr(y,h,g),L.return=y,y=L)}return i(y);case xe:e:{for(Q=g.key;h!==null;){if(h.key===Q)if(h.tag===4&&h.stateNode.containerInfo===g.containerInfo&&h.stateNode.implementation===g.implementation){n(y,h.sibling),h=l(h,g.children||[]),h.return=y,y=h;break e}else{n(y,h);break}else t(y,h);h=h.sibling}h=Bu(g,y.mode,L),h.return=y,y=h}return i(y);case Be:return Q=g._init,ke(y,h,Q(g._payload),L)}if(Vn(g))return $(y,h,g,L);if(X(g))return B(y,h,g,L);pl(y,g)}return typeof g=="string"&&g!==""||typeof g=="number"?(g=""+g,h!==null&&h.tag===6?(n(y,h.sibling),h=l(h,g),h.return=y,y=h):(n(y,h),h=Au(g,y.mode,L),h.return=y,y=h),i(y)):n(y,h)}return ke}var zn=Da(!0),ja=Da(!1),hl=Ht(null),ml=null,Fn=null,Jo=null;function Zo(){Jo=Fn=ml=null}function qo(e){var t=hl.current;fe(hl),e._currentValue=t}function bo(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Dn(e,t){ml=e,Jo=Fn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(Ve=!0),e.firstContext=null)}function nt(e){var t=e._currentValue;if(Jo!==e)if(e={context:e,memoizedValue:t,next:null},Fn===null){if(ml===null)throw Error(a(308));Fn=e,ml.dependencies={lanes:0,firstContext:e}}else Fn=Fn.next=e;return t}var un=null;function eu(e){un===null?un=[e]:un.push(e)}function Oa(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,eu(t)):(n.next=l.next,l.next=n),t.interleaved=n,Nt(e,r)}function Nt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var Qt=!1;function tu(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ma(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Lt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Kt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,(te&2)!==0){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Nt(e,n)}return l=r.interleaved,l===null?(t.next=t,eu(r)):(t.next=l.next,l.next=t),r.interleaved=t,Nt(e,n)}function vl(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,mo(e,n)}}function Ia(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,o=null;if(n=n.firstBaseUpdate,n!==null){do{var i={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};o===null?l=o=i:o=o.next=i,n=n.next}while(n!==null);o===null?l=o=t:o=o.next=t}else l=o=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:o,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function yl(e,t,n,r){var l=e.updateQueue;Qt=!1;var o=l.firstBaseUpdate,i=l.lastBaseUpdate,c=l.shared.pending;if(c!==null){l.shared.pending=null;var d=c,w=d.next;d.next=null,i===null?o=w:i.next=w,i=d;var R=e.alternate;R!==null&&(R=R.updateQueue,c=R.lastBaseUpdate,c!==i&&(c===null?R.firstBaseUpdate=w:c.next=w,R.lastBaseUpdate=d))}if(o!==null){var N=l.baseState;i=0,R=w=d=null,c=o;do{var P=c.lane,j=c.eventTime;if((r&P)===P){R!==null&&(R=R.next={eventTime:j,lane:0,tag:c.tag,payload:c.payload,callback:c.callback,next:null});e:{var $=e,B=c;switch(P=t,j=n,B.tag){case 1:if($=B.payload,typeof $=="function"){N=$.call(j,N,P);break e}N=$;break e;case 3:$.flags=$.flags&-65537|128;case 0:if($=B.payload,P=typeof $=="function"?$.call(j,N,P):$,P==null)break e;N=U({},N,P);break e;case 2:Qt=!0}}c.callback!==null&&c.lane!==0&&(e.flags|=64,P=l.effects,P===null?l.effects=[c]:P.push(c))}else j={eventTime:j,lane:P,tag:c.tag,payload:c.payload,callback:c.callback,next:null},R===null?(w=R=j,d=N):R=R.next=j,i|=P;if(c=c.next,c===null){if(c=l.shared.pending,c===null)break;P=c,c=P.next,P.next=null,l.lastBaseUpdate=P,l.shared.pending=null}}while(!0);if(R===null&&(d=N),l.baseState=d,l.firstBaseUpdate=w,l.lastBaseUpdate=R,t=l.shared.interleaved,t!==null){l=t;do i|=l.lane,l=l.next;while(l!==t)}else o===null&&(l.shared.lanes=0);cn|=i,e.lanes=i,e.memoizedState=N}}function Ua(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=uu.transition;uu.transition={};try{e(!1),t()}finally{ie=n,uu.transition=r}}function rs(){return rt().memoizedState}function qf(e,t,n){var r=Jt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},ls(e))os(t,n);else if(n=Oa(e,t,n,r),n!==null){var l=Ae();pt(n,e,r,l),us(n,t,r)}}function bf(e,t,n){var r=Jt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(ls(e))os(t,l);else{var o=e.alternate;if(e.lanes===0&&(o===null||o.lanes===0)&&(o=t.lastRenderedReducer,o!==null))try{var i=t.lastRenderedState,c=o(i,n);if(l.hasEagerState=!0,l.eagerState=c,at(c,i)){var d=t.interleaved;d===null?(l.next=l,eu(t)):(l.next=d.next,d.next=l),t.interleaved=l;return}}catch{}finally{}n=Oa(e,t,l,r),n!==null&&(l=Ae(),pt(n,e,r,l),us(n,t,r))}}function ls(e){var t=e.alternate;return e===ve||t!==null&&t===ve}function os(e,t){wr=Sl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function us(e,t,n){if((n&4194240)!==0){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,mo(e,n)}}var El={readContext:nt,useCallback:Me,useContext:Me,useEffect:Me,useImperativeHandle:Me,useInsertionEffect:Me,useLayoutEffect:Me,useMemo:Me,useReducer:Me,useRef:Me,useState:Me,useDebugValue:Me,useDeferredValue:Me,useTransition:Me,useMutableSource:Me,useSyncExternalStore:Me,useId:Me,unstable_isNewReconciler:!1},ed={readContext:nt,useCallback:function(e,t){return St().memoizedState=[e,t===void 0?null:t],e},useContext:nt,useEffect:Ga,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,kl(4194308,4,qa.bind(null,t,e),n)},useLayoutEffect:function(e,t){return kl(4194308,4,e,t)},useInsertionEffect:function(e,t){return kl(4,2,e,t)},useMemo:function(e,t){var n=St();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=St();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=qf.bind(null,ve,e),[r.memoizedState,e]},useRef:function(e){var t=St();return e={current:e},t.memoizedState=e},useState:Ya,useDebugValue:pu,useDeferredValue:function(e){return St().memoizedState=e},useTransition:function(){var e=Ya(!1),t=e[0];return e=Zf.bind(null,e[1]),St().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=ve,l=St();if(pe){if(n===void 0)throw Error(a(407));n=n()}else{if(n=t(),Le===null)throw Error(a(349));(sn&30)!==0||Ha(r,t,n)}l.memoizedState=n;var o={value:n,getSnapshot:t};return l.queue=o,Ga(Va.bind(null,r,o,e),[e]),r.flags|=2048,xr(9,Wa.bind(null,r,o,n,t),void 0,null),n},useId:function(){var e=St(),t=Le.identifierPrefix;if(pe){var n=Rt,r=_t;n=(r&~(1<<32-it(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Sr++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=i.createElement(n,{is:r.is}):(e=i.createElement(n),n==="select"&&(i=e,r.multiple?i.multiple=!0:r.size&&(i.size=r.size))):e=i.createElementNS(e,n),e[gt]=t,e[pr]=r,_s(e,t,!1,!1),t.stateNode=e;e:{switch(i=lo(n,r),n){case"dialog":ce("cancel",e),ce("close",e),l=r;break;case"iframe":case"object":case"embed":ce("load",e),l=r;break;case"video":case"audio":for(l=0;lUn&&(t.flags|=128,r=!0,Er(o,!1),t.lanes=4194304)}else{if(!r)if(e=gl(i),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Er(o,!0),o.tail===null&&o.tailMode==="hidden"&&!i.alternate&&!pe)return Ie(t),null}else 2*Se()-o.renderingStartTime>Un&&n!==1073741824&&(t.flags|=128,r=!0,Er(o,!1),t.lanes=4194304);o.isBackwards?(i.sibling=t.child,t.child=i):(n=o.last,n!==null?n.sibling=i:t.child=i,o.last=i)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=Se(),t.sibling=null,n=me.current,se(me,r?n&1|2:n&1),t):(Ie(t),null);case 22:case 23:return Iu(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&(t.mode&1)!==0?(be&1073741824)!==0&&(Ie(t),t.subtreeFlags&6&&(t.flags|=8192)):Ie(t),null;case 24:return null;case 25:return null}throw Error(a(156,t.tag))}function ad(e,t){switch(Ko(t),t.tag){case 1:return We(t.type)&&il(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return jn(),fe(He),fe(Oe),ou(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 5:return ru(t),null;case 13:if(fe(me),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(a(340));Tn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return fe(me),null;case 4:return jn(),null;case 10:return qo(t.type._context),null;case 22:case 23:return Iu(),null;case 24:return null;default:return null}}var Rl=!1,Ue=!1,sd=typeof WeakSet=="function"?WeakSet:Set,I=null;function Mn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){we(e,t,r)}else n.current=null}function Pu(e,t,n){try{n()}catch(r){we(e,t,r)}}var Ls=!1;function cd(e,t){if(Io=Yr,e=ia(),Lo(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var i=0,c=-1,d=-1,w=0,R=0,N=e,P=null;t:for(;;){for(var j;N!==n||l!==0&&N.nodeType!==3||(c=i+l),N!==o||r!==0&&N.nodeType!==3||(d=i+r),N.nodeType===3&&(i+=N.nodeValue.length),(j=N.firstChild)!==null;)P=N,N=j;for(;;){if(N===e)break t;if(P===n&&++w===l&&(c=i),P===o&&++R===r&&(d=i),(j=N.nextSibling)!==null)break;N=P,P=N.parentNode}N=j}n=c===-1||d===-1?null:{start:c,end:d}}else n=null}n=n||{start:0,end:0}}else n=null;for(Uo={focusedElem:e,selectionRange:n},Yr=!1,I=t;I!==null;)if(t=I,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,I=e;else for(;I!==null;){t=I;try{var $=t.alternate;if((t.flags&1024)!==0)switch(t.tag){case 0:case 11:case 15:break;case 1:if($!==null){var B=$.memoizedProps,ke=$.memoizedState,y=t.stateNode,h=y.getSnapshotBeforeUpdate(t.elementType===t.type?B:ct(t.type,B),ke);y.__reactInternalSnapshotBeforeUpdate=h}break;case 3:var g=t.stateNode.containerInfo;g.nodeType===1?g.textContent="":g.nodeType===9&&g.documentElement&&g.removeChild(g.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(a(163))}}catch(L){we(t,t.return,L)}if(e=t.sibling,e!==null){e.return=t.return,I=e;break}I=t.return}return $=Ls,Ls=!1,$}function Cr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Pu(t,n,o)}l=l.next}while(l!==r)}}function Nl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function _u(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Ts(e){var t=e.alternate;t!==null&&(e.alternate=null,Ts(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[gt],delete t[pr],delete t[Ho],delete t[Kf],delete t[Yf])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function zs(e){return e.tag===5||e.tag===3||e.tag===4}function Fs(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||zs(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Ru(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ol));else if(r!==4&&(e=e.child,e!==null))for(Ru(e,t,n),e=e.sibling;e!==null;)Ru(e,t,n),e=e.sibling}function Nu(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(Nu(e,t,n),e=e.sibling;e!==null;)Nu(e,t,n),e=e.sibling}var Fe=null,ft=!1;function Yt(e,t,n){for(n=n.child;n!==null;)Ds(e,t,n),n=n.sibling}function Ds(e,t,n){if(yt&&typeof yt.onCommitFiberUnmount=="function")try{yt.onCommitFiberUnmount(Br,n)}catch{}switch(n.tag){case 5:Ue||Mn(n,t);case 6:var r=Fe,l=ft;Fe=null,Yt(e,t,n),Fe=r,ft=l,Fe!==null&&(ft?(e=Fe,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Fe.removeChild(n.stateNode));break;case 18:Fe!==null&&(ft?(e=Fe,n=n.stateNode,e.nodeType===8?Bo(e.parentNode,n):e.nodeType===1&&Bo(e,n),nr(e)):Bo(Fe,n.stateNode));break;case 4:r=Fe,l=ft,Fe=n.stateNode.containerInfo,ft=!0,Yt(e,t,n),Fe=r,ft=l;break;case 0:case 11:case 14:case 15:if(!Ue&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,i=o.destroy;o=o.tag,i!==void 0&&((o&2)!==0||(o&4)!==0)&&Pu(n,t,i),l=l.next}while(l!==r)}Yt(e,t,n);break;case 1:if(!Ue&&(Mn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(c){we(n,t,c)}Yt(e,t,n);break;case 21:Yt(e,t,n);break;case 22:n.mode&1?(Ue=(r=Ue)||n.memoizedState!==null,Yt(e,t,n),Ue=r):Yt(e,t,n);break;default:Yt(e,t,n)}}function js(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new sd),t.forEach(function(r){var l=wd.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function dt(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=i),r&=~o}if(r=l,r=Se()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*dd(r/1960))-r,10e?16:e,Gt===null)var r=!1;else{if(e=Gt,Gt=null,Dl=0,(te&6)!==0)throw Error(a(331));var l=te;for(te|=4,I=e.current;I!==null;){var o=I,i=o.child;if((I.flags&16)!==0){var c=o.deletions;if(c!==null){for(var d=0;dSe()-zu?dn(e,0):Tu|=n),Ke(e,t)}function Ys(e,t){t===0&&((e.mode&1)===0?t=1:(t=Wr,Wr<<=1,(Wr&130023424)===0&&(Wr=4194304)));var n=Ae();e=Nt(e,t),e!==null&&(Zn(e,t,n),Ke(e,n))}function gd(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ys(e,n)}function wd(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(a(314))}r!==null&&r.delete(t),Ys(e,n)}var Xs;Xs=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||He.current)Ve=!0;else{if((e.lanes&n)===0&&(t.flags&128)===0)return Ve=!1,ud(e,t,n);Ve=(e.flags&131072)!==0}else Ve=!1,pe&&(t.flags&1048576)!==0&&Ra(t,fl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;_l(e,t),e=t.pendingProps;var l=Rn(t,Oe.current);Dn(t,n),l=au(null,t,r,e,l,n);var o=su();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,We(r)?(o=!0,al(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,tu(t),l.updater=Cl,t.stateNode=l,l._reactInternals=t,mu(t,r,e,n),t=wu(null,t,r,!0,o,n)):(t.tag=0,pe&&o&&Qo(t),$e(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(_l(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=kd(r),e=ct(r,e),l){case 0:t=gu(null,t,r,e,n);break e;case 1:t=Ss(null,t,r,e,n);break e;case 11:t=ms(null,t,r,e,n);break e;case 14:t=vs(null,t,r,ct(r.type,e),n);break e}throw Error(a(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ct(r,l),gu(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ct(r,l),Ss(e,t,r,l,n);case 3:e:{if(ks(t),e===null)throw Error(a(387));r=t.pendingProps,o=t.memoizedState,l=o.element,Ma(e,t),yl(t,r,null,n);var i=t.memoizedState;if(r=i.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:i.cache,pendingSuspenseBoundaries:i.pendingSuspenseBoundaries,transitions:i.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=On(Error(a(423)),t),t=xs(e,t,r,n,l);break e}else if(r!==l){l=On(Error(a(424)),t),t=xs(e,t,r,n,l);break e}else for(qe=Bt(t.stateNode.containerInfo.firstChild),Ze=t,pe=!0,st=null,n=ja(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Tn(),r===l){t=Tt(e,t,n);break e}$e(e,t,r,n)}t=t.child}return t;case 5:return $a(t),e===null&&Xo(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,i=l.children,$o(r,l)?i=null:o!==null&&$o(r,o)&&(t.flags|=32),ws(e,t),$e(e,t,i,n),t.child;case 6:return e===null&&Xo(t),null;case 13:return Es(e,t,n);case 4:return nu(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=zn(t,null,r,n):$e(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ct(r,l),ms(e,t,r,l,n);case 7:return $e(e,t,t.pendingProps,n),t.child;case 8:return $e(e,t,t.pendingProps.children,n),t.child;case 12:return $e(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,i=l.value,se(hl,r._currentValue),r._currentValue=i,o!==null)if(at(o.value,i)){if(o.children===l.children&&!He.current){t=Tt(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var c=o.dependencies;if(c!==null){i=o.child;for(var d=c.firstContext;d!==null;){if(d.context===r){if(o.tag===1){d=Lt(-1,n&-n),d.tag=2;var w=o.updateQueue;if(w!==null){w=w.shared;var R=w.pending;R===null?d.next=d:(d.next=R.next,R.next=d),w.pending=d}}o.lanes|=n,d=o.alternate,d!==null&&(d.lanes|=n),bo(o.return,n,t),c.lanes|=n;break}d=d.next}}else if(o.tag===10)i=o.type===t.type?null:o.child;else if(o.tag===18){if(i=o.return,i===null)throw Error(a(341));i.lanes|=n,c=i.alternate,c!==null&&(c.lanes|=n),bo(i,n,t),i=o.sibling}else i=o.child;if(i!==null)i.return=o;else for(i=o;i!==null;){if(i===t){i=null;break}if(o=i.sibling,o!==null){o.return=i.return,i=o;break}i=i.return}o=i}$e(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,Dn(t,n),l=nt(l),r=r(l),t.flags|=1,$e(e,t,r,n),t.child;case 14:return r=t.type,l=ct(r,t.pendingProps),l=ct(r.type,l),vs(e,t,r,l,n);case 15:return ys(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:ct(r,l),_l(e,t),t.tag=1,We(r)?(e=!0,al(t)):e=!1,Dn(t,n),as(t,r,l),mu(t,r,l,n),wu(null,t,r,!0,e,n);case 19:return Ps(e,t,n);case 22:return gs(e,t,n)}throw Error(a(156,t.tag))};function Gs(e,t){return Ni(e,t)}function Sd(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ot(e,t,n,r){return new Sd(e,t,n,r)}function $u(e){return e=e.prototype,!(!e||!e.isReactComponent)}function kd(e){if(typeof e=="function")return $u(e)?1:0;if(e!=null){if(e=e.$$typeof,e===mt)return 11;if(e===vt)return 14}return 2}function qt(e,t){var n=e.alternate;return n===null?(n=ot(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Il(e,t,n,r,l,o){var i=2;if(r=e,typeof e=="function")$u(e)&&(i=1);else if(typeof e=="string")i=5;else e:switch(e){case Ee:return hn(n.children,l,o,t);case ze:i=8,l|=8;break;case Ce:return e=ot(12,n,t,l|2),e.elementType=Ce,e.lanes=o,e;case Xe:return e=ot(13,n,t,l),e.elementType=Xe,e.lanes=o,e;case ut:return e=ot(19,n,t,l),e.elementType=ut,e.lanes=o,e;case ge:return Ul(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case je:i=10;break e;case ht:i=9;break e;case mt:i=11;break e;case vt:i=14;break e;case Be:i=16,r=null;break e}throw Error(a(130,e==null?e:typeof e,""))}return t=ot(i,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function hn(e,t,n,r){return e=ot(7,e,r,t),e.lanes=n,e}function Ul(e,t,n,r){return e=ot(22,e,r,t),e.elementType=ge,e.lanes=n,e.stateNode={isHidden:!1},e}function Au(e,t,n){return e=ot(6,e,null,t),e.lanes=n,e}function Bu(e,t,n){return t=ot(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function xd(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=ho(0),this.expirationTimes=ho(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=ho(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Hu(e,t,n,r,l,o,i,c,d){return e=new xd(e,t,n,c,d),t===1?(t=1,o===!0&&(t|=8)):t=0,o=ot(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},tu(o),e}function Ed(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(u)}catch(s){console.error(s)}}return u(),Xu.exports=Od(),Xu.exports}var sc;function Id(){if(sc)return Ql;sc=1;var u=Md();return Ql.createRoot=u.createRoot,Ql.hydrateRoot=u.hydrateRoot,Ql}var Ud=Id();const $d=mc(Ud);/** + * react-router v7.7.1 + * + * Copyright (c) Remix Software Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */var cc="popstate";function Ad(u={}){function s(p,f){let{pathname:m,search:x,hash:C}=p.location;return bu("",{pathname:m,search:x,hash:C},f.state&&f.state.usr||null,f.state&&f.state.key||"default")}function a(p,f){return typeof f=="string"?f:zr(f)}return Hd(s,a,null,u)}function ye(u,s){if(u===!1||u===null||typeof u>"u")throw new Error(s)}function xt(u,s){if(!u){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function Bd(){return Math.random().toString(36).substring(2,10)}function fc(u,s){return{usr:u.state,key:u.key,idx:s}}function bu(u,s,a=null,p){return{pathname:typeof u=="string"?u:u.pathname,search:"",hash:"",...typeof s=="string"?Hn(s):s,state:a,key:s&&s.key||p||Bd()}}function zr({pathname:u="/",search:s="",hash:a=""}){return s&&s!=="?"&&(u+=s.charAt(0)==="?"?s:"?"+s),a&&a!=="#"&&(u+=a.charAt(0)==="#"?a:"#"+a),u}function Hn(u){let s={};if(u){let a=u.indexOf("#");a>=0&&(s.hash=u.substring(a),u=u.substring(0,a));let p=u.indexOf("?");p>=0&&(s.search=u.substring(p),u=u.substring(0,p)),u&&(s.pathname=u)}return s}function Hd(u,s,a,p={}){let{window:f=document.defaultView,v5Compat:m=!1}=p,x=f.history,C="POP",S=null,k=T();k==null&&(k=0,x.replaceState({...x.state,idx:k},""));function T(){return(x.state||{idx:null}).idx}function z(){C="POP";let O=T(),W=O==null?null:O-k;k=O,S&&S({action:C,location:M.location,delta:W})}function F(O,W){C="PUSH";let V=bu(M.location,O,W);k=T()+1;let Z=fc(V,k),ne=M.createHref(V);try{x.pushState(Z,"",ne)}catch(he){if(he instanceof DOMException&&he.name==="DataCloneError")throw he;f.location.assign(ne)}m&&S&&S({action:C,location:M.location,delta:1})}function J(O,W){C="REPLACE";let V=bu(M.location,O,W);k=T();let Z=fc(V,k),ne=M.createHref(V);x.replaceState(Z,"",ne),m&&S&&S({action:C,location:M.location,delta:0})}function Y(O){return Wd(O)}let M={get action(){return C},get location(){return u(f,x)},listen(O){if(S)throw new Error("A history only accepts one active listener");return f.addEventListener(cc,z),S=O,()=>{f.removeEventListener(cc,z),S=null}},createHref(O){return s(f,O)},createURL:Y,encodeLocation(O){let W=Y(O);return{pathname:W.pathname,search:W.search,hash:W.hash}},push:F,replace:J,go(O){return x.go(O)}};return M}function Wd(u,s=!1){let a="http://localhost";typeof window<"u"&&(a=window.location.origin!=="null"?window.location.origin:window.location.href),ye(a,"No window.location.(origin|href) available to create URL");let p=typeof u=="string"?u:zr(u);return p=p.replace(/ $/,"%20"),!s&&p.startsWith("//")&&(p=a+p),new URL(p,a)}function vc(u,s,a="/"){return Vd(u,s,a,!1)}function Vd(u,s,a,p){let f=typeof s=="string"?Hn(s):s,m=Dt(f.pathname||"/",a);if(m==null)return null;let x=yc(u);Qd(x);let C=null;for(let S=0;C==null&&S{let S={relativePath:C===void 0?m.path||"":C,caseSensitive:m.caseSensitive===!0,childrenIndex:x,route:m};S.relativePath.startsWith("/")&&(ye(S.relativePath.startsWith(p),`Absolute route path "${S.relativePath}" nested under path "${p}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),S.relativePath=S.relativePath.slice(p.length));let k=Ft([p,S.relativePath]),T=a.concat(S);m.children&&m.children.length>0&&(ye(m.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${k}".`),yc(m.children,s,T,k)),!(m.path==null&&!m.index)&&s.push({path:k,score:qd(k,m.index),routesMeta:T})};return u.forEach((m,x)=>{var C;if(m.path===""||!((C=m.path)!=null&&C.includes("?")))f(m,x);else for(let S of gc(m.path))f(m,x,S)}),s}function gc(u){let s=u.split("/");if(s.length===0)return[];let[a,...p]=s,f=a.endsWith("?"),m=a.replace(/\?$/,"");if(p.length===0)return f?[m,""]:[m];let x=gc(p.join("/")),C=[];return C.push(...x.map(S=>S===""?m:[m,S].join("/"))),f&&C.push(...x),C.map(S=>u.startsWith("/")&&S===""?"/":S)}function Qd(u){u.sort((s,a)=>s.score!==a.score?a.score-s.score:bd(s.routesMeta.map(p=>p.childrenIndex),a.routesMeta.map(p=>p.childrenIndex)))}var Kd=/^:[\w-]+$/,Yd=3,Xd=2,Gd=1,Jd=10,Zd=-2,dc=u=>u==="*";function qd(u,s){let a=u.split("/"),p=a.length;return a.some(dc)&&(p+=Zd),s&&(p+=Xd),a.filter(f=>!dc(f)).reduce((f,m)=>f+(Kd.test(m)?Yd:m===""?Gd:Jd),p)}function bd(u,s){return u.length===s.length&&u.slice(0,-1).every((p,f)=>p===s[f])?u[u.length-1]-s[s.length-1]:0}function ep(u,s,a=!1){let{routesMeta:p}=u,f={},m="/",x=[];for(let C=0;C{if(T==="*"){let Y=C[F]||"";x=m.slice(0,m.length-Y.length).replace(/(.)\/+$/,"$1")}const J=C[F];return z&&!J?k[T]=void 0:k[T]=(J||"").replace(/%2F/g,"/"),k},{}),pathname:m,pathnameBase:x,pattern:u}}function tp(u,s=!1,a=!0){xt(u==="*"||!u.endsWith("*")||u.endsWith("/*"),`Route path "${u}" will be treated as if it were "${u.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${u.replace(/\*$/,"/*")}".`);let p=[],f="^"+u.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(x,C,S)=>(p.push({paramName:C,isOptional:S!=null}),S?"/?([^\\/]+)?":"/([^\\/]+)"));return u.endsWith("*")?(p.push({paramName:"*"}),f+=u==="*"||u==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):a?f+="\\/*$":u!==""&&u!=="/"&&(f+="(?:(?=\\/|$))"),[new RegExp(f,s?void 0:"i"),p]}function np(u){try{return u.split("/").map(s=>decodeURIComponent(s).replace(/\//g,"%2F")).join("/")}catch(s){return xt(!1,`The URL path "${u}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${s}).`),u}}function Dt(u,s){if(s==="/")return u;if(!u.toLowerCase().startsWith(s.toLowerCase()))return null;let a=s.endsWith("/")?s.length-1:s.length,p=u.charAt(a);return p&&p!=="/"?null:u.slice(a)||"/"}function rp(u,s="/"){let{pathname:a,search:p="",hash:f=""}=typeof u=="string"?Hn(u):u;return{pathname:a?a.startsWith("/")?a:lp(a,s):s,search:ip(p),hash:ap(f)}}function lp(u,s){let a=s.replace(/\/+$/,"").split("/");return u.split("/").forEach(f=>{f===".."?a.length>1&&a.pop():f!=="."&&a.push(f)}),a.length>1?a.join("/"):"/"}function Zu(u,s,a,p){return`Cannot include a '${u}' character in a manually specified \`to.${s}\` field [${JSON.stringify(p)}]. Please separate it out to the \`to.${a}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function op(u){return u.filter((s,a)=>a===0||s.route.path&&s.route.path.length>0)}function wc(u){let s=op(u);return s.map((a,p)=>p===s.length-1?a.pathname:a.pathnameBase)}function Sc(u,s,a,p=!1){let f;typeof u=="string"?f=Hn(u):(f={...u},ye(!f.pathname||!f.pathname.includes("?"),Zu("?","pathname","search",f)),ye(!f.pathname||!f.pathname.includes("#"),Zu("#","pathname","hash",f)),ye(!f.search||!f.search.includes("#"),Zu("#","search","hash",f)));let m=u===""||f.pathname==="",x=m?"/":f.pathname,C;if(x==null)C=a;else{let z=s.length-1;if(!p&&x.startsWith("..")){let F=x.split("/");for(;F[0]==="..";)F.shift(),z-=1;f.pathname=F.join("/")}C=z>=0?s[z]:"/"}let S=rp(f,C),k=x&&x!=="/"&&x.endsWith("/"),T=(m||x===".")&&a.endsWith("/");return!S.pathname.endsWith("/")&&(k||T)&&(S.pathname+="/"),S}var Ft=u=>u.join("/").replace(/\/\/+/g,"/"),up=u=>u.replace(/\/+$/,"").replace(/^\/*/,"/"),ip=u=>!u||u==="?"?"":u.startsWith("?")?u:"?"+u,ap=u=>!u||u==="#"?"":u.startsWith("#")?u:"#"+u;function sp(u){return u!=null&&typeof u.status=="number"&&typeof u.statusText=="string"&&typeof u.internal=="boolean"&&"data"in u}var kc=["POST","PUT","PATCH","DELETE"];new Set(kc);var cp=["GET",...kc];new Set(cp);var Wn=E.createContext(null);Wn.displayName="DataRouter";var Jl=E.createContext(null);Jl.displayName="DataRouterState";E.createContext(!1);var xc=E.createContext({isTransitioning:!1});xc.displayName="ViewTransition";var fp=E.createContext(new Map);fp.displayName="Fetchers";var dp=E.createContext(null);dp.displayName="Await";var Et=E.createContext(null);Et.displayName="Navigation";var Fr=E.createContext(null);Fr.displayName="Location";var jt=E.createContext({outlet:null,matches:[],isDataRoute:!1});jt.displayName="Route";var ni=E.createContext(null);ni.displayName="RouteError";function pp(u,{relative:s}={}){ye(Dr(),"useHref() may be used only in the context of a component.");let{basename:a,navigator:p}=E.useContext(Et),{hash:f,pathname:m,search:x}=jr(u,{relative:s}),C=m;return a!=="/"&&(C=m==="/"?a:Ft([a,m])),p.createHref({pathname:C,search:x,hash:f})}function Dr(){return E.useContext(Fr)!=null}function en(){return ye(Dr(),"useLocation() may be used only in the context of a component."),E.useContext(Fr).location}var Ec="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Cc(u){E.useContext(Et).static||E.useLayoutEffect(u)}function hp(){let{isDataRoute:u}=E.useContext(jt);return u?Rp():mp()}function mp(){ye(Dr(),"useNavigate() may be used only in the context of a component.");let u=E.useContext(Wn),{basename:s,navigator:a}=E.useContext(Et),{matches:p}=E.useContext(jt),{pathname:f}=en(),m=JSON.stringify(wc(p)),x=E.useRef(!1);return Cc(()=>{x.current=!0}),E.useCallback((S,k={})=>{if(xt(x.current,Ec),!x.current)return;if(typeof S=="number"){a.go(S);return}let T=Sc(S,JSON.parse(m),f,k.relative==="path");u==null&&s!=="/"&&(T.pathname=T.pathname==="/"?s:Ft([s,T.pathname])),(k.replace?a.replace:a.push)(T,k.state,k)},[s,a,m,f,u])}E.createContext(null);function jr(u,{relative:s}={}){let{matches:a}=E.useContext(jt),{pathname:p}=en(),f=JSON.stringify(wc(a));return E.useMemo(()=>Sc(u,JSON.parse(f),p,s==="path"),[u,f,p,s])}function vp(u,s){return Pc(u,s)}function Pc(u,s,a,p){var W;ye(Dr(),"useRoutes() may be used only in the context of a component.");let{navigator:f}=E.useContext(Et),{matches:m}=E.useContext(jt),x=m[m.length-1],C=x?x.params:{},S=x?x.pathname:"/",k=x?x.pathnameBase:"/",T=x&&x.route;{let V=T&&T.path||"";_c(S,!T||V.endsWith("*")||V.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${S}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let z=en(),F;if(s){let V=typeof s=="string"?Hn(s):s;ye(k==="/"||((W=V.pathname)==null?void 0:W.startsWith(k)),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${k}" but pathname "${V.pathname}" was given in the \`location\` prop.`),F=V}else F=z;let J=F.pathname||"/",Y=J;if(k!=="/"){let V=k.replace(/^\//,"").split("/");Y="/"+J.replace(/^\//,"").split("/").slice(V.length).join("/")}let M=vc(u,{pathname:Y});xt(T||M!=null,`No routes matched location "${F.pathname}${F.search}${F.hash}" `),xt(M==null||M[M.length-1].route.element!==void 0||M[M.length-1].route.Component!==void 0||M[M.length-1].route.lazy!==void 0,`Matched leaf route at location "${F.pathname}${F.search}${F.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let O=kp(M&&M.map(V=>Object.assign({},V,{params:Object.assign({},C,V.params),pathname:Ft([k,f.encodeLocation?f.encodeLocation(V.pathname).pathname:V.pathname]),pathnameBase:V.pathnameBase==="/"?k:Ft([k,f.encodeLocation?f.encodeLocation(V.pathnameBase).pathname:V.pathnameBase])})),m,a,p);return s&&O?E.createElement(Fr.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...F},navigationType:"POP"}},O):O}function yp(){let u=_p(),s=sp(u)?`${u.status} ${u.statusText}`:u instanceof Error?u.message:JSON.stringify(u),a=u instanceof Error?u.stack:null,p="rgba(200,200,200, 0.5)",f={padding:"0.5rem",backgroundColor:p},m={padding:"2px 4px",backgroundColor:p},x=null;return console.error("Error handled by React Router default ErrorBoundary:",u),x=E.createElement(E.Fragment,null,E.createElement("p",null,"💿 Hey developer 👋"),E.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",E.createElement("code",{style:m},"ErrorBoundary")," or"," ",E.createElement("code",{style:m},"errorElement")," prop on your route.")),E.createElement(E.Fragment,null,E.createElement("h2",null,"Unexpected Application Error!"),E.createElement("h3",{style:{fontStyle:"italic"}},s),a?E.createElement("pre",{style:f},a):null,x)}var gp=E.createElement(yp,null),wp=class extends E.Component{constructor(u){super(u),this.state={location:u.location,revalidation:u.revalidation,error:u.error}}static getDerivedStateFromError(u){return{error:u}}static getDerivedStateFromProps(u,s){return s.location!==u.location||s.revalidation!=="idle"&&u.revalidation==="idle"?{error:u.error,location:u.location,revalidation:u.revalidation}:{error:u.error!==void 0?u.error:s.error,location:s.location,revalidation:u.revalidation||s.revalidation}}componentDidCatch(u,s){console.error("React Router caught the following error during render",u,s)}render(){return this.state.error!==void 0?E.createElement(jt.Provider,{value:this.props.routeContext},E.createElement(ni.Provider,{value:this.state.error,children:this.props.component})):this.props.children}};function Sp({routeContext:u,match:s,children:a}){let p=E.useContext(Wn);return p&&p.static&&p.staticContext&&(s.route.errorElement||s.route.ErrorBoundary)&&(p.staticContext._deepestRenderedBoundaryId=s.route.id),E.createElement(jt.Provider,{value:u},a)}function kp(u,s=[],a=null,p=null){if(u==null){if(!a)return null;if(a.errors)u=a.matches;else if(s.length===0&&!a.initialized&&a.matches.length>0)u=a.matches;else return null}let f=u,m=a==null?void 0:a.errors;if(m!=null){let S=f.findIndex(k=>k.route.id&&(m==null?void 0:m[k.route.id])!==void 0);ye(S>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(m).join(",")}`),f=f.slice(0,Math.min(f.length,S+1))}let x=!1,C=-1;if(a)for(let S=0;S=0?f=f.slice(0,C+1):f=[f[0]];break}}}return f.reduceRight((S,k,T)=>{let z,F=!1,J=null,Y=null;a&&(z=m&&k.route.id?m[k.route.id]:void 0,J=k.route.errorElement||gp,x&&(C<0&&T===0?(_c("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),F=!0,Y=null):C===T&&(F=!0,Y=k.route.hydrateFallbackElement||null)));let M=s.concat(f.slice(0,T+1)),O=()=>{let W;return z?W=J:F?W=Y:k.route.Component?W=E.createElement(k.route.Component,null):k.route.element?W=k.route.element:W=S,E.createElement(Sp,{match:k,routeContext:{outlet:S,matches:M,isDataRoute:a!=null},children:W})};return a&&(k.route.ErrorBoundary||k.route.errorElement||T===0)?E.createElement(wp,{location:a.location,revalidation:a.revalidation,component:J,error:z,children:O(),routeContext:{outlet:null,matches:M,isDataRoute:!0}}):O()},null)}function ri(u){return`${u} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function xp(u){let s=E.useContext(Wn);return ye(s,ri(u)),s}function Ep(u){let s=E.useContext(Jl);return ye(s,ri(u)),s}function Cp(u){let s=E.useContext(jt);return ye(s,ri(u)),s}function li(u){let s=Cp(u),a=s.matches[s.matches.length-1];return ye(a.route.id,`${u} can only be used on routes that contain a unique "id"`),a.route.id}function Pp(){return li("useRouteId")}function _p(){var p;let u=E.useContext(ni),s=Ep("useRouteError"),a=li("useRouteError");return u!==void 0?u:(p=s.errors)==null?void 0:p[a]}function Rp(){let{router:u}=xp("useNavigate"),s=li("useNavigate"),a=E.useRef(!1);return Cc(()=>{a.current=!0}),E.useCallback(async(f,m={})=>{xt(a.current,Ec),a.current&&(typeof f=="number"?u.navigate(f):await u.navigate(f,{fromRouteId:s,...m}))},[u,s])}var pc={};function _c(u,s,a){!s&&!pc[u]&&(pc[u]=!0,xt(!1,a))}E.memo(Np);function Np({routes:u,future:s,state:a}){return Pc(u,void 0,a,s)}function An(u){ye(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Lp({basename:u="/",children:s=null,location:a,navigationType:p="POP",navigator:f,static:m=!1}){ye(!Dr(),"You cannot render a inside another . You should never have more than one in your app.");let x=u.replace(/^\/*/,"/"),C=E.useMemo(()=>({basename:x,navigator:f,static:m,future:{}}),[x,f,m]);typeof a=="string"&&(a=Hn(a));let{pathname:S="/",search:k="",hash:T="",state:z=null,key:F="default"}=a,J=E.useMemo(()=>{let Y=Dt(S,x);return Y==null?null:{location:{pathname:Y,search:k,hash:T,state:z,key:F},navigationType:p}},[x,S,k,T,z,F,p]);return xt(J!=null,` is not able to match the URL "${S}${k}${T}" because it does not start with the basename, so the won't render anything.`),J==null?null:E.createElement(Et.Provider,{value:C},E.createElement(Fr.Provider,{children:s,value:J}))}function Tp({children:u,location:s}){return vp(ei(u),s)}function ei(u,s=[]){let a=[];return E.Children.forEach(u,(p,f)=>{if(!E.isValidElement(p))return;let m=[...s,f];if(p.type===E.Fragment){a.push.apply(a,ei(p.props.children,m));return}ye(p.type===An,`[${typeof p.type=="string"?p.type:p.type.name}] is not a component. All component children of must be a or `),ye(!p.props.index||!p.props.children,"An index route cannot have child routes.");let x={id:p.props.id||m.join("-"),caseSensitive:p.props.caseSensitive,element:p.props.element,Component:p.props.Component,index:p.props.index,path:p.props.path,loader:p.props.loader,action:p.props.action,hydrateFallbackElement:p.props.hydrateFallbackElement,HydrateFallback:p.props.HydrateFallback,errorElement:p.props.errorElement,ErrorBoundary:p.props.ErrorBoundary,hasErrorBoundary:p.props.hasErrorBoundary===!0||p.props.ErrorBoundary!=null||p.props.errorElement!=null,shouldRevalidate:p.props.shouldRevalidate,handle:p.props.handle,lazy:p.props.lazy};p.props.children&&(x.children=ei(p.props.children,m)),a.push(x)}),a}var Yl="get",Xl="application/x-www-form-urlencoded";function Zl(u){return u!=null&&typeof u.tagName=="string"}function zp(u){return Zl(u)&&u.tagName.toLowerCase()==="button"}function Fp(u){return Zl(u)&&u.tagName.toLowerCase()==="form"}function Dp(u){return Zl(u)&&u.tagName.toLowerCase()==="input"}function jp(u){return!!(u.metaKey||u.altKey||u.ctrlKey||u.shiftKey)}function Op(u,s){return u.button===0&&(!s||s==="_self")&&!jp(u)}var Kl=null;function Mp(){if(Kl===null)try{new FormData(document.createElement("form"),0),Kl=!1}catch{Kl=!0}return Kl}var Ip=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function qu(u){return u!=null&&!Ip.has(u)?(xt(!1,`"${u}" is not a valid \`encType\` for \`\`/\`\` and will default to "${Xl}"`),null):u}function Up(u,s){let a,p,f,m,x;if(Fp(u)){let C=u.getAttribute("action");p=C?Dt(C,s):null,a=u.getAttribute("method")||Yl,f=qu(u.getAttribute("enctype"))||Xl,m=new FormData(u)}else if(zp(u)||Dp(u)&&(u.type==="submit"||u.type==="image")){let C=u.form;if(C==null)throw new Error('Cannot submit a + +
); From 8d1b1fe8eb5c35a11118977fa080729941b12d0c Mon Sep 17 00:00:00 2001 From: Sofie Date: Wed, 6 Aug 2025 15:03:37 +0200 Subject: [PATCH 031/371] reorder files --- .../Subscription.jsx => dashboard/Stats.jsx} | 0 .../src/comp/dashboard/SubscriptionForm.jsx | 0 .../src/comp/dashboard/SubscriptionList.jsx | 0 frontend/src/comp/user/Form.jsx | 75 +++++++++++++++++++ frontend/src/comp/user/Userlogin.jsx | 45 +++++++++++ frontend/src/pages/Admin.jsx | 4 +- frontend/src/pages/comp/user/Form.jsx | 75 ------------------- frontend/src/pages/comp/user/Userlogin.jsx | 45 ----------- 8 files changed, 121 insertions(+), 123 deletions(-) rename frontend/src/comp/{user/Subscription.jsx => dashboard/Stats.jsx} (100%) create mode 100644 frontend/src/comp/dashboard/SubscriptionForm.jsx create mode 100644 frontend/src/comp/dashboard/SubscriptionList.jsx delete mode 100644 frontend/src/pages/comp/user/Form.jsx delete mode 100644 frontend/src/pages/comp/user/Userlogin.jsx diff --git a/frontend/src/comp/user/Subscription.jsx b/frontend/src/comp/dashboard/Stats.jsx similarity index 100% rename from frontend/src/comp/user/Subscription.jsx rename to frontend/src/comp/dashboard/Stats.jsx diff --git a/frontend/src/comp/dashboard/SubscriptionForm.jsx b/frontend/src/comp/dashboard/SubscriptionForm.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/comp/dashboard/SubscriptionList.jsx b/frontend/src/comp/dashboard/SubscriptionList.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/comp/user/Form.jsx b/frontend/src/comp/user/Form.jsx index e69de29bb2..c5f878e447 100644 --- a/frontend/src/comp/user/Form.jsx +++ b/frontend/src/comp/user/Form.jsx @@ -0,0 +1,75 @@ +import React, { useState } from 'react'; + +const Form = () => { + const [formData, setFormData] = useState({ + name: '', + email: '', + password: '', + }); + + const handleChange = (e) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + console.log('Form submitted:', formData); + alert('Account created!'); + }; + + return ( +
+

Sign Up

+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ ); +}; + +export default Form; diff --git a/frontend/src/comp/user/Userlogin.jsx b/frontend/src/comp/user/Userlogin.jsx index e69de29bb2..6749777b36 100644 --- a/frontend/src/comp/user/Userlogin.jsx +++ b/frontend/src/comp/user/Userlogin.jsx @@ -0,0 +1,45 @@ +export const Userlogin = () => { + const handleSubmit = (e) => { + e.preventDefault(); + console.log('Login form submitted'); + }; + + return ( + <> +
+

User Login

+

This is a test to see if the page loads

+ +
+
+ +
+ +
+ +
+ + +
+
+ + ); +}; diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index 8569c02ce5..546b313339 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -5,9 +5,7 @@ export const Admin = () => { <>

Admin Page

-

Welcome XX, here are your subscriptions.

- - +

Welcome XX, here are your subscriptions.

{/* Placeholder for admin name */}
diff --git a/frontend/src/pages/comp/user/Form.jsx b/frontend/src/pages/comp/user/Form.jsx deleted file mode 100644 index c5f878e447..0000000000 --- a/frontend/src/pages/comp/user/Form.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useState } from 'react'; - -const Form = () => { - const [formData, setFormData] = useState({ - name: '', - email: '', - password: '', - }); - - const handleChange = (e) => { - setFormData({ - ...formData, - [e.target.name]: e.target.value, - }); - }; - - const handleSubmit = (e) => { - e.preventDefault(); - console.log('Form submitted:', formData); - alert('Account created!'); - }; - - return ( -
-

Sign Up

- -
-
- - -
- -
- - -
- -
- - -
- - -
-
- ); -}; - -export default Form; diff --git a/frontend/src/pages/comp/user/Userlogin.jsx b/frontend/src/pages/comp/user/Userlogin.jsx deleted file mode 100644 index 6749777b36..0000000000 --- a/frontend/src/pages/comp/user/Userlogin.jsx +++ /dev/null @@ -1,45 +0,0 @@ -export const Userlogin = () => { - const handleSubmit = (e) => { - e.preventDefault(); - console.log('Login form submitted'); - }; - - return ( - <> -
-

User Login

-

This is a test to see if the page loads

- -
-
- -
- -
- -
- - -
-
- - ); -}; From f1965d4780f87d2898a3c7731f96f4fdf9e98e76 Mon Sep 17 00:00:00 2001 From: solen80a Date: Thu, 7 Aug 2025 08:53:23 +0200 Subject: [PATCH 032/371] Fixed import file path --- frontend/src/App.jsx | 6 +++--- frontend/src/comp/user/Userlogin.jsx | 1 + frontend/src/pages/Login.jsx | 3 ++- frontend/src/pages/Signup.jsx | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 892a3f6020..d9401c7ec1 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,11 +1,11 @@ -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; -import { Home } from './pages/Home'; +import { Navbar } from './comp/layout/Navbar'; import { About } from './pages/About'; import { Admin } from './pages/Admin'; +import { Home } from './pages/Home'; import { Login } from './pages/Login'; import { Signup } from './pages/Signup'; -import { Navbar } from './comp/layout/Navbar'; export const App = () => { return ( diff --git a/frontend/src/comp/user/Userlogin.jsx b/frontend/src/comp/user/Userlogin.jsx index 6749777b36..311d35b430 100644 --- a/frontend/src/comp/user/Userlogin.jsx +++ b/frontend/src/comp/user/Userlogin.jsx @@ -1,3 +1,4 @@ + export const Userlogin = () => { const handleSubmit = (e) => { e.preventDefault(); diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index e04fbbad64..0ec61ae192 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -1,4 +1,4 @@ -import { Userlogin } from './comp/user/Userlogin'; +import { Userlogin } from '../comp/user/Userlogin'; export const Login = () => { return ( @@ -7,3 +7,4 @@ export const Login = () => { ); }; + diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index d182288915..06e8499221 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -1,4 +1,4 @@ -import Form from './comp/user/Form'; +import Form from "../comp/user/Form"; export const Signup = () => { return ( From 9e8050f66af1fe9e40ed35d5cce037f0de2bc070 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Thu, 7 Aug 2025 11:13:09 +0200 Subject: [PATCH 033/371] Base setup Base setup --- Procfile => Profile | 0 backend/routes/email.js | 16 +++++++++++++ backend/sendEmail.js | 23 ++++++++++++++++++ backend/server.js | 3 ++- frontend/src/comp/EmailForm.jsx | 41 +++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-) rename Procfile => Profile (100%) create mode 100644 backend/routes/email.js create mode 100644 backend/sendEmail.js create mode 100644 frontend/src/comp/EmailForm.jsx diff --git a/Procfile b/Profile similarity index 100% rename from Procfile rename to Profile diff --git a/backend/routes/email.js b/backend/routes/email.js new file mode 100644 index 0000000000..d957c50035 --- /dev/null +++ b/backend/routes/email.js @@ -0,0 +1,16 @@ +import express from 'express'; +import sendEmail from '../sendEmail.js'; + +const router = express.Router(); + +router.post('/', async (req, res) => { + const { to, subject, text } = req.body; + try { + await sendEmail({ to, subject, text }); + res.status(200).json({ message: 'Email sent!' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +export default router; diff --git a/backend/sendEmail.js b/backend/sendEmail.js new file mode 100644 index 0000000000..60bcb5b086 --- /dev/null +++ b/backend/sendEmail.js @@ -0,0 +1,23 @@ +import dotenv from 'dotenv'; +import nodemailer from 'nodemailer'; + +dotenv.config(); + +const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, +}); + +async function sendEmail({ to, subject, text }) { + await transporter.sendMail({ + from: process.env.EMAIL_USER, + to, + subject, + text, + }); +} + +export default sendEmail; diff --git a/backend/server.js b/backend/server.js index 50ce23019a..ab818fe07d 100644 --- a/backend/server.js +++ b/backend/server.js @@ -5,6 +5,7 @@ import mongoose from 'mongoose'; import userRoutes from './routes/userRoutes'; import subscriptionRoutes from './routes/subscriptionRoutes'; +import emailRoutes from './routes/email'; const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost/final-project'; @@ -68,10 +69,10 @@ app.get('/admin', (req, res) => { }); }); - // Route connections app.use('/users', userRoutes); app.use('/subscriptions', subscriptionRoutes); +app.use('/api/email', emailRoutes); // Start the server app.listen(port, () => { diff --git a/frontend/src/comp/EmailForm.jsx b/frontend/src/comp/EmailForm.jsx new file mode 100644 index 0000000000..8da2568d8a --- /dev/null +++ b/frontend/src/comp/EmailForm.jsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; + +function EmailForm() { + const [to, setTo] = useState(''); + const [subject, setSubject] = useState(''); + const [text, setText] = useState(''); + + const handleSubmit = async (e) => { + e.preventDefault(); + const res = await fetch('/api/email', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ to, subject, text }), + }); + const data = await res.json(); + alert(data.message || data.error); + }; + + return ( +
+ setTo(e.target.value)} + placeholder='Recipient' + /> + setSubject(e.target.value)} + placeholder='Subject' + /> +