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>
159 lines
3.3 KiB
Vue
159 lines
3.3 KiB
Vue
<template>
|
|
<div class="login-container">
|
|
<div class="login-card card">
|
|
<h2>Space Booking</h2>
|
|
<p class="subtitle">Sign in to your account</p>
|
|
|
|
<form @submit.prevent="handleLogin">
|
|
<div class="form-group">
|
|
<label for="email">Email</label>
|
|
<input
|
|
id="email"
|
|
v-model="email"
|
|
type="email"
|
|
required
|
|
placeholder="your@email.com"
|
|
autocomplete="email"
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">Password</label>
|
|
<input
|
|
id="password"
|
|
v-model="password"
|
|
type="password"
|
|
required
|
|
placeholder="Enter your password"
|
|
autocomplete="current-password"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="error" class="error">
|
|
{{ error }}
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary btn-block" :disabled="loading">
|
|
{{ loading ? 'Logging in...' : 'Login' }}
|
|
</button>
|
|
</form>
|
|
|
|
<p class="register-link">
|
|
Don't have an account? <router-link to="/register">Register</router-link>
|
|
</p>
|
|
|
|
<div class="demo-accounts">
|
|
<p class="demo-title">Demo Accounts:</p>
|
|
<p><strong>Admin:</strong> admin@example.com / adminpassword</p>
|
|
<p><strong>User:</strong> user@example.com / userpassword</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { handleApiError } from '@/services/api'
|
|
|
|
const router = useRouter()
|
|
const authStore = useAuthStore()
|
|
|
|
const email = ref('')
|
|
const password = ref('')
|
|
const error = ref('')
|
|
const loading = ref(false)
|
|
|
|
const handleLogin = async () => {
|
|
error.value = ''
|
|
loading.value = true
|
|
|
|
try {
|
|
await authStore.login({
|
|
email: email.value,
|
|
password: password.value
|
|
})
|
|
router.push('/dashboard')
|
|
} catch (err) {
|
|
error.value = handleApiError(err)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-container {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 100vh;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.login-card {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
}
|
|
|
|
h2 {
|
|
text-align: center;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.subtitle {
|
|
text-align: center;
|
|
color: var(--color-text-secondary);
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.btn-block {
|
|
width: 100%;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.register-link {
|
|
text-align: center;
|
|
margin-top: 1.5rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.register-link a {
|
|
color: var(--color-accent);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.register-link a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.demo-accounts {
|
|
margin-top: 2rem;
|
|
padding-top: 1.5rem;
|
|
border-top: 1px solid var(--color-border);
|
|
font-size: 0.9rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.demo-title {
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.demo-accounts p {
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
.error {
|
|
margin-top: 1rem;
|
|
padding: 0.75rem;
|
|
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
|
border-left: 3px solid var(--color-danger);
|
|
border-radius: var(--radius-sm);
|
|
color: var(--color-danger);
|
|
}
|
|
</style>
|