router
This commit is contained in:
parent
68a7bd526f
commit
05be85f9b1
275
src/App.vue
275
src/App.vue
|
|
@ -1,21 +1,10 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import JobCard from './components/JobCard.vue'
|
|
||||||
import SideNav from './components/SideNav.vue'
|
import SideNav from './components/SideNav.vue'
|
||||||
import Dashboard from './components/Dashboard.vue'
|
|
||||||
import Applications from './components/Applications.vue'
|
|
||||||
import SavedJobs from './components/SavedJobs.vue'
|
|
||||||
import JobDetail from './components/JobDetail.vue'
|
|
||||||
import JobFilters from './components/JobFilters.vue'
|
|
||||||
import Profile from './components/Profile.vue'
|
|
||||||
import Settings from './components/Settings.vue'
|
|
||||||
import { ref, provide, onMounted, computed } from 'vue'
|
import { ref, provide, onMounted, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
// Current active section
|
// Current route
|
||||||
const activeSection = ref('dashboard')
|
const route = useRoute()
|
||||||
|
|
||||||
// Selected job for detail view
|
|
||||||
const selectedJobId = ref(null)
|
|
||||||
const showJobDetail = ref(false)
|
|
||||||
|
|
||||||
// Theme state
|
// Theme state
|
||||||
const isDarkMode = ref(false)
|
const isDarkMode = ref(false)
|
||||||
|
|
@ -52,143 +41,16 @@ const handleThemeToggle = (dark) => {
|
||||||
provide('isDarkMode', isDarkMode)
|
provide('isDarkMode', isDarkMode)
|
||||||
provide('toggleTheme', handleThemeToggle)
|
provide('toggleTheme', handleThemeToggle)
|
||||||
|
|
||||||
// Provide navigation state to components
|
// Computed active section from route
|
||||||
|
const activeSection = computed(() => route.name)
|
||||||
|
|
||||||
|
// Provide active section to components that might need it
|
||||||
provide('activeSection', activeSection)
|
provide('activeSection', activeSection)
|
||||||
|
|
||||||
// Handle navigation
|
|
||||||
const handleNavigation = (section) => {
|
|
||||||
activeSection.value = section
|
|
||||||
console.log(`Navigated to: ${section}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sample job listings data
|
|
||||||
const allJobListings = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: 'Frontend Developer',
|
|
||||||
company: 'Tech Innovations Inc.',
|
|
||||||
location: 'San Francisco, CA',
|
|
||||||
salary: '$120,000 - $150,000',
|
|
||||||
employmentType: 'Full-time',
|
|
||||||
experience: '3+ years',
|
|
||||||
description: 'We are looking for an experienced Frontend Developer proficient in Vue.js to join our growing team. You will be responsible for building user interfaces and implementing new features.',
|
|
||||||
applied: false,
|
|
||||||
tags: ['Vue.js', 'JavaScript', 'CSS', 'UI/UX']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: 'Backend Engineer',
|
|
||||||
company: 'DataSystems Co.',
|
|
||||||
location: 'Remote',
|
|
||||||
salary: '$130,000 - $160,000',
|
|
||||||
employmentType: 'Full-time',
|
|
||||||
experience: '4+ years',
|
|
||||||
description: 'Join our backend team to develop scalable APIs and services. Experience with Node.js and database design required.',
|
|
||||||
applied: false,
|
|
||||||
tags: ['Node.js', 'API', 'Backend', 'Databases']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: 'Full Stack Developer',
|
|
||||||
company: 'WebSolutions Ltd.',
|
|
||||||
location: 'New York, NY',
|
|
||||||
salary: '$140,000 - $170,000',
|
|
||||||
employmentType: 'Full-time',
|
|
||||||
experience: '5+ years',
|
|
||||||
description: 'Looking for a versatile developer who can work across the entire stack. Experience with Vue.js and Node.js is a plus.',
|
|
||||||
applied: true,
|
|
||||||
tags: ['Full Stack', 'Vue.js', 'Node.js', 'JavaScript']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: 'UX/UI Designer',
|
|
||||||
company: 'Creative Design Studio',
|
|
||||||
location: 'Chicago, IL',
|
|
||||||
salary: '$110,000 - $135,000',
|
|
||||||
employmentType: 'Full-time',
|
|
||||||
experience: '2+ years',
|
|
||||||
description: 'Join our design team to create beautiful and intuitive user interfaces for web and mobile applications.',
|
|
||||||
applied: false,
|
|
||||||
tags: ['UI/UX', 'Design', 'Figma', 'Prototyping']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: 'DevOps Engineer',
|
|
||||||
company: 'CloudTech Solutions',
|
|
||||||
location: 'Remote',
|
|
||||||
salary: '$135,000 - $165,000',
|
|
||||||
employmentType: 'Full-time',
|
|
||||||
experience: '4+ years',
|
|
||||||
description: 'Looking for a skilled DevOps engineer to help us build and maintain our cloud infrastructure and CI/CD pipelines.',
|
|
||||||
applied: false,
|
|
||||||
tags: ['AWS', 'Docker', 'Kubernetes', 'CI/CD']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
title: 'Product Manager',
|
|
||||||
company: 'Tech Innovations Inc.',
|
|
||||||
location: 'San Francisco, CA',
|
|
||||||
salary: '$150,000 - $180,000',
|
|
||||||
employmentType: 'Full-time',
|
|
||||||
experience: '5+ years',
|
|
||||||
description: 'Lead product development initiatives and work closely with engineering, design, and marketing teams to deliver exceptional products.',
|
|
||||||
applied: false,
|
|
||||||
tags: ['Product', 'Agile', 'Leadership', 'Strategy']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
title: 'Data Scientist',
|
|
||||||
company: 'DataSystems Co.',
|
|
||||||
location: 'Boston, MA',
|
|
||||||
salary: '$140,000 - $170,000',
|
|
||||||
employmentType: 'Full-time',
|
|
||||||
experience: '3+ years',
|
|
||||||
description: 'Apply machine learning and statistical techniques to analyze large datasets and extract valuable insights for our clients.',
|
|
||||||
applied: false,
|
|
||||||
tags: ['Python', 'Machine Learning', 'Data Analysis', 'SQL']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
title: 'Frontend Developer (Contract)',
|
|
||||||
company: 'WebSolutions Ltd.',
|
|
||||||
location: 'Remote',
|
|
||||||
salary: '$90,000 - $120,000',
|
|
||||||
employmentType: 'Contract',
|
|
||||||
experience: '2+ years',
|
|
||||||
description: 'Short-term contract role for a Vue.js developer to help us complete a client project over the next 6 months.',
|
|
||||||
applied: false,
|
|
||||||
tags: ['Vue.js', 'JavaScript', 'Contract', 'Remote']
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Filtered job listings
|
|
||||||
const filteredJobListings = ref([...allJobListings]);
|
|
||||||
|
|
||||||
// Handle job application
|
|
||||||
const handleJobApplication = (jobTitle) => {
|
|
||||||
console.log(`Applied for: ${jobTitle}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// View job details
|
|
||||||
const viewJobDetails = (jobId) => {
|
|
||||||
selectedJobId.value = jobId;
|
|
||||||
showJobDetail.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle filter changes
|
|
||||||
const handleFilterChange = (filteredJobs) => {
|
|
||||||
filteredJobListings.value = filteredJobs;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Close job details
|
|
||||||
const closeJobDetails = () => {
|
|
||||||
showJobDetail.value = false;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<SideNav @navigate="handleNavigation" />
|
<SideNav />
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<nav class="breadcrumb">
|
<nav class="breadcrumb">
|
||||||
|
|
@ -197,74 +59,12 @@ const closeJobDetails = () => {
|
||||||
<li v-else class="active">
|
<li v-else class="active">
|
||||||
{{ activeSection === 'saved' ? 'Saved Jobs' : activeSection.charAt(0).toUpperCase() + activeSection.slice(1) }}
|
{{ activeSection === 'saved' ? 'Saved Jobs' : activeSection.charAt(0).toUpperCase() + activeSection.slice(1) }}
|
||||||
</li>
|
</li>
|
||||||
<li v-if="showJobDetail && activeSection === 'jobs'" class="active">Job Details</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<section v-if="activeSection === 'jobs'" class="job-listings">
|
<!-- Router view will render the appropriate component based on the current route -->
|
||||||
<div v-if="!showJobDetail" class="jobs-container">
|
<router-view></router-view>
|
||||||
<!-- Job Filters Component -->
|
|
||||||
<JobFilters
|
|
||||||
:jobs="allJobListings"
|
|
||||||
:initially-expanded="false"
|
|
||||||
@filter-change="filteredJobListings = $event"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="filter-results">
|
|
||||||
<p class="results-count">{{ filteredJobListings.length }} job{{ filteredJobListings.length !== 1 ? 's' : '' }} found</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="job-cards" v-if="filteredJobListings.length > 0">
|
|
||||||
<JobCard
|
|
||||||
v-for="job in filteredJobListings"
|
|
||||||
:key="job.id"
|
|
||||||
:title="job.title"
|
|
||||||
:company="job.company"
|
|
||||||
:location="job.location"
|
|
||||||
:salary="job.salary"
|
|
||||||
:description="job.description"
|
|
||||||
:applied="job.applied"
|
|
||||||
:tags="job.tags"
|
|
||||||
@apply="handleJobApplication"
|
|
||||||
@click="viewJobDetails(job.id)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="no-results">
|
|
||||||
<div class="no-results-icon">🔍</div>
|
|
||||||
<h3>No jobs found</h3>
|
|
||||||
<p>Try adjusting your search filters to find more opportunities.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<JobDetail
|
|
||||||
v-if="showJobDetail"
|
|
||||||
:jobId="selectedJobId"
|
|
||||||
@close="closeJobDetails"
|
|
||||||
@apply="handleJobApplication"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section v-else-if="activeSection === 'dashboard'" class="dashboard-section">
|
|
||||||
<Dashboard />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section v-else-if="activeSection === 'applications'" class="applications-section">
|
|
||||||
<Applications />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section v-else-if="activeSection === 'saved'" class="saved-section">
|
|
||||||
<SavedJobs />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section v-else-if="activeSection === 'profile'" class="profile-section">
|
|
||||||
<Profile />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section v-else-if="activeSection === 'settings'" class="settings">
|
|
||||||
<Settings />
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -343,60 +143,6 @@ main {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.jobs-container {
|
|
||||||
background-color: var(--bg-color, #f8f9fa);
|
|
||||||
border-radius: 16px;
|
|
||||||
|
|
||||||
.filter-results {
|
|
||||||
margin: 1.5rem 0;
|
|
||||||
|
|
||||||
.results-count {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: var(--text-color-secondary, #666);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-results {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 3rem 1rem;
|
|
||||||
text-align: center;
|
|
||||||
background-color: var(--card-bg, #fff);
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.no-results-icon {
|
|
||||||
font-size: 3rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
margin: 0 0 0.5rem;
|
|
||||||
color: var(--text-color, #333);
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: var(--text-color-secondary, #666);
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.job-cards {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-section {
|
.dashboard-section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
@ -405,7 +151,6 @@ main {
|
||||||
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|
||||||
main section h2 {
|
main section h2 {
|
||||||
color: #eee;
|
color: #eee;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,6 @@ const emit = defineEmits(['apply', 'click']);
|
||||||
border: 1px solid var(--card-border, #eee);
|
border: 1px solid var(--card-border, #eee);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, inject } from 'vue';
|
import { ref, inject, computed } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
// Navigation state
|
// Navigation state
|
||||||
const isCollapsed = ref(false);
|
const isCollapsed = ref(false);
|
||||||
|
|
@ -8,8 +9,12 @@ const isCollapsed = ref(false);
|
||||||
const isDarkMode = inject('isDarkMode');
|
const isDarkMode = inject('isDarkMode');
|
||||||
const parentToggleTheme = inject('toggleTheme');
|
const parentToggleTheme = inject('toggleTheme');
|
||||||
|
|
||||||
// Get current active section from parent
|
// Router setup
|
||||||
const activeSection = inject('activeSection');
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Compute active section from current route
|
||||||
|
const activeSection = computed(() => route.name);
|
||||||
|
|
||||||
const toggleNav = () => {
|
const toggleNav = () => {
|
||||||
isCollapsed.value = !isCollapsed.value;
|
isCollapsed.value = !isCollapsed.value;
|
||||||
|
|
@ -20,7 +25,10 @@ const toggleTheme = () => {
|
||||||
parentToggleTheme(!isDarkMode.value);
|
parentToggleTheme(!isDarkMode.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
defineEmits(['navigate']);
|
// Direct navigation function
|
||||||
|
const navigateTo = (routeName) => {
|
||||||
|
router.push({ name: routeName });
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -34,40 +42,40 @@ defineEmits(['navigate']);
|
||||||
<nav class="nav-menu">
|
<nav class="nav-menu">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" @click.prevent="$emit('navigate', 'dashboard')" :class="{ 'active': activeSection === 'dashboard' }">
|
<router-link :to="{ name: 'dashboard' }" :class="{ 'active': activeSection === 'dashboard' }">
|
||||||
<span class="icon">🏠</span>
|
<span class="icon">🏠</span>
|
||||||
<span class="text" :class="{ 'hidden': isCollapsed }">Home</span>
|
<span class="text" :class="{ 'hidden': isCollapsed }">Home</span>
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" @click.prevent="$emit('navigate', 'jobs')" :class="{ 'active': activeSection === 'jobs' }">
|
<router-link :to="{ name: 'jobs' }" :class="{ 'active': activeSection === 'jobs' }">
|
||||||
<span class="icon">💼</span>
|
<span class="icon">💼</span>
|
||||||
<span class="text" :class="{ 'hidden': isCollapsed }">Jobs</span>
|
<span class="text" :class="{ 'hidden': isCollapsed }">Jobs</span>
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" @click.prevent="$emit('navigate', 'applications')" :class="{ 'active': activeSection === 'applications' }">
|
<router-link :to="{ name: 'applications' }" :class="{ 'active': activeSection === 'applications' }">
|
||||||
<span class="icon">📝</span>
|
<span class="icon">📝</span>
|
||||||
<span class="text" :class="{ 'hidden': isCollapsed }">Applications</span>
|
<span class="text" :class="{ 'hidden': isCollapsed }">Applications</span>
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" @click.prevent="$emit('navigate', 'saved')" :class="{ 'active': activeSection === 'saved' }">
|
<router-link :to="{ name: 'saved' }" :class="{ 'active': activeSection === 'saved' }">
|
||||||
<span class="icon">⭐</span>
|
<span class="icon">⭐</span>
|
||||||
<span class="text" :class="{ 'hidden': isCollapsed }">Saved Jobs</span>
|
<span class="text" :class="{ 'hidden': isCollapsed }">Saved Jobs</span>
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" @click.prevent="$emit('navigate', 'profile')" :class="{ 'active': activeSection === 'profile' }">
|
<router-link :to="{ name: 'profile' }" :class="{ 'active': activeSection === 'profile' }">
|
||||||
<span class="icon">👤</span>
|
<span class="icon">👤</span>
|
||||||
<span class="text" :class="{ 'hidden': isCollapsed }">Profile</span>
|
<span class="text" :class="{ 'hidden': isCollapsed }">Profile</span>
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" @click.prevent="$emit('navigate', 'settings')" :class="{ 'active': activeSection === 'settings' }">
|
<router-link :to="{ name: 'settings' }" :class="{ 'active': activeSection === 'settings' }">
|
||||||
<span class="icon">⚙️</span>
|
<span class="icon">⚙️</span>
|
||||||
<span class="text" :class="{ 'hidden': isCollapsed }">Settings</span>
|
<span class="text" :class="{ 'hidden': isCollapsed }">Settings</span>
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -307,6 +315,11 @@ defineEmits(['navigate']);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
@ -317,7 +330,7 @@ defineEmits(['navigate']);
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a, .router-link-exact-active {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
|
|
@ -332,8 +345,9 @@ defineEmits(['navigate']);
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
.router-link-active, .router-link-exact-active, a.active {
|
||||||
background-color: rgba(52, 152, 219, 0.1);
|
background-color: rgba(52, 152, 219, 0.1);
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
border-left: 3px solid var(--primary-color);
|
border-left: 3px solid var(--primary-color);
|
||||||
|
|
@ -408,5 +422,4 @@ defineEmits(['navigate']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import './assets/scss/main.scss'
|
import './assets/scss/main.scss'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
const app = createApp(App)
|
||||||
|
|
||||||
|
// Use the router
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
// Mount the app
|
||||||
|
app.mount('#app')
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,48 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
|
||||||
|
// Import all view components
|
||||||
|
import Dashboard from '../views/Dashboard.vue';
|
||||||
|
import Jobs from '../views/Jobs.vue';
|
||||||
|
import Applications from '../views/Applications.vue';
|
||||||
|
import SavedJobs from '../views/SavedJobs.vue';
|
||||||
|
import Profile from '../views/Profile.vue';
|
||||||
|
import Settings from '../views/Settings.vue';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
// Add your routes here
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: '/dashboard'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/dashboard',
|
||||||
|
name: 'dashboard',
|
||||||
|
component: Dashboard
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/jobs',
|
||||||
|
name: 'jobs',
|
||||||
|
component: Jobs
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/applications',
|
||||||
|
name: 'applications',
|
||||||
|
component: Applications
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/saved',
|
||||||
|
name: 'saved',
|
||||||
|
component: SavedJobs
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/profile',
|
||||||
|
name: 'profile',
|
||||||
|
component: Profile
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/settings',
|
||||||
|
name: 'settings',
|
||||||
|
component: Settings
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,372 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import JobCard from '../components/JobCard.vue';
|
||||||
|
import JobFilters from '../components/JobFilters.vue';
|
||||||
|
import JobDetail from '../components/JobDetail.vue';
|
||||||
|
|
||||||
|
// State for job listings and UI
|
||||||
|
const allJobListings = ref([]);
|
||||||
|
const filteredJobListings = ref([]);
|
||||||
|
const isLoading = ref(true);
|
||||||
|
const selectedJobId = ref(null);
|
||||||
|
const showJobDetail = ref(false);
|
||||||
|
|
||||||
|
// Fetch job listings (simulated API call)
|
||||||
|
const fetchJobListings = () => {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
// Simulate API delay
|
||||||
|
setTimeout(() => {
|
||||||
|
// In a real app, this would be an API call
|
||||||
|
allJobListings.value = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Frontend Developer',
|
||||||
|
company: 'Tech Innovations Inc.',
|
||||||
|
location: 'San Francisco, CA',
|
||||||
|
salary: '$120,000 - $150,000',
|
||||||
|
employmentType: 'Full-time',
|
||||||
|
experience: '3+ years',
|
||||||
|
description: 'We are looking for an experienced Frontend Developer proficient in Vue.js to join our growing team. You will be responsible for building user interfaces and implementing new features.',
|
||||||
|
applied: false,
|
||||||
|
tags: ['Vue.js', 'JavaScript', 'CSS', 'UI/UX']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Backend Engineer',
|
||||||
|
company: 'DataSystems Co.',
|
||||||
|
location: 'Remote',
|
||||||
|
salary: '$130,000 - $160,000',
|
||||||
|
employmentType: 'Full-time',
|
||||||
|
experience: '4+ years',
|
||||||
|
description: 'Join our backend team to develop scalable APIs and services. Experience with Node.js and database design required.',
|
||||||
|
applied: false,
|
||||||
|
tags: ['Node.js', 'API', 'Backend', 'Databases']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'Full Stack Developer',
|
||||||
|
company: 'WebSolutions Ltd.',
|
||||||
|
location: 'New York, NY',
|
||||||
|
salary: '$140,000 - $170,000',
|
||||||
|
employmentType: 'Full-time',
|
||||||
|
experience: '5+ years',
|
||||||
|
description: 'Looking for a versatile developer who can work across the entire stack. Experience with Vue.js and Node.js is a plus.',
|
||||||
|
applied: true,
|
||||||
|
tags: ['Full Stack', 'Vue.js', 'Node.js', 'JavaScript']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: 'UX/UI Designer',
|
||||||
|
company: 'Creative Design Studio',
|
||||||
|
location: 'Chicago, IL',
|
||||||
|
salary: '$110,000 - $135,000',
|
||||||
|
employmentType: 'Full-time',
|
||||||
|
experience: '2+ years',
|
||||||
|
description: 'Join our design team to create beautiful and intuitive user interfaces for web and mobile applications.',
|
||||||
|
applied: false,
|
||||||
|
tags: ['UI/UX', 'Design', 'Figma', 'Prototyping']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'DevOps Engineer',
|
||||||
|
company: 'CloudTech Solutions',
|
||||||
|
location: 'Remote',
|
||||||
|
salary: '$135,000 - $165,000',
|
||||||
|
employmentType: 'Full-time',
|
||||||
|
experience: '4+ years',
|
||||||
|
description: 'Looking for a skilled DevOps engineer to help us build and maintain our cloud infrastructure and CI/CD pipelines.',
|
||||||
|
applied: false,
|
||||||
|
tags: ['AWS', 'Docker', 'Kubernetes', 'CI/CD']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'Product Manager',
|
||||||
|
company: 'Tech Innovations Inc.',
|
||||||
|
location: 'San Francisco, CA',
|
||||||
|
salary: '$150,000 - $180,000',
|
||||||
|
employmentType: 'Full-time',
|
||||||
|
experience: '5+ years',
|
||||||
|
description: 'Lead product development initiatives and work closely with engineering, design, and marketing teams to deliver exceptional products.',
|
||||||
|
applied: false,
|
||||||
|
tags: ['Product', 'Agile', 'Leadership', 'Strategy']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
title: 'Data Scientist',
|
||||||
|
company: 'DataSystems Co.',
|
||||||
|
location: 'Boston, MA',
|
||||||
|
salary: '$140,000 - $170,000',
|
||||||
|
employmentType: 'Full-time',
|
||||||
|
experience: '3+ years',
|
||||||
|
description: 'Apply machine learning and statistical techniques to analyze large datasets and extract valuable insights for our clients.',
|
||||||
|
applied: false,
|
||||||
|
tags: ['Python', 'Machine Learning', 'Data Analysis', 'SQL']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
title: 'Frontend Developer (Contract)',
|
||||||
|
company: 'WebSolutions Ltd.',
|
||||||
|
location: 'Remote',
|
||||||
|
salary: '$90,000 - $120,000',
|
||||||
|
employmentType: 'Contract',
|
||||||
|
experience: '2+ years',
|
||||||
|
description: 'Short-term contract role for a Vue.js developer to help us complete a client project over the next 6 months.',
|
||||||
|
applied: false,
|
||||||
|
tags: ['Vue.js', 'JavaScript', 'Contract', 'Remote']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Initialize filtered listings with all listings
|
||||||
|
filteredJobListings.value = [...allJobListings.value];
|
||||||
|
isLoading.value = false;
|
||||||
|
}, 800); // Simulate network delay
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle job application
|
||||||
|
const handleJobApplication = (jobTitle) => {
|
||||||
|
console.log(`Applied for: ${jobTitle}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// View job details
|
||||||
|
const viewJobDetails = (jobId) => {
|
||||||
|
selectedJobId.value = jobId;
|
||||||
|
showJobDetail.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle filter changes
|
||||||
|
const handleFilterChange = (filteredJobs) => {
|
||||||
|
filteredJobListings.value = filteredJobs;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close job details
|
||||||
|
const closeJobDetails = () => {
|
||||||
|
showJobDetail.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch job listings on component mount
|
||||||
|
onMounted(() => {
|
||||||
|
fetchJobListings();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="jobs-container">
|
||||||
|
<h1 class="page-title">Find Your Next Opportunity</h1>
|
||||||
|
<p class="page-subtitle">Browse through our curated list of job opportunities tailored to your skills and preferences.</p>
|
||||||
|
|
||||||
|
<!-- Job Filters Component -->
|
||||||
|
<JobFilters
|
||||||
|
:jobs="allJobListings"
|
||||||
|
@filter-change="handleFilterChange"
|
||||||
|
:initially-expanded="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div v-if="isLoading" class="loading-container">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<p>Loading job listings...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Listings -->
|
||||||
|
<div v-else class="job-listings">
|
||||||
|
<div v-if="filteredJobListings.length === 0" class="no-results">
|
||||||
|
<h3>No jobs match your search criteria</h3>
|
||||||
|
<p>Try adjusting your filters or search query to see more results.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="job-cards-grid">
|
||||||
|
<div
|
||||||
|
v-for="job in filteredJobListings"
|
||||||
|
:key="job.id"
|
||||||
|
class="job-card-wrapper"
|
||||||
|
>
|
||||||
|
<JobCard
|
||||||
|
:title="job.title"
|
||||||
|
:company="job.company"
|
||||||
|
:location="job.location"
|
||||||
|
:salary="job.salary"
|
||||||
|
:description="job.description"
|
||||||
|
:applied="job.applied"
|
||||||
|
:tags="job.tags"
|
||||||
|
@apply="handleJobApplication(job.title)"
|
||||||
|
@click="viewJobDetails(job.id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Detail Modal -->
|
||||||
|
<div v-if="showJobDetail" class="job-detail-modal">
|
||||||
|
<div class="modal-overlay" @click="closeJobDetails"></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<button class="close-button" @click="closeJobDetails">×</button>
|
||||||
|
<JobDetail
|
||||||
|
:job-id="selectedJobId"
|
||||||
|
@close="closeJobDetails"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.jobs-container {
|
||||||
|
padding: 2rem;
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-color, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--text-color-secondary, #666);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 3rem;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 5px solid var(--card-border, #eee);
|
||||||
|
border-top: 5px solid var(--primary-color, #3498db);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-listings {
|
||||||
|
margin-top: 2rem;
|
||||||
|
|
||||||
|
.no-results {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem;
|
||||||
|
background-color: var(--card-bg, #fff);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-color, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--text-color-secondary, #666);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-cards-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-detail-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1000px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: var(--card-bg, #fff);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1001;
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--text-color-secondary, #666);
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1002;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--card-bg, #fff);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--text-color, #333);
|
||||||
|
background-color: var(--hover-bg, #f5f5f5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark mode styles
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.jobs-container {
|
||||||
|
.loading-container {
|
||||||
|
.loading-spinner {
|
||||||
|
border-color: #444;
|
||||||
|
border-top-color: var(--primary-color, #3498db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-detail-modal {
|
||||||
|
.modal-overlay {
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
.close-button {
|
||||||
|
background-color: #333;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue