Compare commits
	
		
			3 Commits
		
	
	
		
			1965fed75e
			...
			1ceca94607
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 1ceca94607 | |
|  | 145a40ab12 | |
|  | f8bb4cf1cb | 
							
								
								
									
										40
									
								
								src/App.vue
								
								
								
								
							
							
						
						
									
										40
									
								
								src/App.vue
								
								
								
								
							|  | @ -1,15 +1,19 @@ | |||
| <script setup> | ||||
| import SideNav from './components/SideNav.vue' | ||||
| import { ref, provide, onMounted, computed } from 'vue' | ||||
| import { useRoute } from 'vue-router' | ||||
| import { ref, provide, onMounted, computed, watch } from 'vue' | ||||
| import { useRoute, useRouter } from 'vue-router' | ||||
| 
 | ||||
| // Current route | ||||
| const route = useRoute() | ||||
| const router = useRouter() | ||||
| 
 | ||||
| // Authentication state | ||||
| const isLoggedIn = ref(false) | ||||
| 
 | ||||
| // Theme state | ||||
| const isDarkMode = ref(false) | ||||
| 
 | ||||
| // Initialize theme on mount | ||||
| // Initialize theme and check auth on mount | ||||
| onMounted(() => { | ||||
|   // Check for saved preference in localStorage | ||||
|   const savedTheme = localStorage.getItem('theme') | ||||
|  | @ -22,6 +26,24 @@ onMounted(() => { | |||
|    | ||||
|   // Apply initial theme | ||||
|   applyTheme(isDarkMode.value) | ||||
|    | ||||
|   // 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() | ||||
| }) | ||||
| 
 | ||||
| // Apply theme to document | ||||
|  | @ -41,6 +63,9 @@ const handleThemeToggle = (dark) => { | |||
| provide('isDarkMode', isDarkMode) | ||||
| provide('toggleTheme', handleThemeToggle) | ||||
| 
 | ||||
| // Provide authentication state | ||||
| provide('isLoggedIn', isLoggedIn) | ||||
| 
 | ||||
| // Computed active section from route | ||||
| const activeSection = computed(() => route.name) | ||||
| 
 | ||||
|  | @ -50,9 +75,14 @@ provide('activeSection', activeSection) | |||
| 
 | ||||
| <template> | ||||
|   <div class="app-container"> | ||||
|     <SideNav /> | ||||
|     <!-- Only show SideNav when user is logged in --> | ||||
|     <SideNav v-if="isLoggedIn && route.name !== 'login'" /> | ||||
|      | ||||
|     <div class="content-wrapper"> | ||||
|     <!-- Full-screen login page when not logged in or on login route --> | ||||
|     <router-view v-if="!isLoggedIn || route.name === 'login'"></router-view> | ||||
|      | ||||
|     <!-- Content wrapper only shown when logged in and not on login page --> | ||||
|     <div v-if="isLoggedIn && route.name !== 'login'" class="content-wrapper"> | ||||
|       <nav class="breadcrumb"> | ||||
|         <ul> | ||||
|           <li v-if="activeSection === 'dashboard'" class="active">Home</li> | ||||
|  |  | |||
|  | @ -1,9 +1,10 @@ | |||
| <script setup> | ||||
| import { ref, inject, computed } from 'vue'; | ||||
| import { ref, inject, computed, onMounted, watch } from 'vue'; | ||||
| import { useRoute, useRouter } from 'vue-router'; | ||||
| 
 | ||||
| // Navigation state | ||||
| const isCollapsed = ref(false); | ||||
| const isLoggedIn = ref(false); | ||||
| 
 | ||||
| // Get theme state from parent | ||||
| const isDarkMode = inject('isDarkMode'); | ||||
|  | @ -16,6 +17,20 @@ const router = useRouter(); | |||
| // Compute active section from current route | ||||
| const activeSection = computed(() => route.name); | ||||
| 
 | ||||
| // Check authentication status on mount and when route changes | ||||
| onMounted(() => { | ||||
|   checkAuthStatus(); | ||||
| }); | ||||
| 
 | ||||
| watch(() => route.path, () => { | ||||
|   checkAuthStatus(); | ||||
| }); | ||||
| 
 | ||||
| // Function to check if user is logged in | ||||
| const checkAuthStatus = () => { | ||||
|   isLoggedIn.value = localStorage.getItem('isLoggedIn') === 'true'; | ||||
| }; | ||||
| 
 | ||||
| const toggleNav = () => { | ||||
|   isCollapsed.value = !isCollapsed.value; | ||||
| }; | ||||
|  | @ -29,6 +44,16 @@ const toggleTheme = () => { | |||
| const navigateTo = (routeName) => { | ||||
|   router.push({ name: routeName }); | ||||
| }; | ||||
| 
 | ||||
| // Logout function | ||||
| const handleLogout = () => { | ||||
|   // Clear authentication state | ||||
|   localStorage.removeItem('isLoggedIn'); | ||||
|   isLoggedIn.value = false; | ||||
|    | ||||
|   // Redirect to login page | ||||
|   router.push({ name: 'login' }); | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -78,6 +103,14 @@ const navigateTo = (routeName) => { | |||
|           </router-link> | ||||
|         </li> | ||||
|       </ul> | ||||
|        | ||||
|       <!-- Logout button - only visible when logged in --> | ||||
|       <div v-if="isLoggedIn" class="logout-container"> | ||||
|         <button class="logout-button" @click="handleLogout"> | ||||
|           <span class="icon">🚪</span> | ||||
|           <span class="text" :class="{ 'hidden': isCollapsed }">Logout</span> | ||||
|         </button> | ||||
|       </div> | ||||
|     </nav> | ||||
|     <div class="click-switch" :class="{ 'collapsed-mode': isCollapsed }" @click="toggleTheme"> | ||||
|       <div :class="isDarkMode ? 'sun' : 'moon'"> | ||||
|  | @ -312,6 +345,8 @@ const navigateTo = (routeName) => { | |||
|   .nav-menu { | ||||
|     padding: 1rem 0; | ||||
|     flex: 1; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     overflow-y: auto; | ||||
|     transition: all 0.3s ease; | ||||
|      | ||||
|  | @ -374,6 +409,49 @@ const navigateTo = (routeName) => { | |||
|   } | ||||
|   } | ||||
|    | ||||
|   .logout-container { | ||||
|     margin-top: auto; | ||||
|     padding: 0.5rem 1rem 1rem; | ||||
|     border-top: 1px solid var(--sidebar-border); | ||||
|      | ||||
|     .logout-button { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       width: 100%; | ||||
|       padding: 0.75rem 1rem; | ||||
|       background-color: transparent; | ||||
|       color: var(--text-color); | ||||
|       border: 1px solid var(--sidebar-border); | ||||
|       border-radius: 8px; | ||||
|       cursor: pointer; | ||||
|       transition: all 0.3s ease; | ||||
|        | ||||
|       &:hover { | ||||
|         background-color: rgba(220, 53, 69, 0.1); | ||||
|         color: #dc3545; | ||||
|         border-color: rgba(220, 53, 69, 0.3); | ||||
|       } | ||||
|        | ||||
|       .icon { | ||||
|         margin-right: 0.75rem; | ||||
|         font-size: 1.2rem; | ||||
|         width: 1.5rem; | ||||
|         text-align: center; | ||||
|       } | ||||
|        | ||||
|       .text { | ||||
|         transition: opacity 0.3s ease, transform 0.3s ease; | ||||
|         opacity: 1; | ||||
|         transform: translateX(0); | ||||
|          | ||||
|         &.hidden { | ||||
|           opacity: 0; | ||||
|           transform: translateX(10px); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   .nav-footer { | ||||
|     padding: 1rem; | ||||
|     border-top: 1px solid var(--sidebar-border); | ||||
|  |  | |||
|  | @ -7,11 +7,17 @@ import Applications from '../views/Applications.vue'; | |||
| import SavedJobs from '../views/SavedJobs.vue'; | ||||
| import Profile from '../views/Profile.vue'; | ||||
| import Settings from '../views/Settings.vue'; | ||||
| import Login from '../views/Login.vue'; | ||||
| 
 | ||||
| const routes = [ | ||||
|   { | ||||
|     path: '/', | ||||
|     redirect: '/dashboard' | ||||
|     redirect: '/login' | ||||
|   }, | ||||
|   { | ||||
|     path: '/login', | ||||
|     name: 'login', | ||||
|     component: Login | ||||
|   }, | ||||
|   { | ||||
|     path: '/dashboard', | ||||
|  |  | |||
|  | @ -0,0 +1,227 @@ | |||
| <script setup> | ||||
| import { ref, computed } from 'vue'; | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| // Form state | ||||
| const username = ref(''); | ||||
| const password = ref(''); | ||||
| 
 | ||||
| // Router for navigation | ||||
| const router = useRouter(); | ||||
| 
 | ||||
| // Computed property to check if form is valid | ||||
| const isFormValid = computed(() => { | ||||
|   return username.value.trim() !== '' && password.value.trim() !== ''; | ||||
| }); | ||||
| 
 | ||||
| // Login function | ||||
| const handleLogin = () => { | ||||
|   if (!isFormValid.value) return; | ||||
|    | ||||
|   // Store authentication state in localStorage | ||||
|   localStorage.setItem('isLoggedIn', 'true'); | ||||
|    | ||||
|   // Navigate to dashboard | ||||
|   router.push({ name: 'dashboard' }); | ||||
| }; | ||||
| 
 | ||||
| // Forgot password function | ||||
| const handleForgotPassword = () => { | ||||
|   // This would typically open a modal or navigate to a forgot password page | ||||
|   alert('Forgot password functionality will be implemented in a future update.'); | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div class="login-container"> | ||||
|     <div class="login-modal"> | ||||
|       <div class="login-header"> | ||||
|         <h1><span class="logo-icon">👔</span> jJobs</h1> | ||||
|         <p>Sign in to your account</p> | ||||
|       </div> | ||||
|        | ||||
|       <form @submit.prevent="handleLogin" class="login-form"> | ||||
|         <div class="form-group"> | ||||
|           <label for="username">Username</label> | ||||
|           <input  | ||||
|             type="text"  | ||||
|             id="username"  | ||||
|             v-model="username"  | ||||
|             placeholder="Enter your username" | ||||
|             autocomplete="username" | ||||
|           /> | ||||
|         </div> | ||||
|          | ||||
|         <div class="form-group"> | ||||
|           <label for="password">Password</label> | ||||
|           <input  | ||||
|             type="password"  | ||||
|             id="password"  | ||||
|             v-model="password"  | ||||
|             placeholder="Enter your password" | ||||
|             autocomplete="current-password" | ||||
|           /> | ||||
|         </div> | ||||
|          | ||||
|         <div class="form-actions"> | ||||
|           <button  | ||||
|             type="submit"  | ||||
|             class="login-button"  | ||||
|             :disabled="!isFormValid" | ||||
|             :class="{ 'disabled': !isFormValid }" | ||||
|           > | ||||
|             Login | ||||
|           </button> | ||||
|            | ||||
|           <button  | ||||
|             type="button"  | ||||
|             class="forgot-password-button"  | ||||
|             @click="handleForgotPassword" | ||||
|           > | ||||
|             Forgot Password? | ||||
|           </button> | ||||
|         </div> | ||||
|       </form> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .login-container { | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   min-height: 100vh; | ||||
|   width: 100%; | ||||
|   background-color: var(--bg-color); | ||||
|   position: fixed; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   z-index: 100; | ||||
| } | ||||
| 
 | ||||
| .login-modal { | ||||
|   width: 100%; | ||||
|   max-width: 420px; | ||||
|   background-color: var(--card-bg); | ||||
|   border-radius: 12px; | ||||
|   box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); | ||||
|   padding: 2.5rem; | ||||
|   transition: all 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .login-header { | ||||
|   text-align: center; | ||||
|   margin-bottom: 2rem; | ||||
|    | ||||
|   h1 { | ||||
|     font-size: 2rem; | ||||
|     margin-bottom: 0.5rem; | ||||
|     color: var(--text-color); | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|      | ||||
|     .logo-icon { | ||||
|       margin-right: 0.5rem; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   p { | ||||
|     color: var(--text-color-secondary); | ||||
|     font-size: 1rem; | ||||
|     margin: 0; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .login-form { | ||||
|   .form-group { | ||||
|     margin-bottom: 1.5rem; | ||||
|      | ||||
|     label { | ||||
|       display: block; | ||||
|       margin-bottom: 0.5rem; | ||||
|       font-size: 0.9rem; | ||||
|       color: var(--text-color); | ||||
|       font-weight: 500; | ||||
|     } | ||||
|      | ||||
|     input { | ||||
|       width: 100%; | ||||
|       padding: 0.75rem 1rem; | ||||
|       border: 1px solid var(--card-border); | ||||
|       border-radius: 8px; | ||||
|       background-color: var(--card-bg-secondary); | ||||
|       color: var(--text-color); | ||||
|       font-size: 1rem; | ||||
|       transition: all 0.3s ease; | ||||
|        | ||||
|       &:focus { | ||||
|         outline: none; | ||||
|         border-color: var(--primary-color); | ||||
|         box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); | ||||
|       } | ||||
|        | ||||
|       &::placeholder { | ||||
|         color: var(--text-color-tertiary); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   .form-actions { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     gap: 1rem; | ||||
|     margin-top: 2rem; | ||||
|      | ||||
|     .login-button { | ||||
|       width: 100%; | ||||
|       padding: 0.75rem; | ||||
|       background-color: var(--primary-color); | ||||
|       color: white; | ||||
|       border: none; | ||||
|       border-radius: 8px; | ||||
|       font-size: 1rem; | ||||
|       font-weight: 500; | ||||
|       cursor: pointer; | ||||
|       transition: all 0.3s ease; | ||||
|        | ||||
|       &:hover:not(.disabled) { | ||||
|         background-color: var(--primary-hover-color); | ||||
|       } | ||||
|        | ||||
|       &.disabled { | ||||
|         opacity: 0.6; | ||||
|         cursor: not-allowed; | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     .forgot-password-button { | ||||
|       background: none; | ||||
|       border: none; | ||||
|       color: var(--primary-color); | ||||
|       font-size: 0.9rem; | ||||
|       cursor: pointer; | ||||
|       padding: 0.5rem; | ||||
|       text-align: center; | ||||
|        | ||||
|       &:hover { | ||||
|         text-decoration: underline; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Responsive adjustments | ||||
| @media (max-width: 480px) { | ||||
|   .login-modal { | ||||
|     max-width: 100%; | ||||
|     border-radius: 0; | ||||
|     padding: 2rem 1.5rem; | ||||
|     height: 100vh; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
		Loading…
	
		Reference in New Issue