This commit is contained in:
Kiran Surendran Pankan 2025-04-02 13:47:31 +05:30
parent 9cafcce470
commit df58caff42
8 changed files with 1297 additions and 133 deletions

View File

@ -7,6 +7,7 @@ import SavedJobs from './components/SavedJobs.vue'
import JobDetail from './components/JobDetail.vue' import JobDetail from './components/JobDetail.vue'
import JobFilters from './components/JobFilters.vue' import JobFilters from './components/JobFilters.vue'
import Profile from './components/Profile.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'
// Current active section // Current active section
@ -190,15 +191,19 @@ const closeJobDetails = () => {
<SideNav @navigate="handleNavigation" /> <SideNav @navigate="handleNavigation" />
<div class="content-wrapper"> <div class="content-wrapper">
<header> <nav class="breadcrumb">
<h1>{{ activeSection === 'dashboard' ? 'Dashboard' : activeSection === 'jobs' ? 'Job Board' : activeSection.charAt(0).toUpperCase() + activeSection.slice(1) }}</h1> <ul>
</header> <li v-if="activeSection === 'dashboard'" class="active">Home</li>
<li v-else class="active">
{{ activeSection === 'saved' ? 'Saved Jobs' : activeSection.charAt(0).toUpperCase() + activeSection.slice(1) }}
</li>
<li v-if="showJobDetail && activeSection === 'jobs'" class="active">Job Details</li>
</ul>
</nav>
<main> <main>
<section v-if="activeSection === 'jobs'" class="job-listings"> <section v-if="activeSection === 'jobs'" class="job-listings">
<div v-if="!showJobDetail" class="jobs-container"> <div v-if="!showJobDetail" class="jobs-container">
<h2 class="section-heading">Available Positions</h2>
<!-- Job Filters Component --> <!-- Job Filters Component -->
<JobFilters <JobFilters
:jobs="allJobListings" :jobs="allJobListings"
@ -258,14 +263,9 @@ const closeJobDetails = () => {
</section> </section>
<section v-else-if="activeSection === 'settings'" class="settings"> <section v-else-if="activeSection === 'settings'" class="settings">
<h2>Settings</h2> <Settings />
<p>Adjust your account settings.</p>
</section> </section>
</main> </main>
<footer>
<p>Job Board Application</p>
</footer>
</div> </div>
</div> </div>
</template> </template>
@ -289,22 +289,48 @@ const closeJobDetails = () => {
} }
} }
header { .breadcrumb {
padding: 1.5rem 2rem; padding: 1.5rem 2rem;
margin-bottom: 1rem; margin-bottom: 1rem;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--card-border, #eee);
h1 { ul {
display: flex;
list-style: none;
padding: 0;
margin: 0; margin: 0;
font-size: 1.8rem; flex-wrap: wrap;
}
li {
display: flex;
align-items: center;
font-size: 0.95rem;
&:not(:last-child)::after {
content: '/';
margin: 0 0.75rem;
color: var(--text-color-tertiary, #aaa);
}
a {
color: var(--primary-color, #3498db);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
&.active {
color: var(--text-color, #333);
font-weight: 500;
}
} }
} }
main { main {
flex: 1; flex: 1;
width: 100%;
max-width: 1100px;
margin: 0 auto;
padding: 0 2rem; padding: 0 2rem;
section { section {
@ -318,17 +344,9 @@ main {
} }
.jobs-container { .jobs-container {
padding: 1.5rem;
background-color: var(--bg-color, #f8f9fa); background-color: var(--bg-color, #f8f9fa);
border-radius: 16px; border-radius: 16px;
.section-heading {
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 2rem;
color: var(--text-color, #333);
}
.filter-results { .filter-results {
margin: 1.5rem 0; margin: 1.5rem 0;
@ -384,27 +402,26 @@ main {
} }
} }
footer {
text-align: center;
padding: 1rem 0;
margin-top: 2rem;
border-top: 1px solid #eee;
font-size: 0.9rem;
color: #888;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
header { .breadcrumb {
border-bottom-color: #333; border-bottom-color: #333;
li {
&:not(:last-child)::after {
color: #666;
}
&.active {
color: #eee;
}
}
} }
main section h2 { main section h2 {
color: #eee; color: #eee;
} }
footer {
border-top-color: #333;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -412,7 +429,7 @@ footer {
margin-left: 60px; margin-left: 60px;
} }
header { .breadcrumb {
padding: 1rem; padding: 1rem;
} }

View File

@ -2,6 +2,9 @@
// Reset and base styles // Reset and base styles
:root { :root {
// Global transition for theme switching
--theme-transition-duration: 0.3s;
--theme-transition-timing: ease;
font-family: $font-family; font-family: $font-family;
line-height: $line-height; line-height: $line-height;
font-weight: $font-weight-normal; font-weight: $font-weight-normal;
@ -22,11 +25,15 @@
--primary-color: #{$primary-color}; --primary-color: #{$primary-color};
--primary-hover-color: #{$primary-hover-color}; --primary-hover-color: #{$primary-hover-color};
--text-color: #{$text-color-dark}; --text-color: #{$text-color-dark};
--text-color-secondary: #bbb;
--text-color-tertiary: #999;
--bg-color: #{$bg-color-dark}; --bg-color: #{$bg-color-dark};
--sidebar-bg: #1e1e1e; --sidebar-bg: #1e1e1e;
--sidebar-border: #333; --sidebar-border: #333;
--card-bg: #2a2a2a; --card-bg: #2a2a2a;
--card-bg-secondary: #222;
--card-border: #444; --card-border: #444;
--primary-color-light: rgba(52, 152, 219, 0.2);
} }
a { a {
@ -43,6 +50,8 @@ body {
margin: 0; margin: 0;
min-width: 320px; min-width: 320px;
min-height: 100vh; min-height: 100vh;
transition: background-color var(--theme-transition-duration) var(--theme-transition-timing),
color var(--theme-transition-duration) var(--theme-transition-timing);
} }
h1 { h1 {
@ -82,11 +91,15 @@ button {
--primary-color: #{$primary-color}; --primary-color: #{$primary-color};
--primary-hover-color: #{$primary-hover-color}; --primary-hover-color: #{$primary-hover-color};
--text-color: #{$text-color-light}; --text-color: #{$text-color-light};
--text-color-secondary: #555;
--text-color-tertiary: #777;
--bg-color: #{$bg-color-light}; --bg-color: #{$bg-color-light};
--sidebar-bg: #fff; --sidebar-bg: #fff;
--sidebar-border: #eee; --sidebar-border: #eee;
--card-bg: #fff; --card-bg: #fff;
--card-bg-secondary: #f9f9f9;
--card-border: #ddd; --card-border: #ddd;
--primary-color-light: rgba(52, 152, 219, 0.1);
button { button {
background-color: $button-bg-light; background-color: $button-bg-light;
@ -103,11 +116,15 @@ button {
--primary-color: #{$primary-color}; --primary-color: #{$primary-color};
--primary-hover-color: #{$primary-hover-color}; --primary-hover-color: #{$primary-hover-color};
--text-color: #{$text-color-light}; --text-color: #{$text-color-light};
--text-color-secondary: #555;
--text-color-tertiary: #777;
--bg-color: #{$bg-color-light}; --bg-color: #{$bg-color-light};
--sidebar-bg: #fff; --sidebar-bg: #fff;
--sidebar-border: #eee; --sidebar-border: #eee;
--card-bg: #fff; --card-bg: #fff;
--card-bg-secondary: #f9f9f9;
--card-border: #ddd; --card-border: #ddd;
--primary-color-light: rgba(52, 152, 219, 0.1);
} }
:root:not(.dark-mode):not(.light-mode) button { :root:not(.dark-mode):not(.light-mode) button {

View File

@ -15,4 +15,4 @@ $font-weight-medium: 500;
$line-height: 1.5; $line-height: 1.5;
// Layout // Layout
$max-content-width: 1000px; $max-content-width: 100vw;

View File

@ -934,7 +934,6 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.job-detail-container { .job-detail-container {
width: 100%; width: 100%;
max-width: 1000px;
margin: 0 auto; margin: 0 auto;
background-color: var(--card-bg, #fff); background-color: var(--card-bg, #fff);
border-radius: 12px; border-radius: 12px;

View File

@ -393,10 +393,15 @@ const totalExperience = computed(() => {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
/* Global transition properties for smoother theme switching */
.profile-container * {
transition: color var(--theme-transition-duration) var(--theme-transition-timing),
background-color var(--theme-transition-duration) var(--theme-transition-timing),
border-color var(--theme-transition-duration) var(--theme-transition-timing);
}
.profile-container { .profile-container {
width: 100%; width: 100%;
max-width: 1000px; margin: 0;
margin: 0 auto;
background-color: var(--card-bg, #fff); background-color: var(--card-bg, #fff);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
@ -614,7 +619,10 @@ const totalExperience = computed(() => {
flex: 1; flex: 1;
min-width: 120px; min-width: 120px;
background-color: var(--card-bg-secondary, #f9f9f9); background-color: var(--card-bg-secondary, #f9f9f9);
transition: background-color 0.3s ease; border: 1px solid var(--card-border, #eee);
transition: background-color var(--theme-transition-duration) var(--theme-transition-timing),
border-color var(--theme-transition-duration) var(--theme-transition-timing),
box-shadow var(--theme-transition-duration) var(--theme-transition-timing);
padding: 1.25rem; padding: 1.25rem;
border-radius: 8px; border-radius: 8px;
text-align: center; text-align: center;
@ -628,7 +636,7 @@ const totalExperience = computed(() => {
.stat-label { .stat-label {
font-size: 0.9rem; font-size: 0.9rem;
color: var(--text-color-secondary, #777); color: var(--text-color-tertiary, #777);
} }
} }
} }
@ -639,7 +647,11 @@ const totalExperience = computed(() => {
.highlight-item { .highlight-item {
padding: 1.25rem; padding: 1.25rem;
background-color: var(--card-bg-secondary, #f9f9f9); background-color: var(--card-bg-secondary, #f9f9f9);
transition: background-color 0.3s ease; border: 1px solid var(--card-border, #eee);
transition: background-color var(--theme-transition-duration) var(--theme-transition-timing),
border-color var(--theme-transition-duration) var(--theme-transition-timing),
color var(--theme-transition-duration) var(--theme-transition-timing),
box-shadow var(--theme-transition-duration) var(--theme-transition-timing);
border-radius: 8px; border-radius: 8px;
margin-bottom: 1rem; margin-bottom: 1rem;
@ -701,14 +713,14 @@ const totalExperience = computed(() => {
.skill-level { .skill-level {
font-size: 0.85rem; font-size: 0.85rem;
color: var(--text-color-secondary, #777); color: var(--text-color-tertiary, #777);
} }
} }
.skill-bar { .skill-bar {
height: 8px; height: 8px;
background-color: var(--card-bg-secondary, #f0f0f0); background-color: var(--card-bg-secondary, #f9f9f9);
transition: background-color 0.3s ease; transition: background-color var(--theme-transition-duration) var(--theme-transition-timing);
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
@ -728,14 +740,18 @@ const totalExperience = computed(() => {
.project-item { .project-item {
background-color: var(--card-bg-secondary, #f9f9f9); background-color: var(--card-bg-secondary, #f9f9f9);
transition: background-color 0.3s ease; transition: background-color var(--theme-transition-duration) var(--theme-transition-timing),
border-color var(--theme-transition-duration) var(--theme-transition-timing),
box-shadow var(--theme-transition-duration) var(--theme-transition-timing);
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--card-border, #eee);
overflow: hidden; overflow: hidden;
.project-image { .project-image {
height: 160px; height: 160px;
background-color: var(--card-bg-secondary, #e0e0e0); background-color: var(--card-bg-secondary, #f9f9f9);
transition: background-color 0.3s ease; transition: background-color var(--theme-transition-duration) var(--theme-transition-timing),
border-color var(--theme-transition-duration) var(--theme-transition-timing);
img { img {
width: 100%; width: 100%;
@ -776,6 +792,7 @@ const totalExperience = computed(() => {
line-clamp: 3; line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
transition: color 0.3s ease;
} }
.project-technologies { .project-technologies {
@ -792,7 +809,7 @@ const totalExperience = computed(() => {
color: var(--primary-color, #3498db); color: var(--primary-color, #3498db);
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
background-color: rgba(52, 152, 219, 0.2); background-color: var(--primary-color-light, rgba(52, 152, 219, 0.2));
} }
} }
} }
@ -859,12 +876,24 @@ const totalExperience = computed(() => {
color: var(--text-color-secondary, #bbb); color: var(--text-color-secondary, #bbb);
} }
.stat-label {
color: var(--text-color-secondary, #bbb) !important;
}
.overview-stats .stat-item, .overview-stats .stat-item,
.experience-highlights .highlight-item, .experience-highlights .highlight-item,
.projects-highlights .project-item, .tab-content .stat-item {
.tab-content .stat-item, background-color: var(--card-bg, #2a2a2a);
.project-image { border-color: var(--card-border, #444);
background-color: var(--card-bg-secondary-dark, rgba(255, 255, 255, 0.05)); }
.projects-highlights .project-item {
background-color: var(--card-bg, #2a2a2a);
border-color: var(--card-border, #444);
}
.project-info {
background-color: var(--card-bg, #2a2a2a);
} }
.highlight-header { .highlight-header {
@ -889,14 +918,31 @@ const totalExperience = computed(() => {
} }
.skill-item .skill-bar { .skill-item .skill-bar {
background-color: var(--card-bg-secondary-dark, rgba(255, 255, 255, 0.1)); background-color: var(--card-bg-secondary, rgba(255, 255, 255, 0.1));
} }
} }
.tech-tag { .tech-tag {
background-color: rgba(52, 152, 219, 0.2); background-color: var(--primary-color-light, rgba(52, 152, 219, 0.2));
color: #64b5f6; color: #64b5f6;
} }
.project-image {
background-color: var(--card-bg, #222);
border-color: var(--card-border, #444);
}
.project-image.project-placeholder {
background-color: var(--primary-color-light, rgba(52, 152, 219, 0.2));
}
.project-item {
border-color: var(--card-border, #444);
}
.project-info .project-title {
color: var(--text-color, #eee);
}
} }
} }
</style> </style>

View File

@ -109,19 +109,8 @@ const applyForJob = (jobId) => {
<template> <template>
<div class="saved-jobs-container"> <div class="saved-jobs-container">
<div class="saved-header"> <div class="saved-header">
<h2>Saved Jobs ({{ savedJobs.length }})</h2>
<div class="search-bar">
<input
type="text"
v-model="searchQuery"
placeholder="Search saved jobs..."
class="search-input"
/>
</div>
</div>
<div class="tags-filter"> <div class="tags-filter">
<div class="tags-label">Filter by tags:</div> <div class="tags-label">Filter by tags</div>
<div class="tags-list"> <div class="tags-list">
<button <button
v-for="tag in allTags" v-for="tag in allTags"
@ -134,6 +123,16 @@ const applyForJob = (jobId) => {
</button> </button>
</div> </div>
</div> </div>
<div class="search-bar">
<div class="tags-label">Search saved jobs</div>
<input
type="text"
v-model="searchQuery"
placeholder="Type to search..."
class="search-input"
/>
</div>
</div>
<div class="saved-jobs-list" v-if="filteredJobs.length > 0"> <div class="saved-jobs-list" v-if="filteredJobs.length > 0">
<div <div
@ -190,7 +189,7 @@ const applyForJob = (jobId) => {
.saved-header { .saved-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: flex-start;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
@media (max-width: 768px) { @media (max-width: 768px) {
@ -224,15 +223,14 @@ const applyForJob = (jobId) => {
} }
} }
} }
.tags-filter {
margin-bottom: 1.5rem;
.tags-label { .tags-label {
font-weight: 500; font-weight: 500;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.tags-filter {
margin-bottom: 1.5rem;
.tags-list { .tags-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

891
src/components/Settings.vue Normal file
View File

@ -0,0 +1,891 @@
<script setup>
import { ref, inject, watch } from 'vue';
// Get theme state from parent
const isDarkMode = inject('isDarkMode');
const toggleTheme = inject('toggleTheme');
const userSettings = ref({
account: {
name: 'Alex Johnson',
email: 'alex.johnson@example.com',
phone: '+1 (555) 123-4567',
location: 'San Francisco, CA',
},
preferences: {
darkMode: isDarkMode,
emailNotifications: true,
pushNotifications: false,
jobAlerts: true,
newsletter: false,
language: 'English',
timezone: 'Pacific Time (PT)',
},
privacy: {
profileVisibility: 'Public',
resumeVisibility: 'Connections only',
activityVisibility: 'Private',
allowRecruiters: true,
allowCompanies: true,
dataSharing: false,
},
security: {
twoFactorAuth: false,
lastPasswordChange: '3 months ago',
loginHistory: [
{ device: 'MacBook Pro', location: 'San Francisco, CA', time: 'Today, 9:45 AM' },
{ device: 'iPhone 14', location: 'San Francisco, CA', time: 'Yesterday, 6:30 PM' },
{ device: 'Windows PC', location: 'San Jose, CA', time: '3 days ago, 2:15 PM' },
],
connectedAccounts: [
{ name: 'Google', connected: true },
{ name: 'LinkedIn', connected: true },
{ name: 'GitHub', connected: false },
],
},
billing: {
plan: 'Professional',
nextBilling: 'May 15, 2025',
paymentMethod: 'Visa ending in 4242',
billingHistory: [
{ date: 'April 15, 2025', amount: '$19.99', status: 'Paid' },
{ date: 'March 15, 2025', amount: '$19.99', status: 'Paid' },
{ date: 'February 15, 2025', amount: '$19.99', status: 'Paid' },
],
},
});
const activeTab = ref('account');
const setActiveTab = (tab) => {
activeTab.value = tab;
};
// Watch for changes to the dark mode preference
watch(() => userSettings.value.preferences.darkMode, (newValue) => {
// Update the app's theme when the setting changes
if (newValue !== isDarkMode.value) {
toggleTheme(newValue);
}
});
// Watch for changes to the app's theme
watch(isDarkMode, (newValue) => {
// Update the setting when the app's theme changes
userSettings.value.preferences.darkMode = newValue;
});
const saveSettings = () => {
// In a real app, this would save settings to a backend
// Apply theme change if dark mode preference was changed
if (userSettings.value.preferences.darkMode !== isDarkMode.value) {
toggleTheme(userSettings.value.preferences.darkMode);
}
alert('Settings saved successfully!');
};
</script>
<template>
<div class="settings-container" :class="{ 'dark-theme': isDarkMode }">
<div class="settings-header">
<h2>Account Settings</h2>
<p>Manage your account settings and preferences</p>
</div>
<div class="settings-content">
<div class="settings-sidebar">
<ul class="settings-nav">
<li>
<button
@click="setActiveTab('account')"
:class="{ active: activeTab === 'account' }"
>
Account
</button>
</li>
<li>
<button
@click="setActiveTab('preferences')"
:class="{ active: activeTab === 'preferences' }"
>
Preferences
</button>
</li>
<li>
<button
@click="setActiveTab('privacy')"
:class="{ active: activeTab === 'privacy' }"
>
Privacy
</button>
</li>
<li>
<button
@click="setActiveTab('security')"
:class="{ active: activeTab === 'security' }"
>
Security
</button>
</li>
<li>
<button
@click="setActiveTab('billing')"
:class="{ active: activeTab === 'billing' }"
>
Billing
</button>
</li>
</ul>
</div>
<div class="settings-main">
<!-- Account Settings -->
<div v-if="activeTab === 'account'" class="settings-panel">
<h3>Personal Information</h3>
<div class="form-group">
<label for="name">Full Name</label>
<input
type="text"
id="name"
v-model="userSettings.account.name"
class="form-control"
/>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input
type="email"
id="email"
v-model="userSettings.account.email"
class="form-control"
/>
</div>
<div class="form-group">
<label for="phone">Phone Number</label>
<input
type="tel"
id="phone"
v-model="userSettings.account.phone"
class="form-control"
/>
</div>
<div class="form-group">
<label for="location">Location</label>
<input
type="text"
id="location"
v-model="userSettings.account.location"
class="form-control"
/>
</div>
<div class="form-actions">
<button @click="saveSettings" class="btn-save">Save Changes</button>
</div>
</div>
<!-- Preferences Settings -->
<div v-if="activeTab === 'preferences'" class="settings-panel">
<h3>User Preferences</h3>
<div class="form-group toggle">
<label>
<span>Dark Mode</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.preferences.darkMode"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-group toggle">
<label>
<span>Email Notifications</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.preferences.emailNotifications"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-group toggle">
<label>
<span>Push Notifications</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.preferences.pushNotifications"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-group toggle">
<label>
<span>Job Alerts</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.preferences.jobAlerts"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-group toggle">
<label>
<span>Newsletter</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.preferences.newsletter"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-group">
<label for="language">Language</label>
<select id="language" v-model="userSettings.preferences.language" class="form-control">
<option>English</option>
<option>Spanish</option>
<option>French</option>
<option>German</option>
<option>Chinese</option>
</select>
</div>
<div class="form-group">
<label for="timezone">Timezone</label>
<select id="timezone" v-model="userSettings.preferences.timezone" class="form-control">
<option>Pacific Time (PT)</option>
<option>Mountain Time (MT)</option>
<option>Central Time (CT)</option>
<option>Eastern Time (ET)</option>
<option>Greenwich Mean Time (GMT)</option>
</select>
</div>
<div class="form-actions">
<button @click="saveSettings" class="btn-save">Save Changes</button>
</div>
</div>
<!-- Privacy Settings -->
<div v-if="activeTab === 'privacy'" class="settings-panel">
<h3>Privacy Settings</h3>
<div class="form-group">
<label for="profileVisibility">Profile Visibility</label>
<select id="profileVisibility" v-model="userSettings.privacy.profileVisibility" class="form-control">
<option>Public</option>
<option>Connections only</option>
<option>Private</option>
</select>
</div>
<div class="form-group">
<label for="resumeVisibility">Resume Visibility</label>
<select id="resumeVisibility" v-model="userSettings.privacy.resumeVisibility" class="form-control">
<option>Public</option>
<option>Connections only</option>
<option>Private</option>
</select>
</div>
<div class="form-group">
<label for="activityVisibility">Activity Visibility</label>
<select id="activityVisibility" v-model="userSettings.privacy.activityVisibility" class="form-control">
<option>Public</option>
<option>Connections only</option>
<option>Private</option>
</select>
</div>
<div class="form-group toggle">
<label>
<span>Allow Recruiters to Contact Me</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.privacy.allowRecruiters"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-group toggle">
<label>
<span>Allow Companies to View My Profile</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.privacy.allowCompanies"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-group toggle">
<label>
<span>Data Sharing with Partners</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.privacy.dataSharing"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-actions">
<button @click="saveSettings" class="btn-save">Save Changes</button>
</div>
</div>
<!-- Security Settings -->
<div v-if="activeTab === 'security'" class="settings-panel">
<h3>Security Settings</h3>
<div class="form-group toggle">
<label>
<span>Two-Factor Authentication</span>
<div class="toggle-switch">
<input
type="checkbox"
v-model="userSettings.security.twoFactorAuth"
/>
<span class="toggle-slider"></span>
</div>
</label>
</div>
<div class="form-group">
<label>Password</label>
<div class="password-info">
<span>Last changed {{ userSettings.security.lastPasswordChange }}</span>
<button class="btn-link">Change Password</button>
</div>
</div>
<h4>Login History</h4>
<div class="login-history">
<div v-for="(login, index) in userSettings.security.loginHistory" :key="index" class="login-item">
<div class="login-device">{{ login.device }}</div>
<div class="login-details">
<div>{{ login.location }}</div>
<div class="login-time">{{ login.time }}</div>
</div>
</div>
</div>
<h4>Connected Accounts</h4>
<div class="connected-accounts">
<div v-for="(account, index) in userSettings.security.connectedAccounts" :key="index" class="account-item">
<div class="account-name">{{ account.name }}</div>
<button class="btn-link">
{{ account.connected ? 'Disconnect' : 'Connect' }}
</button>
</div>
</div>
<div class="form-actions">
<button @click="saveSettings" class="btn-save">Save Changes</button>
</div>
</div>
<!-- Billing Settings -->
<div v-if="activeTab === 'billing'" class="settings-panel">
<h3>Billing Information</h3>
<div class="billing-summary">
<div class="billing-plan">
<h4>Current Plan</h4>
<div class="plan-details">
<div class="plan-name">{{ userSettings.billing.plan }}</div>
<div class="plan-billing">Next billing: {{ userSettings.billing.nextBilling }}</div>
</div>
<button class="btn-outline">Change Plan</button>
</div>
<div class="payment-method">
<h4>Payment Method</h4>
<div class="payment-details">
<div class="card-info">{{ userSettings.billing.paymentMethod }}</div>
</div>
<button class="btn-outline">Update Payment</button>
</div>
</div>
<h4>Billing History</h4>
<div class="billing-history">
<table class="billing-table">
<thead>
<tr>
<th>Date</th>
<th>Amount</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="(bill, index) in userSettings.billing.billingHistory" :key="index">
<td>{{ bill.date }}</td>
<td>{{ bill.amount }}</td>
<td>
<span class="status-badge" :class="bill.status.toLowerCase()">
{{ bill.status }}
</span>
</td>
<td>
<button class="btn-link">Download</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.settings-container {
width: 100%;
background-color: var(--card-bg, #fff);
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
overflow: hidden;
color: var(--text-color, #333);
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
.settings-header {
padding: 2rem;
border-bottom: 1px solid var(--card-border, #eee);
h2 {
font-size: 1.8rem;
margin: 0 0 0.5rem;
font-weight: 600;
}
p {
color: var(--text-color-secondary, #666);
margin: 0;
}
}
.settings-content {
display: flex;
min-height: 600px;
@media (max-width: 768px) {
flex-direction: column;
}
}
.settings-sidebar {
width: 250px;
border-right: 1px solid var(--card-border, #eee);
@media (max-width: 768px) {
width: 100%;
border-right: none;
border-bottom: 1px solid var(--card-border, #eee);
}
.settings-nav {
list-style: none;
padding: 0;
margin: 0;
@media (max-width: 768px) {
display: flex;
overflow-x: auto;
white-space: nowrap;
}
li {
@media (max-width: 768px) {
flex: 1 0 auto;
}
button {
display: block;
width: 100%;
padding: 1rem 1.5rem;
text-align: left;
background: none;
border: none;
color: var(--text-color, #333);
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;
&:hover {
background-color: var(--card-bg-secondary, rgba(0, 0, 0, 0.03));
}
&.active {
background-color: var(--primary-color-light, rgba(52, 152, 219, 0.1));
color: var(--primary-color, #3498db);
border-left: 3px solid var(--primary-color, #3498db);
@media (max-width: 768px) {
border-left: none;
border-bottom: 3px solid var(--primary-color, #3498db);
}
}
}
}
}
}
.settings-main {
flex: 1;
padding: 2rem;
h3 {
margin-top: 0;
margin-bottom: 1.5rem;
font-size: 1.4rem;
font-weight: 600;
}
h4 {
margin: 2rem 0 1rem;
font-size: 1.1rem;
font-weight: 600;
}
}
.form-group {
margin-bottom: 1.5rem;
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
&.toggle {
label {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
}
.form-control {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--card-border, #ddd);
border-radius: 4px;
background-color: var(--card-bg, #fff);
color: var(--text-color, #333);
font-size: 1rem;
transition: border-color 0.2s;
&:focus {
outline: none;
border-color: var(--primary-color, #3498db);
}
}
}
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
input {
opacity: 0;
width: 0;
height: 0;
&:checked + .toggle-slider {
background-color: var(--primary-color, #3498db);
&:before {
transform: translateX(26px);
}
}
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
&:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
}
}
.form-actions {
margin-top: 2rem;
.btn-save {
padding: 0.75rem 1.5rem;
background-color: var(--primary-color, #3498db);
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: var(--primary-color-dark, #2980b9);
}
}
}
.password-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background-color: var(--card-bg-secondary, #f9f9f9);
border-radius: 4px;
}
.btn-link {
background: none;
border: none;
color: var(--primary-color, #3498db);
cursor: pointer;
padding: 0;
font-size: 0.9rem;
&:hover {
text-decoration: underline;
}
}
.login-history {
.login-item {
display: flex;
padding: 1rem;
border-bottom: 1px solid var(--card-border, #eee);
&:last-child {
border-bottom: none;
}
.login-device {
font-weight: 500;
width: 150px;
}
.login-details {
flex: 1;
.login-time {
font-size: 0.9rem;
color: var(--text-color-secondary, #777);
margin-top: 0.25rem;
}
}
}
}
.connected-accounts {
.account-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid var(--card-border, #eee);
&:last-child {
border-bottom: none;
}
.account-name {
font-weight: 500;
}
}
}
.billing-summary {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
.billing-plan, .payment-method {
padding: 1.5rem;
background-color: var(--card-bg, #fff);
border: 1px solid var(--card-border, #eee);
border-radius: 8px;
transition: background-color 0.3s ease, border-color 0.3s ease;
h4 {
margin-top: 0;
margin-bottom: 1rem;
}
.plan-details, .payment-details {
margin-bottom: 1.5rem;
.plan-name, .card-info {
font-weight: 500;
font-size: 1.1rem;
margin-bottom: 0.5rem;
}
.plan-billing {
color: var(--text-color-secondary, #777);
}
}
}
}
.btn-outline {
padding: 0.5rem 1rem;
background: none;
border: 1px solid var(--primary-color, #3498db);
color: var(--primary-color, #3498db);
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;
&:hover {
background-color: var(--primary-color, #3498db);
color: white;
}
}
.billing-table {
width: 100%;
border-collapse: collapse;
th, td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid var(--card-border, #eee);
}
th {
font-weight: 600;
color: var(--text-color-secondary, #666);
}
.status-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.85rem;
&.paid {
background-color: rgba(39, 174, 96, 0.1);
color: #27ae60;
}
&.pending {
background-color: rgba(243, 156, 18, 0.1);
color: #f39c12;
}
&.failed {
background-color: rgba(231, 76, 60, 0.1);
color: #e74c3c;
}
}
}
.dark-theme {
background-color: var(--card-bg-dark, #222);
color: var(--text-color-dark, #eee);
.settings-header {
border-bottom-color: var(--card-border-dark, #333);
}
.settings-sidebar {
border-right-color: var(--card-border-dark, #333);
@media (max-width: 768px) {
border-bottom-color: var(--card-border-dark, #333);
}
}
.form-group {
.form-control {
background-color: var(--card-bg-dark, #333);
border-color: var(--card-border-dark, #444);
color: var(--text-color-dark, #eee);
}
}
.password-info {
background-color: var(--card-bg-secondary-dark, rgba(255, 255, 255, 0.05));
}
.billing-plan,
.payment-method {
background-color: var(--card-bg-dark, #222);
border-color: var(--card-border-dark, #333);
}
.login-item,
.account-item,
.billing-table th,
.billing-table td {
border-bottom-color: var(--card-border-dark, #333);
}
.toggle-switch {
.toggle-slider {
background-color: #555;
}
}
.btn-outline {
&:hover {
background-color: var(--primary-color, #3498db);
color: #eee;
}
}
}
</style>

View File

@ -26,59 +26,72 @@ defineEmits(['navigate']);
<template> <template>
<aside class="side-nav" :class="{ 'collapsed': isCollapsed }"> <aside class="side-nav" :class="{ 'collapsed': isCollapsed }">
<div class="nav-header"> <div class="nav-header">
<h2 class="nav-title">Jobs</h2> <h2 class="nav-title"><span class="icon">👔</span><span class="title-text">jJobs</span></h2>
<button class="toggle-btn" @click="toggleNav"> <button class="toggle-btn" @click="toggleNav">
{{ isCollapsed ? '→' : '←' }} {{ isCollapsed ? '→' : '←' }}
</button> </button>
</div> </div>
<div class="theme-toggle" :class="{ 'collapsed-mode': isCollapsed }">
<button @click="toggleTheme" class="theme-btn">
<span class="icon">{{ isDarkMode ? '☀️' : '🌙' }}</span>
<span class="text" v-if="!isCollapsed">{{ isDarkMode ? 'Light Mode' : 'Dark Mode' }}</span>
</button>
</div>
<nav class="nav-menu"> <nav class="nav-menu">
<ul> <ul>
<li> <li>
<a href="#" @click.prevent="$emit('navigate', 'dashboard')" :class="{ 'active': activeSection === 'dashboard' }"> <a href="#" @click.prevent="$emit('navigate', 'dashboard')" :class="{ 'active': activeSection === 'dashboard' }">
<span class="icon">📊</span> <span class="icon">🏠</span>
<span class="text" v-if="!isCollapsed">Dashboard</span> <span class="text" :class="{ 'hidden': isCollapsed }">Home</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" @click.prevent="$emit('navigate', 'jobs')" :class="{ 'active': activeSection === 'jobs' }"> <a href="#" @click.prevent="$emit('navigate', 'jobs')" :class="{ 'active': activeSection === 'jobs' }">
<span class="icon">💼</span> <span class="icon">💼</span>
<span class="text" v-if="!isCollapsed">Jobs</span> <span class="text" :class="{ 'hidden': isCollapsed }">Jobs</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" @click.prevent="$emit('navigate', 'applications')" :class="{ 'active': activeSection === 'applications' }"> <a href="#" @click.prevent="$emit('navigate', 'applications')" :class="{ 'active': activeSection === 'applications' }">
<span class="icon">📝</span> <span class="icon">📝</span>
<span class="text" v-if="!isCollapsed">Applications</span> <span class="text" :class="{ 'hidden': isCollapsed }">Applications</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" @click.prevent="$emit('navigate', 'saved')" :class="{ 'active': activeSection === 'saved' }"> <a href="#" @click.prevent="$emit('navigate', 'saved')" :class="{ 'active': activeSection === 'saved' }">
<span class="icon"></span> <span class="icon"></span>
<span class="text" v-if="!isCollapsed">Saved</span> <span class="text" :class="{ 'hidden': isCollapsed }">Saved Jobs</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" @click.prevent="$emit('navigate', 'profile')" :class="{ 'active': activeSection === 'profile' }"> <a href="#" @click.prevent="$emit('navigate', 'profile')" :class="{ 'active': activeSection === 'profile' }">
<span class="icon">👤</span> <span class="icon">👤</span>
<span class="text" v-if="!isCollapsed">Profile</span> <span class="text" :class="{ 'hidden': isCollapsed }">Profile</span>
</a> </a>
</li> </li>
<li> <li>
<a href="#" @click.prevent="$emit('navigate', 'settings')" :class="{ 'active': activeSection === 'settings' }"> <a href="#" @click.prevent="$emit('navigate', 'settings')" :class="{ 'active': activeSection === 'settings' }">
<span class="icon"></span> <span class="icon"></span>
<span class="text" v-if="!isCollapsed">Settings</span> <span class="text" :class="{ 'hidden': isCollapsed }">Settings</span>
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
<div class="click-switch" :class="{ 'collapsed-mode': isCollapsed }" @click="toggleTheme">
<div :class="isDarkMode ? 'sun' : 'moon'">
<div class="darkmode_icon">
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
</div>
</div>
<div class="text" :class="{ 'hidden': isCollapsed }">{{ !isDarkMode ? 'Dark' : 'Light' }} Theme</div>
</div>
<div class="nav-footer" :class="{ 'collapsed-mode': isCollapsed }">
<p class="copyright">
<span class="full-copyright" :class="{ 'hidden': isCollapsed }">© 2025 Loxx</span>
<span class="short-copyright" :class="{ 'visible': isCollapsed }">©</span>
</p>
</div>
</aside> </aside>
</template> </template>
@ -88,19 +101,21 @@ defineEmits(['navigate']);
height: 100vh; height: 100vh;
background-color: var(--sidebar-bg); background-color: var(--sidebar-bg);
border-right: 1px solid var(--sidebar-border); border-right: 1px solid var(--sidebar-border);
transition: width 0.3s ease; transition: all 0.3s ease;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index: 10; z-index: 10;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
&.collapsed { &.collapsed {
width: 60px; width: 60px;
.nav-title { .nav-title .title-text {
display: none; opacity: 0;
transform: translateX(-20px);
} }
} }
@ -109,12 +124,21 @@ defineEmits(['navigate']);
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 1rem; padding: 1rem;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--sidebar-border);
overflow: hidden;
.nav-title { .nav-title {
margin: 0; margin: 0;
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 600; font-weight: 600;
white-space: nowrap;
overflow: hidden;
.title-text {
display: inline-block;
margin-left: 20px;
transition: opacity 0.3s ease, transform 0.3s ease;
}
} }
.toggle-btn { .toggle-btn {
@ -132,37 +156,147 @@ defineEmits(['navigate']);
} }
} }
.theme-toggle { .click-switch {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-bottom: 1px solid var(--sidebar-border); border-top: 1px solid var(--sidebar-border);
&.collapsed-mode {
.text {
display: none;
}
}
.theme-btn {
width: 100%;
display: flex;
align-items: center;
padding: 0.5rem;
background: none;
border: 1px solid var(--sidebar-border);
border-radius: 4px;
color: var(--text-color);
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; user-select: none;
transition: all 0.3s ease;
position: relative;
display: flex;
align-items: flex-start;
height: auto;
&:hover { .text {
background-color: rgba(0, 0, 0, 0.05); display: inline-block;
margin-left: 36px;
transition: opacity 0.3s ease, transform 0.3s ease;
opacity: 1;
transform: translateX(0);
white-space: nowrap;
overflow: hidden;
color: var(--text-color);
&.hidden {
opacity: 0;
transform: translateX(10px);
max-width: 0;
}
}
} }
.icon { .sun {
margin-right: 0.75rem; .darkmode_icon {
font-size: 1.2rem; margin-top: 3px;
width: 1.5rem; position: absolute;
text-align: center; width: 20px;
height: 20px;
border-radius: 10px;
background: var(--text-color);
transform-origin: center center;
transition: transform 0.75s ease-in-out;
transform: scale(0.6);
&::after {
content: '';
left: 15px;
bottom: 8px;
transform: scale(0);
}
.ray {
position: absolute;
left: 7px;
top: 7px;
width: 6px;
height: 6px;
border-radius: 6px;
background: var(--text-color);
transform-origin: center;
transition: transform 0.5s ease-in-out;
}
.ray:nth-child(1) {
transform: rotate(45deg) translateX(-16px);
}
.ray:nth-child(2) {
transform: rotate(90deg) translateX(-16px);
}
.ray:nth-child(3) {
transform: rotate(135deg) translateX(-16px);
}
.ray:nth-child(4) {
transform: rotate(180deg) translateX(-16px);
}
.ray:nth-child(5) {
transform: rotate(225deg) translateX(-16px);
}
.ray:nth-child(6) {
transform: rotate(270deg) translateX(-16px);
}
.ray:nth-child(7) {
transform: rotate(315deg) translateX(-16px);
}
.ray:nth-child(8) {
transform: rotate(360deg) translateX(-16px);
}
}
}
.moon {
.darkmode_icon {
margin-top: 3px;
position: absolute;
width: 20px;
height: 20px;
border-radius: 10px;
background: var(--text-color);
transform-origin: center center;
transition: transform 0.75s ease-in-out;
transform: scale(1);
&::after {
content: '';
position: absolute;
width: 16px;
height: 16px;
left: 8px;
bottom: 4px;
border-radius: 10px;
background: var(--sidebar-bg);
transform-origin: center center;
transition: transform 0.5s ease, left 0.25s ease, bottom 0.25s ease;
}
.ray {
position: absolute;
left: 7px;
top: 7px;
width: 6px;
height: 6px;
border-radius: 6px;
background: var(--text-color);
transform-origin: center;
transition: transform 0.5s ease-in-out;
}
.ray:nth-child(1) {
transform: rotate(45deg) translateX(0px);
}
.ray:nth-child(2) {
transform: rotate(90deg) translateX(0px);
}
.ray:nth-child(3) {
transform: rotate(135deg) translateX(0px);
}
.ray:nth-child(4) {
transform: rotate(180deg) translateX(0px);
}
.ray:nth-child(5) {
transform: rotate(225deg) translateX(0px);
}
.ray:nth-child(6) {
transform: rotate(270deg) translateX(0px);
}
.ray:nth-child(7) {
transform: rotate(315deg) translateX(0px);
}
.ray:nth-child(8) {
transform: rotate(360deg) translateX(0px);
} }
} }
} }
@ -171,6 +305,7 @@ defineEmits(['navigate']);
padding: 1rem 0; padding: 1rem 0;
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
transition: all 0.3s ease;
ul { ul {
list-style: none; list-style: none;
@ -188,8 +323,10 @@ defineEmits(['navigate']);
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
color: var(--text-color); color: var(--text-color);
text-decoration: none; text-decoration: none;
transition: background-color 0.2s; transition: all 0.3s ease;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
white-space: nowrap;
overflow: hidden;
&:hover { &:hover {
background-color: rgba(0, 0, 0, 0.05); background-color: rgba(0, 0, 0, 0.05);
@ -207,10 +344,69 @@ defineEmits(['navigate']);
font-size: 1.2rem; font-size: 1.2rem;
width: 1.5rem; width: 1.5rem;
text-align: center; text-align: center;
transition: margin 0.3s ease;
}
.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);
font-size: 0.9rem;
color: var(--text-color-secondary, #888);
transition: all 0.3s ease;
text-align: center;
&.collapsed-mode {
padding: 1rem 0;
.copyright {
display: flex;
justify-content: center;
align-items: center;
}
}
.copyright {
margin: 0;
white-space: nowrap;
overflow: hidden;
position: relative;
.full-copyright {
transition: opacity 0.3s ease, transform 0.3s ease;
opacity: 1;
transform: translateX(0);
&.hidden {
opacity: 0;
transform: translateX(10px);
}
}
.short-copyright {
position: absolute;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s ease;
&.visible {
opacity: 1;
}
} }
} }
} }
} }
</style> </style>