jobs/src/App.vue

203 lines
4.3 KiB
Vue
Raw Normal View History

2025-03-30 11:52:59 +00:00
<script setup>
import SideNav from './components/SideNav.vue'
2025-04-07 04:09:25 +00:00
import { ref, provide, onMounted, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
2025-03-30 11:52:59 +00:00
2025-04-02 10:42:04 +00:00
// Current route
const route = useRoute()
2025-04-07 04:09:25 +00:00
const router = useRouter()
// Authentication state
const isLoggedIn = ref(false)
2025-03-30 11:52:59 +00:00
// Theme state
const isDarkMode = ref(false)
2025-04-07 04:09:25 +00:00
// Initialize theme and check auth on mount
2025-03-30 11:52:59 +00:00
onMounted(() => {
// Check for saved preference in localStorage
const savedTheme = localStorage.getItem('theme')
if (savedTheme) {
isDarkMode.value = savedTheme === 'dark'
} else {
// Use system preference as fallback
isDarkMode.value = window.matchMedia('(prefers-color-scheme: dark)').matches
}
// Apply initial theme
applyTheme(isDarkMode.value)
2025-04-07 04:09:25 +00:00
// Check authentication state
checkAuthStatus()
})
// Check authentication status
const checkAuthStatus = () => {
isLoggedIn.value = localStorage.getItem('isLoggedIn') === 'true'
// Redirect to login if not logged in and not already on login page
if (!isLoggedIn.value && route.name !== 'login') {
router.push({ name: 'login' })
}
}
// Watch for route changes to check auth status
watch(() => route.path, () => {
checkAuthStatus()
2025-03-30 11:52:59 +00:00
})
// Apply theme to document
const applyTheme = (dark) => {
document.documentElement.classList.toggle('dark-mode', dark)
document.documentElement.classList.toggle('light-mode', !dark)
localStorage.setItem('theme', dark ? 'dark' : 'light')
}
// Handle theme toggle from SideNav
const handleThemeToggle = (dark) => {
isDarkMode.value = dark
applyTheme(dark)
}
// Provide theme state to components
provide('isDarkMode', isDarkMode)
provide('toggleTheme', handleThemeToggle)
2025-04-07 04:09:25 +00:00
// Provide authentication state
provide('isLoggedIn', isLoggedIn)
2025-04-02 10:42:04 +00:00
// Computed active section from route
const activeSection = computed(() => route.name)
2025-03-30 11:52:59 +00:00
2025-04-02 10:42:04 +00:00
// Provide active section to components that might need it
provide('activeSection', activeSection)
2025-03-30 11:52:59 +00:00
</script>
<template>
<div class="app-container">
2025-04-07 04:09:25 +00:00
<!-- Only show SideNav when user is logged in -->
<SideNav v-if="isLoggedIn && route.name !== 'login'" />
<!-- Full-screen login page when not logged in or on login route -->
<router-view v-if="!isLoggedIn || route.name === 'login'"></router-view>
2025-03-30 11:52:59 +00:00
2025-04-07 04:09:25 +00:00
<!-- Content wrapper only shown when logged in and not on login page -->
<div v-if="isLoggedIn && route.name !== 'login'" class="content-wrapper">
2025-04-02 08:17:31 +00:00
<nav class="breadcrumb">
<ul>
<li v-if="activeSection === 'dashboard'" class="active">Home</li>
<li v-else class="active">
{{ activeSection === 'saved' ? 'Saved Jobs' : activeSection.charAt(0).toUpperCase() + activeSection.slice(1) }}
</li>
</ul>
</nav>
2025-03-30 11:52:59 +00:00
<main>
2025-04-02 10:42:04 +00:00
<!-- Router view will render the appropriate component based on the current route -->
<router-view></router-view>
2025-03-30 11:52:59 +00:00
</main>
</div>
</div>
</template>
<style scoped lang="scss">
.app-container {
display: flex;
min-height: 100vh;
}
.content-wrapper {
flex: 1;
margin-left: 240px;
transition: margin-left 0.3s ease;
display: flex;
flex-direction: column;
min-height: 100vh;
.side-nav.collapsed + & {
margin-left: 60px;
}
}
2025-04-02 08:17:31 +00:00
.breadcrumb {
2025-03-30 11:52:59 +00:00
padding: 1.5rem 2rem;
margin-bottom: 1rem;
2025-04-02 08:17:31 +00:00
border-bottom: 1px solid var(--card-border, #eee);
2025-03-30 11:52:59 +00:00
2025-04-02 08:17:31 +00:00
ul {
display: flex;
list-style: none;
padding: 0;
2025-03-30 11:52:59 +00:00
margin: 0;
2025-04-02 08:17:31 +00:00
flex-wrap: wrap;
}
li {
display: flex;
align-items: center;
font-size: 0.95rem;
&:not(:last-child)::after {
content: '/';
margin: 0 0.75rem;
color: var(--text-color-tertiary, #aaa);
}
a {
color: var(--primary-color, #3498db);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
&.active {
color: var(--text-color, #333);
font-weight: 500;
}
2025-03-30 11:52:59 +00:00
}
}
main {
flex: 1;
padding: 0 2rem;
section {
margin-bottom: 2rem;
h2 {
margin-bottom: 1.5rem;
font-size: 1.4rem;
color: var(--text-color);
}
}
.dashboard-section {
width: 100%;
}
}
2025-04-02 08:17:31 +00:00
2025-03-30 11:52:59 +00:00
@media (prefers-color-scheme: dark) {
main section h2 {
color: #eee;
}
}
@media (max-width: 768px) {
.content-wrapper {
margin-left: 60px;
}
2025-04-02 08:17:31 +00:00
.breadcrumb {
2025-03-30 11:52:59 +00:00
padding: 1rem;
}
main {
padding: 0 1rem;
}
}
</style>