@@ -88,7 +77,7 @@ export const Profile: React.FC = () => {
{profile.faculty && (
-
+
Faculty
{profile.faculty}
@@ -99,12 +88,24 @@ export const Profile: React.FC = () => {
)}
-
Other Services
+
Quick Access
-
-
-
-
+
+
+ Schedule
+
+
+
+ Grades
+
+
+
+ Messages
+
+
+
+ Settings
+
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
new file mode 100644
index 0000000..86b476b
--- /dev/null
+++ b/src/pages/Settings.tsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import { useStore } from '../store/useStore';
+import { Palette, Moon, Sun, Monitor, LogOut } from 'lucide-react';
+import { useNavigate } from 'react-router-dom';
+
+const colors = [
+ { name: 'Blue', value: '#0061a4' },
+ { name: 'Red', value: '#ba1a1a' },
+ { name: 'Green', value: '#006d3a' },
+ { name: 'Purple', value: '#7c4dff' },
+ { name: 'Orange', value: '#8b5000' },
+];
+
+export const Settings: React.FC = () => {
+ const { theme, setTheme, seedColor, setSeedColor, reset } = useStore();
+ const navigate = useNavigate();
+
+ const handleLogout = () => {
+ reset();
+ navigate('/login');
+ };
+
+ return (
+
+
Settings
+
+
+
+
+ Theme
+
+
+
+
+
+
+
+
+
+
+
+ Accent Color
+
+
+ {colors.map((c) => (
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/store/useStore.ts b/src/store/useStore.ts
index 2ec3a38..e3a4d6b 100644
--- a/src/store/useStore.ts
+++ b/src/store/useStore.ts
@@ -5,9 +5,13 @@ interface AppState {
apiDomain: string;
apiKey: string;
isMockMode: boolean;
+ theme: 'system' | 'light' | 'dark';
+ seedColor: string;
setApiDomain: (domain: string) => void;
setApiKey: (key: string) => void;
setMockMode: (isMock: boolean) => void;
+ setTheme: (theme: 'system' | 'light' | 'dark') => void;
+ setSeedColor: (color: string) => void;
reset: () => void;
}
@@ -17,9 +21,13 @@ export const useStore = create
()(
apiDomain: import.meta.env.VITE_API_DOMAIN || '',
apiKey: '',
isMockMode: import.meta.env.VITE_MOCK_MODE === 'true',
+ theme: 'system',
+ seedColor: '#0061a4', // Default M3 Blue
setApiDomain: (apiDomain) => set({ apiDomain }),
setApiKey: (apiKey) => set({ apiKey }),
setMockMode: (isMockMode) => set({ isMockMode }),
+ setTheme: (theme) => set({ theme }),
+ setSeedColor: (seedColor) => set({ seedColor }),
reset: () => set({ apiKey: '' }),
}),
{
diff --git a/src/styles/globals.css b/src/styles/globals.css
index c803f68..20efa98 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -13,25 +13,26 @@
--md-sys-color-on-surface-variant: #43474e;
--md-sys-color-outline: #73777f;
--md-sys-color-error: #ba1a1a;
+
+ --nav-width: 80px;
+ --bottom-nav-height: 80px;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --md-sys-color-primary: #9ecaff;
- --md-sys-color-on-primary: #003258;
- --md-sys-color-primary-container: #00497d;
- --md-sys-color-on-primary-container: #d1e4ff;
- --md-sys-color-secondary: #bbc7db;
- --md-sys-color-on-secondary: #253140;
- --md-sys-color-secondary-container: #3b4858;
- --md-sys-color-on-secondary-container: #d7e3f7;
- --md-sys-color-surface: #1a1c1e;
- --md-sys-color-on-surface: #e2e2e6;
- --md-sys-color-surface-variant: #43474e;
- --md-sys-color-on-surface-variant: #c3c7cf;
- --md-sys-color-outline: #8d9199;
- --md-sys-color-error: #ffb4ab;
- }
+:root.dark {
+ --md-sys-color-primary: #9ecaff;
+ --md-sys-color-on-primary: #003258;
+ --md-sys-color-primary-container: #00497d;
+ --md-sys-color-on-primary-container: #d1e4ff;
+ --md-sys-color-secondary: #bbc7db;
+ --md-sys-color-on-secondary: #253140;
+ --md-sys-color-secondary-container: #3b4858;
+ --md-sys-color-on-secondary-container: #d7e3f7;
+ --md-sys-color-surface: #1a1c1e;
+ --md-sys-color-on-surface: #e2e2e6;
+ --md-sys-color-surface-variant: #43474e;
+ --md-sys-color-on-surface-variant: #c3c7cf;
+ --md-sys-color-outline: #8d9199;
+ --md-sys-color-error: #ffb4ab;
}
* {
@@ -48,14 +49,7 @@ body {
transition: background-color 0.3s, color 0.3s;
}
-button {
- cursor: pointer;
- font-family: inherit;
-}
-
-input {
- font-family: inherit;
-}
+/* Material 3 Components */
.m3-button {
display: inline-flex;
@@ -69,6 +63,8 @@ input {
font-size: 14px;
letter-spacing: 0.1px;
transition: box-shadow 0.2s, background-color 0.2s;
+ background-color: transparent;
+ color: var(--md-sys-color-primary);
}
.m3-button-filled {
@@ -105,3 +101,97 @@ input {
outline: none;
border-bottom: 2px solid var(--md-sys-color-primary);
}
+
+/* Layout and Navigation */
+
+.app-container {
+ display: flex;
+ min-height: 100vh;
+}
+
+.main-content {
+ flex: 1;
+ padding-bottom: var(--bottom-nav-height);
+}
+
+@media (min-width: 600px) {
+ .main-content {
+ padding-bottom: 0;
+ margin-left: var(--nav-width);
+ }
+}
+
+.navigation-rail {
+ position: fixed;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: var(--nav-width);
+ background-color: var(--md-sys-color-surface);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16px 0;
+ border-right: 1px solid var(--md-sys-color-outline);
+ z-index: 100;
+}
+
+.navigation-bar {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: var(--bottom-nav-height);
+ background-color: var(--md-sys-color-surface-variant);
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ padding: 0 8px;
+ z-index: 100;
+}
+
+.nav-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-decoration: none;
+ color: var(--md-sys-color-on-surface-variant);
+ gap: 4px;
+ flex: 1;
+ height: 100%;
+ border-radius: 16px;
+ transition: background-color 0.2s;
+}
+
+.nav-item-icon-wrapper {
+ width: 64px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 16px;
+ transition: background-color 0.2s;
+}
+
+.nav-item.active .nav-item-icon-wrapper {
+ background-color: var(--md-sys-color-secondary-container);
+ color: var(--md-sys-color-on-secondary-container);
+}
+
+.nav-item-label {
+ font-size: 12px;
+ font-weight: 500;
+}
+
+@media (min-width: 600px) {
+ .navigation-bar {
+ display: none;
+ }
+}
+
+@media (max-width: 599px) {
+ .navigation-rail {
+ display: none;
+ }
+}