Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 94 additions & 9 deletions frontend/src/components/AppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { Dropdown } from 'react-bootstrap';
import { useAuth } from '../contexts/AuthContext';
Expand Down Expand Up @@ -170,6 +170,42 @@
<polyline points="6 9 12 15 18 9" />
</svg>
),
Sun: () => (
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
),
Moon: () => (
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
),
};

// Simple MS Logo
Expand Down Expand Up @@ -205,6 +241,28 @@
const navigate = useNavigate();
const location = useLocation();
const [showProfileModal, setShowProfileModal] = useState(false);
const [isDarkMode, setIsDarkMode] = useState(() => {
// Check localStorage first, then system preference
const stored = localStorage.getItem('theme');
if (stored) return stored === 'dark';
return window.matchMedia('(prefers-color-scheme: dark)').matches;
});

// Apply theme to document
useEffect(() => {
const root = document.documentElement;
if (isDarkMode) {
root.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
} else {
root.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'light');
}
}, [isDarkMode]);

const toggleDarkMode = () => {
setIsDarkMode((prev) => !prev);
};

const isActive = (path: string) => location.pathname === path;
const isAdmin = user?.role === 'admin';
Expand All @@ -214,7 +272,7 @@
navigate('/login');
};

const handleProfileUpdate = (updatedUser: any) => {

Check warning on line 275 in frontend/src/components/AppShell.tsx

View workflow job for this annotation

GitHub Actions / Frontend Tests

Unexpected any. Specify a different type
setUser(updatedUser);
};

Expand Down Expand Up @@ -390,30 +448,41 @@
<Dropdown.Menu
style={{
borderRadius: '14px',
boxShadow: '0 8px 24px rgba(0,0,0,0.12)',
border: '1px solid #E5E7EB',
boxShadow: 'var(--ms-shadow-md, 0 8px 24px rgba(0,0,0,0.12))',
border: '1px solid var(--ms-border, #E5E7EB)',
padding: '8px',
background: 'var(--ms-card-bg, #ffffff)',
}}
>
<div
style={{
padding: '12px 16px',
borderBottom: '1px solid #E5E7EB',
borderBottom: '1px solid var(--ms-border, #E5E7EB)',
marginBottom: '8px',
}}
>
<div style={{ fontWeight: 600, color: '#000' }}>{user?.name}</div>
<div style={{ fontSize: '0.8rem', color: '#6B7280' }}>{user?.email}</div>
<div style={{ fontWeight: 600, color: 'var(--ms-text-primary, #000)' }}>
{user?.name}
</div>
<div style={{ fontSize: '0.8rem', color: 'var(--ms-text-muted, #6B7280)' }}>
{user?.email}
</div>
{user?.class_year && (
<div style={{ fontSize: '0.75rem', color: '#6B7280', marginTop: '4px' }}>
<div
style={{
fontSize: '0.75rem',
color: 'var(--ms-text-muted, #6B7280)',
marginTop: '4px',
}}
>
Class of {user.class_year}
</div>
)}
{user?.bio && (
<div
style={{
fontSize: '0.75rem',
color: '#6B7280',
color: 'var(--ms-text-muted, #6B7280)',
marginTop: '6px',
fontStyle: 'italic',
}}
Expand All @@ -439,16 +508,32 @@
gap: '8px',
padding: '10px 16px',
marginBottom: '4px',
color: 'var(--ms-text-primary, inherit)',
}}
>
<Icons.Settings />
Edit Profile
</Dropdown.Item>
<Dropdown.Item
onClick={toggleDarkMode}
style={{
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '10px 16px',
marginBottom: '4px',
color: 'var(--ms-text-primary, inherit)',
}}
>
{isDarkMode ? <Icons.Sun /> : <Icons.Moon />}
{isDarkMode ? 'Light Mode' : 'Dark Mode'}
</Dropdown.Item>
<Dropdown.Item
onClick={handleLogout}
style={{
borderRadius: '8px',
color: '#EF4444',
color: 'var(--ms-danger, #EF4444)',
display: 'flex',
alignItems: 'center',
gap: '8px',
Expand Down
116 changes: 116 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,119 @@ button {
.list-group-flush > .list-group-item:first-child {
border-top: none;
}

/* =============================================================================
DARK MODE - Global Styles
============================================================================= */

@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) {
color-scheme: dark;
}

:root:not([data-theme='light']) body {
background-color: #0f172a;
color: #f1f5f9;
}

/* Custom scrollbar for dark mode */
:root:not([data-theme='light']) ::-webkit-scrollbar-track {
background: #0f172a;
}

:root:not([data-theme='light']) ::-webkit-scrollbar-thumb {
background: #334155;
border-color: #0f172a;
}

:root:not([data-theme='light']) ::-webkit-scrollbar-thumb:hover {
background: #475569;
}

/* Selection styling for dark mode */
:root:not([data-theme='light']) ::selection {
background: rgba(56, 189, 248, 0.4);
color: inherit;
}

/* Focus outline for dark mode */
:root:not([data-theme='light']) *:focus-visible {
outline-color: var(--ms-sky, #38bdf8);
}

/* Dropdown menus */
:root:not([data-theme='light']) .dropdown-menu {
background-color: #1e293b;
border-color: #334155;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

:root:not([data-theme='light']) .dropdown-item {
color: #e2e8f0;
}

:root:not([data-theme='light']) .dropdown-item:hover {
background: #334155;
color: #f1f5f9;
}

/* List group items */
:root:not([data-theme='light']) .list-group-item {
background-color: #1e293b;
border-color: #334155;
color: #e2e8f0;
}
}

/* Manual dark mode toggle */
[data-theme='dark'] {
color-scheme: dark;
}

[data-theme='dark'] body {
background-color: #0f172a;
color: #f1f5f9;
}

[data-theme='dark'] ::-webkit-scrollbar-track {
background: #0f172a;
}

[data-theme='dark'] ::-webkit-scrollbar-thumb {
background: #334155;
border-color: #0f172a;
}

[data-theme='dark'] ::-webkit-scrollbar-thumb:hover {
background: #475569;
}

[data-theme='dark'] ::selection {
background: rgba(56, 189, 248, 0.4);
color: inherit;
}

[data-theme='dark'] *:focus-visible {
outline-color: var(--ms-sky, #38bdf8);
}

[data-theme='dark'] .dropdown-menu {
background-color: #1e293b;
border-color: #334155;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

[data-theme='dark'] .dropdown-item {
color: #e2e8f0;
}

[data-theme='dark'] .dropdown-item:hover {
background: #334155;
color: #f1f5f9;
}

[data-theme='dark'] .list-group-item {
background-color: #1e293b;
border-color: #334155;
color: #e2e8f0;
}
95 changes: 95 additions & 0 deletions frontend/src/styles/calendar-overrides.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,98 @@
padding: 0.2rem 0.4rem;
}
}

/* ============================================
DARK MODE - FullCalendar Overrides
============================================ */

@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) .fc {
--fc-border-color: #334155;
--fc-page-bg-color: #0f172a;
--fc-neutral-bg-color: #1e293b;
--fc-today-bg-color: rgba(56, 189, 248, 0.15);
}

:root:not([data-theme='light']) .fc .fc-timegrid-col {
border-left-color: #334155;
}

:root:not([data-theme='light']) .fc .fc-timegrid-event {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}

:root:not([data-theme='light']) .fc .fc-timegrid-event:hover {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}

:root:not([data-theme='light']) .fc .fc-timegrid-event.fc-event-dragging {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
}

:root:not([data-theme='light']) .fc .fc-timegrid-axis {
background-color: #1e293b;
color: #94a3b8;
}

:root:not([data-theme='light']) .fc .fc-timegrid-slot-label {
color: #94a3b8;
}

:root:not([data-theme='light']) .fc .fc-col-header-cell-cushion {
color: #e2e8f0;
}

:root:not([data-theme='light']) .fc .fc-daygrid-day-number {
color: #e2e8f0;
}

:root:not([data-theme='light']) .fc .fc-toolbar-title {
color: #f1f5f9;
}
}

/* Manual dark mode toggle */
[data-theme='dark'] .fc {
--fc-border-color: #334155;
--fc-page-bg-color: #0f172a;
--fc-neutral-bg-color: #1e293b;
--fc-today-bg-color: rgba(56, 189, 248, 0.15);
}

[data-theme='dark'] .fc .fc-timegrid-col {
border-left-color: #334155;
}

[data-theme='dark'] .fc .fc-timegrid-event {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}

[data-theme='dark'] .fc .fc-timegrid-event:hover {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}

[data-theme='dark'] .fc .fc-timegrid-event.fc-event-dragging {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
}

[data-theme='dark'] .fc .fc-timegrid-axis {
background-color: #1e293b;
color: #94a3b8;
}

[data-theme='dark'] .fc .fc-timegrid-slot-label {
color: #94a3b8;
}

[data-theme='dark'] .fc .fc-col-header-cell-cushion {
color: #e2e8f0;
}

[data-theme='dark'] .fc .fc-daygrid-day-number {
color: #e2e8f0;
}

[data-theme='dark'] .fc .fc-toolbar-title {
color: #f1f5f9;
}
Loading
Loading