From 7cbb544fe1ac201608d9cd89279e819ee99f21b3 Mon Sep 17 00:00:00 2001 From: Rkoester47 Date: Fri, 13 Feb 2026 15:36:17 -0500 Subject: [PATCH 1/7] Set up register page --- package-lock.json | 28 +++++------- package.json | 1 + src/App.tsx | 34 +++++++------- src/components/AuthGuard.jsx | 26 +++++++++++ src/context/AuthContext.jsx | 86 ++++++++++++++++++++++++++++++++++++ src/css/register.css | 0 src/pages/Register.tsx | 84 +++++++++++++++++++++++++++++++---- 7 files changed, 217 insertions(+), 42 deletions(-) create mode 100644 src/components/AuthGuard.jsx create mode 100644 src/context/AuthContext.jsx create mode 100644 src/css/register.css diff --git a/package-lock.json b/package-lock.json index 2741a99..41370f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@heroicons/react": "^2.2.0", + "lucide-react": "^0.564.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0" @@ -62,7 +63,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -350,7 +350,6 @@ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -392,7 +391,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -433,7 +431,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -1698,7 +1695,6 @@ "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1709,7 +1705,6 @@ "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1769,7 +1764,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -2021,7 +2015,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2163,7 +2156,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2520,7 +2512,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3369,6 +3360,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.564.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.564.0.tgz", + "integrity": "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/mathml-tag-names": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", @@ -3615,7 +3615,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3643,7 +3642,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3686,7 +3684,6 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3777,7 +3774,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3787,7 +3783,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4445,7 +4440,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4552,7 +4546,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -4688,7 +4681,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index eb24117..1a7a2d0 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@heroicons/react": "^2.2.0", + "lucide-react": "^0.564.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0" diff --git a/src/App.tsx b/src/App.tsx index 1226399..6d53dc9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,25 +9,29 @@ import Roster from './pages/Roster.tsx'; import Notifications from './pages/Notifications.tsx'; import Profile from './pages/Profile.tsx'; import Settings from './pages/Settings.tsx'; +/*@ts-ignore*/ +import { AuthProvider } from './context/AuthContext' function App() { return ( <> - - - } /> - } /> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - + + + + } /> + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); } diff --git a/src/components/AuthGuard.jsx b/src/components/AuthGuard.jsx new file mode 100644 index 0000000..da3dc17 --- /dev/null +++ b/src/components/AuthGuard.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Navigate, useLocation } from 'react-router-dom'; +/*@ts-ignore*/ +import { useAuth } from '../context/AuthContext'; +import { Activity } from 'lucide-react'; + +const AuthGuard = ({ children }) => { + const { user, loading } = useAuth(); + const location = useLocation(); + + if (loading) { + return ( +
+ +
+ ); + } + + if (!user) { + return ; + } + + return children; +}; + +export default AuthGuard; \ No newline at end of file diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx new file mode 100644 index 0000000..d3224f6 --- /dev/null +++ b/src/context/AuthContext.jsx @@ -0,0 +1,86 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; + +const AuthContext = createContext(null); + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchMe = async () => { + try { + setLoading(true); + const response = await fetch('https://api.capyrpi.org/v1/auth/me', { + headers: { + 'Accept': 'application/json', + }, + }); + + if (response.ok) { + const data = await response.json(); + setUser(data); + } else { + setUser(null); + } + } catch (err) { + console.error('Failed to fetch user:', err); + setError(err.message); + setUser(null); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchMe(); + }, []); + + const login = () => { + window.open('https://api.capyrpi.org/v1/auth/google', '_blank'); + + // Start polling to check if the user has authenticated in the other window + const pollInterval = setInterval(async () => { + try { + const response = await fetch('https://api.capyrpi.org/v1/auth/me', { + headers: { 'Accept': 'application/json' }, + }); + if (response.ok) { + const data = await response.json(); + setUser(data); + clearInterval(pollInterval); + } + } catch (err) { + console.error('Polling failed:', err); + } + }, 3000); + + // Stop polling after 2 minutes anyway + setTimeout(() => { + clearInterval(pollInterval); + }, 120000); + }; + + const logout = async () => { + try { + await fetch('https://api.capyrpi.org/v1/auth/logout', { method: 'POST' }); + setUser(null); + window.location.href = '/app/'; + } catch (err) { + console.error('Logout failed:', err); + } + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; \ No newline at end of file diff --git a/src/css/register.css b/src/css/register.css new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 20ed1ab..30c103c 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -1,14 +1,80 @@ -import { useNavigate } from 'react-router-dom'; +import { Navigate, useLocation } from 'react-router-dom'; +/*@ts-ignore*/ +import { useAuth } from '../context/AuthContext'; +import { Shield, Sparkles } from 'lucide-react'; -export default function Register() { - const navigate = useNavigate(); +const Login = () => { + const { user, login } = useAuth(); + const location = useLocation(); + + // If the user is already authenticated, redirect them to the dashboard + // or to the page they were trying to access before being redirected to login + const from = location.state?.from?.pathname || "/becapy"; + + if (user) { + return ; + } return ( - <> -

Register

-
- +
+
+
+
+ +
+

Be CAPY

+
+ +
+ + +
+
+ SECURE ACCESS +
+
+
+ +
+ + Interactive dashboard for RPI students +
- +
); -} +}; + +export default Login; \ No newline at end of file From 4859d375c0fe6e3c8e449caec58bbe1dc58adf5e Mon Sep 17 00:00:00 2001 From: Rkoester47 Date: Fri, 13 Feb 2026 15:45:04 -0500 Subject: [PATCH 2/7] Added Microsoft authentication button --- src/components/Login.tsx | 0 src/pages/Register.tsx | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 src/components/Login.tsx diff --git a/src/components/Login.tsx b/src/components/Login.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 30c103c..7e45d82 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -60,6 +60,20 @@ const Login = () => { Google Continue with Google +
From 5dd0aff0698d2a0cabc8ec2eb2f710164f69fec1 Mon Sep 17 00:00:00 2001 From: Rkoester47 Date: Sun, 15 Feb 2026 22:49:01 -0500 Subject: [PATCH 3/7] Mock Login Pop Up --- src/App.tsx | 4 +- src/components/AuthGuard.jsx | 26 ----- src/components/Header.tsx | 33 ++++-- src/components/Login.tsx | 0 src/components/LoginPopup.tsx | 67 +++++++++++ src/context/AuthContext.jsx | 86 -------------- src/context/AuthContext.tsx | 154 ++++++++++++++++++++++++++ src/css/login.css | 62 +++++++++++ src/css/register.css | 0 src/pages/{Register.tsx => Login.tsx} | 3 - 10 files changed, 307 insertions(+), 128 deletions(-) delete mode 100644 src/components/AuthGuard.jsx delete mode 100644 src/components/Login.tsx create mode 100644 src/components/LoginPopup.tsx delete mode 100644 src/context/AuthContext.jsx create mode 100644 src/context/AuthContext.tsx create mode 100644 src/css/login.css delete mode 100644 src/css/register.css rename src/pages/{Register.tsx => Login.tsx} (95%) diff --git a/src/App.tsx b/src/App.tsx index 6d53dc9..2da5c92 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,6 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'; import AppLayout from './components/AppLayout.tsx'; import Home from './pages/Home.tsx'; -import Register from './pages/Register.tsx'; import Dashboard from './pages/Dashboard.tsx'; import Events from './pages/Events.tsx'; import Calendar from './pages/Calendar.tsx'; @@ -10,7 +9,7 @@ import Notifications from './pages/Notifications.tsx'; import Profile from './pages/Profile.tsx'; import Settings from './pages/Settings.tsx'; /*@ts-ignore*/ -import { AuthProvider } from './context/AuthContext' +import { AuthProvider } from './context/AuthContext.tsx' function App() { return ( @@ -19,7 +18,6 @@ function App() { } /> - } /> }> } /> } /> diff --git a/src/components/AuthGuard.jsx b/src/components/AuthGuard.jsx deleted file mode 100644 index da3dc17..0000000 --- a/src/components/AuthGuard.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { Navigate, useLocation } from 'react-router-dom'; -/*@ts-ignore*/ -import { useAuth } from '../context/AuthContext'; -import { Activity } from 'lucide-react'; - -const AuthGuard = ({ children }) => { - const { user, loading } = useAuth(); - const location = useLocation(); - - if (loading) { - return ( -
- -
- ); - } - - if (!user) { - return ; - } - - return children; -}; - -export default AuthGuard; \ No newline at end of file diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 7977a8d..8cc8dbe 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,19 +1,32 @@ import '../css/header.css'; import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; +import LoginPopup from '../components/LoginPopup'; export default function Header() { const navigate = useNavigate(); + + const [showLogin, setShowLogin] = useState(false); + return ( -
-

CAPY Web UI

- + setShowLogin(false)} + /> + ); } diff --git a/src/components/Login.tsx b/src/components/Login.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/LoginPopup.tsx b/src/components/LoginPopup.tsx new file mode 100644 index 0000000..c6ab1ca --- /dev/null +++ b/src/components/LoginPopup.tsx @@ -0,0 +1,67 @@ +import React, { useEffect } from "react"; +import { useAuth } from "../context/AuthContext"; +import "../css/login.css"; + +type Props = { + isOpen: boolean; + onClose: () => void; +}; + +export default function LoginPopup({ isOpen, onClose }: Props) { + + const { login, user } = useAuth(); + + useEffect(() => { + if (user && isOpen) { + onClose(); + } + }, [user, isOpen, onClose]); + + if (!isOpen) return null; + + const handleOverlayClick = () => { + onClose(); + }; + + const handlePopupClick = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + return ( +
+ +
+ + + +

+ Register or Login +

+ + + + + +
+ +
+ ); +} \ No newline at end of file diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx deleted file mode 100644 index d3224f6..0000000 --- a/src/context/AuthContext.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { createContext, useContext, useState, useEffect } from 'react'; - -const AuthContext = createContext(null); - -export const AuthProvider = ({ children }) => { - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - const fetchMe = async () => { - try { - setLoading(true); - const response = await fetch('https://api.capyrpi.org/v1/auth/me', { - headers: { - 'Accept': 'application/json', - }, - }); - - if (response.ok) { - const data = await response.json(); - setUser(data); - } else { - setUser(null); - } - } catch (err) { - console.error('Failed to fetch user:', err); - setError(err.message); - setUser(null); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchMe(); - }, []); - - const login = () => { - window.open('https://api.capyrpi.org/v1/auth/google', '_blank'); - - // Start polling to check if the user has authenticated in the other window - const pollInterval = setInterval(async () => { - try { - const response = await fetch('https://api.capyrpi.org/v1/auth/me', { - headers: { 'Accept': 'application/json' }, - }); - if (response.ok) { - const data = await response.json(); - setUser(data); - clearInterval(pollInterval); - } - } catch (err) { - console.error('Polling failed:', err); - } - }, 3000); - - // Stop polling after 2 minutes anyway - setTimeout(() => { - clearInterval(pollInterval); - }, 120000); - }; - - const logout = async () => { - try { - await fetch('https://api.capyrpi.org/v1/auth/logout', { method: 'POST' }); - setUser(null); - window.location.href = '/app/'; - } catch (err) { - console.error('Logout failed:', err); - } - }; - - return ( - - {children} - - ); -}; - -export const useAuth = () => { - const context = useContext(AuthContext); - if (!context) { - throw new Error('useAuth must be used within an AuthProvider'); - } - return context; -}; \ No newline at end of file diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx new file mode 100644 index 0000000..9f68c11 --- /dev/null +++ b/src/context/AuthContext.tsx @@ -0,0 +1,154 @@ +import React, { createContext, useContext, useState, type ReactNode } from 'react'; + +type User = { + id?: string; + name?: string; + email?: string; +}; + +type AuthContextType = { + user: User | null; + loading: boolean; + error: string | null; + login: () => void; + logout: () => Promise; + refreshUser: () => Promise; +}; + +type AuthProviderProps = { + children: ReactNode; +}; + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC = ({ children }) => { + + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchMe = async (): Promise => { + try { + setLoading(true); + + const response = await fetch( + 'https://api.capyrpi.org/v1/auth/me', + { + headers: { + Accept: 'application/json' + } + } + ); + + if (response.ok) { + const data: User = await response.json(); + setUser(data); + } else { + setUser(null); + } + + } catch (err: unknown) { + if (err instanceof Error) { + console.error('Failed to fetch user:', err); + setError(err.message); + } else { + console.error('Unknown error:', err); + setError('Unknown error'); + } + + setUser(null); + + } finally { + setLoading(false); + } + }; + + const login = (): void => { + + window.open('https://api.capyrpi.org/v1/auth/google', '_blank'); + + const pollInterval = setInterval(async () => { + + try { + + const response = await fetch( + 'https://api.capyrpi.org/v1/auth/me', + { + headers: { + Accept: 'application/json' + } + } + ); + + if (response.ok) { + + const data: User = await response.json(); + setUser(data); + clearInterval(pollInterval); + + } + + } catch (err: unknown) { + + if (err instanceof Error) { + console.error('Polling failed:', err.message); + } + } + }, 3000); + + setTimeout(() => { + clearInterval(pollInterval); + }, 120000); + }; + const logout = async (): Promise => { + + try { + + await fetch( + 'https://api.capyrpi.org/v1/auth/logout', + { + method: 'POST' + } + ); + + setUser(null); + + window.location.href = '/app/'; + + } catch (err: unknown) { + + if (err instanceof Error) { + console.error('Logout failed:', err.message); + } + + } + + }; + const value: AuthContextType = { + user, + loading, + error, + login, + logout, + refreshUser: fetchMe + }; + + return ( + + {children} + + ); +}; + +export const useAuth = (): AuthContextType => { + + const context = useContext(AuthContext); + + if (!context) { + throw new Error( + 'useAuth must be used within an AuthProvider' + ); + } + + return context; +}; \ No newline at end of file diff --git a/src/css/login.css b/src/css/login.css new file mode 100644 index 0000000..c2ea29e --- /dev/null +++ b/src/css/login.css @@ -0,0 +1,62 @@ +.popup-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--standard-text); + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; +} + +.popup-container { + position: relative; + background-color: var(--standard-white); + border: 1px var(--standard-text); + border-radius: 8px; + padding: 24px; + width: 320px; + display: flex; + flex-direction: column; + gap: 12px; + box-shadow: 0 4px 12px var(--standard-text); +} + +.popup-title { + margin: 0; + margin-bottom: 10px; + text-align: center; + color: var(--standard-black); +} + +.popup-button { + background-color: var(--standard-text); + border: var(--standard-text); + color: var(--standard-white); + padding: 10px; + border-radius: 8px; + cursor: pointer; + font-size: 14px; +} + +.popup-button:hover { + background-color: #f0f0f0; +} + +.popup-close-x { + position: absolute; + top: 8px; + right: 8px; + background: none; + border: none; + font-size: 20px; + font-weight: bold; + cursor: pointer; + color: var(--standard-text); +} + +.popup-close-x:hover { + color: var(--standard-black); +} diff --git a/src/css/register.css b/src/css/register.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/Register.tsx b/src/pages/Login.tsx similarity index 95% rename from src/pages/Register.tsx rename to src/pages/Login.tsx index 7e45d82..69ea195 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Login.tsx @@ -6,9 +6,6 @@ import { Shield, Sparkles } from 'lucide-react'; const Login = () => { const { user, login } = useAuth(); const location = useLocation(); - - // If the user is already authenticated, redirect them to the dashboard - // or to the page they were trying to access before being redirected to login const from = location.state?.from?.pathname || "/becapy"; if (user) { From 5220827e55b610ab5f38448ec6e7f699ca5414b5 Mon Sep 17 00:00:00 2001 From: Rkoester47 Date: Mon, 16 Feb 2026 16:59:07 -0500 Subject: [PATCH 4/7] Moved register button out of Header Moved the button that triggers the login popup to the sidebar temporarily, as Header has been deleted on main. --- src/components/Sidebar.tsx | 48 ++++++++++++++++++++++++-------------- src/css/sidebar.css | 4 ++++ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 0d2c64e..3f22526 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,5 +1,8 @@ import '../css/sidebar.css'; import SidebarLink from './SidebarLink.tsx'; +import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; +import LoginPopup from '../components/LoginPopup'; // Icon Imports import { @@ -19,25 +22,34 @@ type SidebarProps = { }; export default function Sidebar({ collapsed, onToggle }: SidebarProps) { + const [showLogin, setShowLogin] = useState(false); + return ( -
-
-
- + <> +
+
+
+ +
+
+ } nav="/dashboard" /> + } nav="/events" /> + } nav="/calendar" /> + } nav="/roster" /> + } nav="/notifications" /> +
+
+ } nav="/profile" /> + } nav="/settings" /> +
+ setShowLogin(true)}> Register
-
- } nav="/dashboard" /> - } nav="/events" /> - } nav="/calendar" /> - } nav="/roster" /> - } nav="/notifications" /> -
-
- } nav="/profile" /> - } nav="/settings" /> -
-
+ setShowLogin(false)} + /> + ); } diff --git a/src/css/sidebar.css b/src/css/sidebar.css index c116325..2c89ae9 100644 --- a/src/css/sidebar.css +++ b/src/css/sidebar.css @@ -117,3 +117,7 @@ width: 100%; height: 100%; } + +.register-btn { + color: black; +} \ No newline at end of file From b68d902d3f17af5c10e6e835adfe796d14f775d5 Mon Sep 17 00:00:00 2001 From: Rkoester47 Date: Thu, 19 Feb 2026 21:19:51 -0500 Subject: [PATCH 5/7] Resolved conflicts I moved the mock login popup into the sidebar as asked, since Header got deleted on main. We will need to decide where we actually want this to go. --- src/components/sidebar/Sidebar.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx index dd9fb85..18624dc 100644 --- a/src/components/sidebar/Sidebar.tsx +++ b/src/components/sidebar/Sidebar.tsx @@ -1,10 +1,10 @@ import '../../css/sidebar.css'; +import '../../css/login.css'; import SidebarGroup from './SidebarGroup.tsx'; import SidebarLink from './SidebarLink.tsx'; -import { useNavigate } from 'react-router-dom'; +//import { type useNavigate } from 'react-router-dom'; import { useState } from 'react'; -/*@ts-ignore*/ -import LoginPopup from '../components/LoginPopup'; +import LoginPopup from "../LoginPopup.tsx"; import SidebarProfile from './SidebarProfile.tsx'; import SidebarWorkspace from './SidebarWorkspace.tsx'; @@ -51,6 +51,10 @@ export default function Sidebar({ collapsed, onToggle }: SidebarProps) { } nav="/settings" />
setShowLogin(true)}> Register + setShowLogin(false)} + /> } nav="/dashboard" /> } collapsed={collapsed}> @@ -75,10 +79,7 @@ export default function Sidebar({ collapsed, onToggle }: SidebarProps) { />
- setShowLogin(false)} - /> + ); From 93a892d2d41b1d7e51e5173612822780aa65a1a9 Mon Sep 17 00:00:00 2001 From: Rkoester47 Date: Fri, 20 Feb 2026 15:04:00 -0500 Subject: [PATCH 6/7] Updated Google / Microsoft links --- src/components/LoginPopup.tsx | 4 ++-- src/context/AuthContext.tsx | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/LoginPopup.tsx b/src/components/LoginPopup.tsx index c6ab1ca..756a55d 100644 --- a/src/components/LoginPopup.tsx +++ b/src/components/LoginPopup.tsx @@ -48,14 +48,14 @@ export default function LoginPopup({ isOpen, onClose }: Props) { diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 9f68c11..e26b125 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -10,7 +10,7 @@ type AuthContextType = { user: User | null; loading: boolean; error: string | null; - login: () => void; + login: (provider: "google" | "microsoft") => void; logout: () => Promise; refreshUser: () => Promise; }; @@ -63,9 +63,14 @@ export const AuthProvider: React.FC = ({ children }) => { } }; - const login = (): void => { + const login = (provider: "google" | "microsoft"): void => { - window.open('https://api.capyrpi.org/v1/auth/google', '_blank'); + const url = + provider === "google" + ? "https://api.capyrpi.org/v1/auth/google" + : "https://api.capyrpi.org/v1/auth/microsoft"; + + window.open(url, "_blank"); const pollInterval = setInterval(async () => { @@ -93,7 +98,9 @@ export const AuthProvider: React.FC = ({ children }) => { if (err instanceof Error) { console.error('Polling failed:', err.message); } + } + }, 3000); setTimeout(() => { From 1ef674ecce0731af3f38928d9d1b4f35584ea36e Mon Sep 17 00:00:00 2001 From: Rkoester47 Date: Fri, 20 Feb 2026 15:31:17 -0500 Subject: [PATCH 7/7] Lint fixes --- src/App.tsx | 3 +- src/components/LoginPopup.tsx | 37 +++------ src/components/sidebar/Sidebar.tsx | 72 ++++++++++-------- src/context/AuthContext.tsx | 81 ++++++-------------- src/css/login.css | 7 +- src/css/sidebar.css | 1 - src/pages/Login.tsx | 116 ++++++++++++++++++++--------- 7 files changed, 157 insertions(+), 160 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 5409786..62582dc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,8 +10,7 @@ import Roster from './pages/Roster.tsx'; import Notifications from './pages/Notifications.tsx'; import Profile from './pages/Profile.tsx'; import Settings from './pages/Settings.tsx'; -/*@ts-ignore*/ -import { AuthProvider } from './context/AuthContext.tsx' +import { AuthProvider } from './context/AuthContext.tsx'; import HelpCenter from './pages/HelpCenter.tsx'; function App() { diff --git a/src/components/LoginPopup.tsx b/src/components/LoginPopup.tsx index 756a55d..0500910 100644 --- a/src/components/LoginPopup.tsx +++ b/src/components/LoginPopup.tsx @@ -1,6 +1,6 @@ -import React, { useEffect } from "react"; -import { useAuth } from "../context/AuthContext"; -import "../css/login.css"; +import React, { useEffect } from 'react'; +import { useAuth } from '../context/AuthContext'; +import '../css/login.css'; type Props = { isOpen: boolean; @@ -8,7 +8,6 @@ type Props = { }; export default function LoginPopup({ isOpen, onClose }: Props) { - const { login, user } = useAuth(); useEffect(() => { @@ -29,39 +28,21 @@ export default function LoginPopup({ isOpen, onClose }: Props) { return (
- -
- - -

- Register or Login -

+

Register or Login

- - -
-
); -} \ No newline at end of file +} diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx index 18624dc..757f806 100644 --- a/src/components/sidebar/Sidebar.tsx +++ b/src/components/sidebar/Sidebar.tsx @@ -4,7 +4,7 @@ import SidebarGroup from './SidebarGroup.tsx'; import SidebarLink from './SidebarLink.tsx'; //import { type useNavigate } from 'react-router-dom'; import { useState } from 'react'; -import LoginPopup from "../LoginPopup.tsx"; +import LoginPopup from '../LoginPopup.tsx'; import SidebarProfile from './SidebarProfile.tsx'; import SidebarWorkspace from './SidebarWorkspace.tsx'; @@ -29,7 +29,7 @@ type SidebarProps = { export default function Sidebar({ collapsed, onToggle }: SidebarProps) { const [showLogin, setShowLogin] = useState(false); - + return ( <>
@@ -44,43 +44,53 @@ export default function Sidebar({ collapsed, onToggle }: SidebarProps) { } nav="/events" /> } nav="/calendar" /> } nav="/roster" /> - } nav="/notifications" /> + } + nav="/notifications" + />
} nav="/profile" /> } nav="/settings" />
- setShowLogin(true)}> Register - setShowLogin(false)} - /> + setShowLogin(true)}> + {' '} + Register{' '} + + setShowLogin(false)} /> - } nav="/dashboard" /> - } collapsed={collapsed}> - } nav="/events/create" /> + } nav="/dashboard" /> + } collapsed={collapsed}> + } nav="/events/create" /> + } + nav="/events/history" + /> + + } nav="/calendar" /> + } nav="/roster" /> + } + nav="/notifications" + /> + + } - nav="/events/history" + label="Help Center" + icon={} + nav="/help" + /> + } nav="/settings" /> + } + name="Kevin Smith" + email="smithk@rpi.edu" /> - - } nav="/calendar" /> - } nav="/roster" /> - } nav="/notifications" /> - - - } nav="/help" /> - } nav="/settings" /> - } - name="Kevin Smith" - email="smithk@rpi.edu" - /> - -
- + +
- ); } diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index e26b125..e2eb7fb 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components*/ import React, { createContext, useContext, useState, type ReactNode } from 'react'; type User = { @@ -10,7 +11,7 @@ type AuthContextType = { user: User | null; loading: boolean; error: string | null; - login: (provider: "google" | "microsoft") => void; + login: (provider: 'google' | 'microsoft') => void; logout: () => Promise; refreshUser: () => Promise; }; @@ -22,7 +23,6 @@ type AuthProviderProps = { const AuthContext = createContext(undefined); export const AuthProvider: React.FC = ({ children }) => { - const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -31,14 +31,11 @@ export const AuthProvider: React.FC = ({ children }) => { try { setLoading(true); - const response = await fetch( - 'https://api.capyrpi.org/v1/auth/me', - { - headers: { - Accept: 'application/json' - } - } - ); + const response = await fetch('https://api.capyrpi.org/v1/auth/me', { + headers: { + Accept: 'application/json', + }, + }); if (response.ok) { const data: User = await response.json(); @@ -46,7 +43,6 @@ export const AuthProvider: React.FC = ({ children }) => { } else { setUser(null); } - } catch (err: unknown) { if (err instanceof Error) { console.error('Failed to fetch user:', err); @@ -57,50 +53,37 @@ export const AuthProvider: React.FC = ({ children }) => { } setUser(null); - } finally { setLoading(false); } }; - const login = (provider: "google" | "microsoft"): void => { - + const login = (provider: 'google' | 'microsoft'): void => { const url = - provider === "google" - ? "https://api.capyrpi.org/v1/auth/google" - : "https://api.capyrpi.org/v1/auth/microsoft"; + provider === 'google' + ? 'https://api.capyrpi.org/v1/auth/google' + : 'https://api.capyrpi.org/v1/auth/microsoft'; - window.open(url, "_blank"); + window.open(url, '_blank'); const pollInterval = setInterval(async () => { - try { - - const response = await fetch( - 'https://api.capyrpi.org/v1/auth/me', - { - headers: { - Accept: 'application/json' - } - } - ); + const response = await fetch('https://api.capyrpi.org/v1/auth/me', { + headers: { + Accept: 'application/json', + }, + }); if (response.ok) { - const data: User = await response.json(); setUser(data); clearInterval(pollInterval); - } - } catch (err: unknown) { - if (err instanceof Error) { console.error('Polling failed:', err.message); } - } - }, 3000); setTimeout(() => { @@ -108,28 +91,19 @@ export const AuthProvider: React.FC = ({ children }) => { }, 120000); }; const logout = async (): Promise => { - try { - - await fetch( - 'https://api.capyrpi.org/v1/auth/logout', - { - method: 'POST' - } - ); + await fetch('https://api.capyrpi.org/v1/auth/logout', { + method: 'POST', + }); setUser(null); window.location.href = '/app/'; - } catch (err: unknown) { - if (err instanceof Error) { console.error('Logout failed:', err.message); } - } - }; const value: AuthContextType = { user, @@ -137,25 +111,18 @@ export const AuthProvider: React.FC = ({ children }) => { error, login, logout, - refreshUser: fetchMe + refreshUser: fetchMe, }; - return ( - - {children} - - ); + return {children}; }; export const useAuth = (): AuthContextType => { - const context = useContext(AuthContext); if (!context) { - throw new Error( - 'useAuth must be used within an AuthProvider' - ); + throw new Error('useAuth must be used within an AuthProvider'); } return context; -}; \ No newline at end of file +}; diff --git a/src/css/login.css b/src/css/login.css index c2ea29e..218bcfa 100644 --- a/src/css/login.css +++ b/src/css/login.css @@ -1,9 +1,6 @@ .popup-overlay { position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; background-color: var(--standard-text); display: flex; justify-content: center; @@ -34,7 +31,7 @@ .popup-button { background-color: var(--standard-text); border: var(--standard-text); - color: var(--standard-white); + color: var(--standard-white); padding: 10px; border-radius: 8px; cursor: pointer; diff --git a/src/css/sidebar.css b/src/css/sidebar.css index 31f7087..061b9e5 100644 --- a/src/css/sidebar.css +++ b/src/css/sidebar.css @@ -206,5 +206,4 @@ .register-btn { color: black; - } diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 69ea195..3decd95 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,85 +1,129 @@ import { Navigate, useLocation } from 'react-router-dom'; -/*@ts-ignore*/ import { useAuth } from '../context/AuthContext'; import { Shield, Sparkles } from 'lucide-react'; const Login = () => { const { user, login } = useAuth(); const location = useLocation(); - const from = location.state?.from?.pathname || "/becapy"; + const from = location.state?.from?.pathname || '/becapy'; if (user) { return ; } return ( -
-
-
-
+ alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '100vh', + }} + > +
+
+
-

Be CAPY

+

+ Be CAPY +

-
-
+
+
SECURE ACCESS -
+
-
+
Interactive dashboard for RPI students
@@ -88,4 +132,4 @@ const Login = () => { ); }; -export default Login; \ No newline at end of file +export default Login;