router
This commit is contained in:
parent
68a7bd526f
commit
05be85f9b1
275
src/App.vue
275
src/App.vue
|
|
@ -1,21 +1,10 @@
|
|||
<script setup>
|
||||
import JobCard from './components/JobCard.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 { useRoute } from 'vue-router'
|
||||
|
||||
// Current active section
|
||||
const activeSection = ref('dashboard')
|
||||
|
||||
// Selected job for detail view
|
||||
const selectedJobId = ref(null)
|
||||
const showJobDetail = ref(false)
|
||||
// Current route
|
||||
const route = useRoute()
|
||||
|
||||
// Theme state
|
||||
const isDarkMode = ref(false)
|
||||
|
|
@ -52,143 +41,16 @@ const handleThemeToggle = (dark) => {
|
|||
provide('isDarkMode', isDarkMode)
|
||||
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)
|
||||
|
||||
// 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>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<SideNav @navigate="handleNavigation" />
|
||||
<SideNav />
|
||||
|
||||
<div class="content-wrapper">
|
||||
<nav class="breadcrumb">
|
||||
|
|
@ -197,74 +59,12 @@ const closeJobDetails = () => {
|
|||
<li v-else class="active">
|
||||
{{ activeSection === 'saved' ? 'Saved Jobs' : activeSection.charAt(0).toUpperCase() + activeSection.slice(1) }}
|
||||
</li>
|
||||
<li v-if="showJobDetail && activeSection === 'jobs'" class="active">Job Details</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<section v-if="activeSection === 'jobs'" class="job-listings">
|
||||
<div v-if="!showJobDetail" class="jobs-container">
|
||||
<!-- 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>
|
||||
<!-- Router view will render the appropriate component based on the current route -->
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
</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 {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -405,7 +151,6 @@ main {
|
|||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
main section h2 {
|
||||
color: #eee;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ const emit = defineEmits(['apply', 'click']);
|
|||
border: 1px solid var(--card-border, #eee);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 1.5rem;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script setup>
|
||||
import { ref, inject } from 'vue';
|
||||
import { ref, inject, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
// Navigation state
|
||||
const isCollapsed = ref(false);
|
||||
|
|
@ -8,8 +9,12 @@ const isCollapsed = ref(false);
|
|||
const isDarkMode = inject('isDarkMode');
|
||||
const parentToggleTheme = inject('toggleTheme');
|
||||
|
||||
// Get current active section from parent
|
||||
const activeSection = inject('activeSection');
|
||||
// Router setup
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// Compute active section from current route
|
||||
const activeSection = computed(() => route.name);
|
||||
|
||||
const toggleNav = () => {
|
||||
isCollapsed.value = !isCollapsed.value;
|
||||
|
|
@ -20,7 +25,10 @@ const toggleTheme = () => {
|
|||
parentToggleTheme(!isDarkMode.value);
|
||||
};
|
||||
|
||||
defineEmits(['navigate']);
|
||||
// Direct navigation function
|
||||
const navigateTo = (routeName) => {
|
||||
router.push({ name: routeName });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -34,40 +42,40 @@ defineEmits(['navigate']);
|
|||
<nav class="nav-menu">
|
||||
<ul>
|
||||
<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="text" :class="{ 'hidden': isCollapsed }">Home</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</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="text" :class="{ 'hidden': isCollapsed }">Jobs</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</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="text" :class="{ 'hidden': isCollapsed }">Applications</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</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="text" :class="{ 'hidden': isCollapsed }">Saved Jobs</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</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="text" :class="{ 'hidden': isCollapsed }">Profile</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</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="text" :class="{ 'hidden': isCollapsed }">Settings</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
@ -307,6 +315,11 @@ defineEmits(['navigate']);
|
|||
overflow-y: auto;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
|
@ -317,7 +330,7 @@ defineEmits(['navigate']);
|
|||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
a, .router-link-exact-active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
|
|
@ -332,33 +345,34 @@ defineEmits(['navigate']);
|
|||
background-color: rgba(0, 0, 0, 0.05);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: rgba(52, 152, 219, 0.1);
|
||||
color: var(--primary-color);
|
||||
border-left: 3px solid var(--primary-color);
|
||||
}
|
||||
.router-link-active, .router-link-exact-active, a.active {
|
||||
background-color: rgba(52, 152, 219, 0.1);
|
||||
color: var(--primary-color);
|
||||
border-left: 3px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 0.75rem;
|
||||
font-size: 1.2rem;
|
||||
width: 1.5rem;
|
||||
text-align: center;
|
||||
transition: margin 0.3s ease;
|
||||
}
|
||||
.icon {
|
||||
margin-right: 0.75rem;
|
||||
font-size: 1.2rem;
|
||||
width: 1.5rem;
|
||||
text-align: center;
|
||||
transition: margin 0.3s ease;
|
||||
}
|
||||
|
||||
.text {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
.text {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
}
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-footer {
|
||||
padding: 1rem;
|
||||
|
|
@ -408,5 +422,4 @@ defineEmits(['navigate']);
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import { createApp } from 'vue'
|
||||
import './assets/scss/main.scss'
|
||||
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 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 = [
|
||||
// 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({
|
||||
|
|
|
|||
|
|
@ -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