This commit is contained in:
Kiran Surendran Pankan 2025-04-02 16:12:04 +05:30
parent 68a7bd526f
commit 05be85f9b1
11 changed files with 484 additions and 307 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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>

View File

@ -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')

View File

@ -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({

372
src/views/Jobs.vue Normal file
View File

@ -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>