feat: complete UI redesign with dark mode, sidebar navigation, and modern design system

Implemented comprehensive UI overhaul with three-layer architecture:

Layer 1 - Theme System:
- CSS variables for light/dark themes (theme.css)
- Theme composable with light/dark/auto mode (useTheme.ts)
- Sidebar state management composable (useSidebar.ts)
- Refactored main.css to use CSS variables throughout

Layer 2 - Core Components:
- AppSidebar with collapsible navigation (desktop) and overlay (mobile)
- CollapsibleSection reusable component for expandable cards
- Restructured App.vue with new sidebar layout
- Integrated Lucide icons library (lucide-vue-next)

Layer 3 - Views & Components:
- Updated all 14 views with CSS variables and responsive design
- Replaced inline SVG with Lucide icon components
- Added collapsible sections to Dashboard, Admin pages, UserProfile
- Updated 3 shared components (BookingForm, SpaceCalendar, AttachmentsList)

Features:
- Dark/light/auto theme with persistent preference
- Collapsible sidebar (icons-only on desktop, overlay on mobile)
- Consistent color palette using CSS variables
- Full responsive design across all pages
- Modern minimalist aesthetic with Indigo accent color

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-11 21:27:05 +00:00
parent 9c2846cf00
commit 0bf3e6a7e2
28 changed files with 1960 additions and 1641 deletions

View File

@@ -3,8 +3,7 @@
<h2>User Profile</h2>
<!-- Profile Information Card -->
<div class="card">
<h3>Profile Information</h3>
<CollapsibleSection title="Profile Information" :icon="UserIcon">
<div v-if="user" class="profile-info">
<div class="info-item">
<label>Email:</label>
@@ -23,12 +22,10 @@
<span>{{ user.role }}</span>
</div>
</div>
</div>
</CollapsibleSection>
<!-- Timezone Preferences Card -->
<div class="card">
<h3>Timezone Preferences</h3>
<CollapsibleSection title="Timezone Preferences" :icon="Globe">
<div v-if="loadingTimezones" class="loading">Loading timezones...</div>
<div v-else class="timezone-settings">
@@ -56,18 +53,16 @@
<div v-if="timezoneSuccess" class="success">{{ timezoneSuccess }}</div>
<div v-if="timezoneError" class="error">{{ timezoneError }}</div>
</div>
</div>
</CollapsibleSection>
<!-- Google Calendar Integration Card -->
<div class="card">
<h3>Google Calendar Integration</h3>
<CollapsibleSection title="Google Calendar Integration" :icon="CalendarDays">
<div v-if="loadingGoogleStatus" class="loading">Checking connection status...</div>
<div v-else>
<div v-if="googleStatus.connected" class="google-connected">
<div class="status-indicator">
<span class="status-icon"></span>
<CheckCircle :size="20" class="status-icon-connected" />
<span>Connected to Google Calendar</span>
</div>
@@ -103,12 +98,11 @@
<div v-if="error" class="error">{{ error }}</div>
<div v-if="success" class="success">{{ success }}</div>
</div>
</CollapsibleSection>
<!-- Info Card -->
<div class="card info-card">
<h4>About Calendar Integration</h4>
<ul>
<CollapsibleSection title="About Calendar Integration" :icon="Info">
<ul class="info-list">
<li>
<strong>Automatic Sync:</strong> When your booking is approved, it's automatically added to
your Google Calendar
@@ -122,7 +116,7 @@
above
</li>
</ul>
</div>
</CollapsibleSection>
</div>
</template>
@@ -131,6 +125,8 @@ import { ref, computed, onMounted } from 'vue'
import { usersApi, googleCalendarApi, handleApiError } from '@/services/api'
import { useAuthStore } from '@/stores/auth'
import { formatDateTime as formatDateTimeUtil } from '@/utils/datetime'
import CollapsibleSection from '@/components/CollapsibleSection.vue'
import { User as UserIcon, Globe, CalendarDays, CheckCircle, Info } from 'lucide-vue-next'
import type { User } from '@/types'
const authStore = useAuthStore()
@@ -184,7 +180,6 @@ const updateTimezone = async () => {
try {
await usersApi.updateTimezone(selectedTimezone.value)
// Update auth store
if (authStore.user) {
authStore.user.timezone = selectedTimezone.value
}
@@ -195,7 +190,6 @@ const updateTimezone = async () => {
}, 5000)
} catch (err) {
timezoneError.value = handleApiError(err)
// Revert selection on error
if (user.value) {
selectedTimezone.value = user.value.timezone
}
@@ -223,21 +217,17 @@ const connectGoogle = async () => {
try {
const response = await googleCalendarApi.connect()
// Open OAuth URL in popup window
const popup = window.open(
response.authorization_url,
'Google Calendar Authorization',
'width=600,height=600,toolbar=no,menubar=no,location=no'
)
// Poll for connection status
const pollInterval = setInterval(async () => {
// Check if popup was closed
if (popup && popup.closed) {
clearInterval(pollInterval)
connecting.value = false
// Check status one more time
await checkGoogleStatus()
if (googleStatus.value.connected) {
@@ -247,7 +237,6 @@ const connectGoogle = async () => {
}, 3000)
}
} else {
// Poll for connection status
try {
const status = await googleCalendarApi.status()
if (status.connected) {
@@ -256,7 +245,6 @@ const connectGoogle = async () => {
googleStatus.value = status
success.value = 'Google Calendar connected successfully!'
// Close popup
if (popup) {
popup.close()
}
@@ -271,7 +259,6 @@ const connectGoogle = async () => {
}
}, 2000)
// Stop polling after 5 minutes
setTimeout(() => {
clearInterval(pollInterval)
connecting.value = false
@@ -328,22 +315,7 @@ onMounted(() => {
h2 {
margin-bottom: 1.5rem;
color: #333;
}
.card {
background: white;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
h3,
h4 {
margin-top: 0;
margin-bottom: 1.5rem;
color: #444;
color: var(--color-text-primary);
}
.profile-info {
@@ -360,17 +332,17 @@ h4 {
.info-item label {
font-weight: 600;
min-width: 120px;
color: #555;
color: var(--color-text-secondary);
}
.info-item span {
color: #333;
color: var(--color-text-primary);
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
color: var(--color-text-secondary);
}
.google-connected,
@@ -386,29 +358,21 @@ h4 {
gap: 0.5rem;
font-size: 1.1rem;
font-weight: 500;
color: #4caf50;
color: var(--color-success);
}
.status-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
background-color: #4caf50;
color: white;
border-radius: 50%;
font-size: 0.9rem;
.status-icon-connected {
color: var(--color-success);
}
.expiry-info {
color: #666;
color: var(--color-text-secondary);
font-size: 0.9rem;
margin: 0;
}
.info-text {
color: #555;
color: var(--color-text-secondary);
line-height: 1.6;
margin: 0;
}
@@ -420,35 +384,35 @@ h4 {
.benefits-list li {
margin-bottom: 0.5rem;
color: #555;
color: var(--color-text-secondary);
}
.btn {
padding: 0.6rem 1.2rem;
border: none;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
transition: all var(--transition-fast);
align-self: flex-start;
}
.btn-primary {
background-color: #4285f4;
background: var(--color-accent);
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #357ae8;
background: var(--color-accent-hover);
}
.btn-danger {
background-color: #dc3545;
background: var(--color-danger);
color: white;
}
.btn-danger:hover:not(:disabled) {
background-color: #c82333;
background: color-mix(in srgb, var(--color-danger) 85%, black);
}
.btn:disabled {
@@ -458,34 +422,32 @@ h4 {
.error {
padding: 0.75rem;
background-color: #fee;
border: 1px solid #fcc;
border-radius: 4px;
color: #c33;
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
border-radius: var(--radius-sm);
color: var(--color-danger);
margin-top: 1rem;
}
.success {
padding: 0.75rem;
background-color: #efe;
border: 1px solid #cfc;
border-radius: 4px;
color: #3c3;
background: color-mix(in srgb, var(--color-success) 10%, transparent);
border-radius: var(--radius-sm);
color: var(--color-success);
margin-top: 1rem;
}
.info-card {
background-color: #f8f9fa;
}
.info-card ul {
.info-list {
margin: 0;
padding-left: 1.5rem;
}
.info-card li {
.info-list li {
margin-bottom: 0.5rem;
color: #555;
color: var(--color-text-secondary);
}
.info-list strong {
color: var(--color-text-primary);
}
.timezone-settings {
@@ -502,16 +464,18 @@ h4 {
.form-group label {
font-weight: 600;
color: #555;
color: var(--color-text-secondary);
}
.timezone-select {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
font-size: 1rem;
max-width: 400px;
cursor: pointer;
background: var(--color-surface);
color: var(--color-text-primary);
}
.timezone-select:disabled {
@@ -520,9 +484,13 @@ h4 {
}
.help-text {
color: #666;
color: var(--color-text-secondary);
font-size: 0.9rem;
font-style: italic;
margin: 0;
}
.collapsible-section + .collapsible-section {
margin-top: 16px;
}
</style>