Initial commit
This commit is contained in:
193
admin/src/components/AdminLayout.vue
Normal file
193
admin/src/components/AdminLayout.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useAuth } from '../composables/useAuth'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { user, logout } = useAuth()
|
||||
|
||||
const isScrolled = ref(false)
|
||||
|
||||
function onScroll() {
|
||||
isScrolled.value = window.scrollY > 0
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
onScroll()
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('scroll', onScroll)
|
||||
})
|
||||
|
||||
const pageTitle = computed(() => {
|
||||
const name = route.name?.toString() ?? ''
|
||||
if (name === 'events') return 'Events'
|
||||
if (name === 'event-create') return 'Create Event'
|
||||
if (name === 'event-edit') return 'Edit Event'
|
||||
if (name === 'event-uploads') return 'Event Uploads'
|
||||
return 'Admin'
|
||||
})
|
||||
|
||||
async function handleLogout() {
|
||||
await logout()
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-layout">
|
||||
<aside class="admin-sidebar">
|
||||
<div class="sidebar-brand">
|
||||
<span class="brand-icon">◉</span>
|
||||
<span class="brand-text">Event Uploader</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<router-link to="/" class="nav-item" :class="{ active: route.name === 'events' }">
|
||||
<span class="nav-icon">◈</span>
|
||||
<span>Events</span>
|
||||
</router-link>
|
||||
<router-link to="/events/create" class="nav-item" :class="{ active: route.name === 'event-create' }">
|
||||
<span class="nav-icon">+</span>
|
||||
<span>Create Event</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<a
|
||||
href="#"
|
||||
class="nav-item"
|
||||
@click.prevent="handleLogout"
|
||||
>
|
||||
<span class="nav-icon">⎋</span>
|
||||
<span>Logout</span>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="admin-main">
|
||||
<header class="main-header" :class="{ 'main-header--scrolled': isScrolled }">
|
||||
<h1 class="page-title">{{ pageTitle }}</h1>
|
||||
<div class="header-actions">
|
||||
<span class="user-email">{{ user?.email }}</span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="main-content">
|
||||
<router-view />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.admin-layout {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background: var(--admin-bg);
|
||||
}
|
||||
|
||||
.admin-sidebar {
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
background: var(--admin-sidebar-bg);
|
||||
color: var(--admin-sidebar-text);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--admin-sidebar-border);
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
padding: 1.5rem 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
border-bottom: 1px solid var(--admin-sidebar-border);
|
||||
}
|
||||
|
||||
.brand-icon {
|
||||
font-size: 1.5rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
padding: 1rem 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.625rem 1rem;
|
||||
border-radius: var(--admin-radius);
|
||||
color: var(--admin-sidebar-text-muted);
|
||||
text-decoration: none;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: var(--admin-sidebar-hover);
|
||||
color: var(--admin-sidebar-text);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: var(--admin-sidebar-active);
|
||||
color: var(--admin-primary);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 1rem;
|
||||
width: 1.25rem;
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding: 1rem 0.75rem;
|
||||
border-top: 1px solid var(--admin-sidebar-border);
|
||||
}
|
||||
|
||||
.main-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.25rem 1.5rem;
|
||||
background: var(--admin-surface);
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
transition: box-shadow 0.15s ease;
|
||||
}
|
||||
.main-header--scrolled {
|
||||
box-shadow: var(--admin-shadow);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 1.375rem;
|
||||
font-weight: 600;
|
||||
color: var(--admin-heading);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.user-email {
|
||||
font-size: 0.875rem;
|
||||
color: var(--admin-muted);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: var(--admin-space-8);
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
14
admin/src/components/AdminLoading.vue
Normal file
14
admin/src/components/AdminLoading.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
message?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-card admin-card-body text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status" aria-label="Loading">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-3 mb-0 text-muted">{{ message ?? 'Loading...' }}</p>
|
||||
</div>
|
||||
</template>
|
||||
41
admin/src/components/HelloWorld.vue
Normal file
41
admin/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user