router
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// Sample applications data
|
||||
const applications = ref([
|
||||
{
|
||||
id: 1,
|
||||
jobTitle: 'Frontend Developer',
|
||||
company: 'Tech Innovations Inc.',
|
||||
location: 'San Francisco, CA',
|
||||
appliedDate: '2025-03-15',
|
||||
status: 'Interview',
|
||||
nextStep: 'Technical Interview on April 5, 2025',
|
||||
notes: 'Prepare for React and Vue questions. Review portfolio projects.'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
jobTitle: 'Senior UI Developer',
|
||||
company: 'Digital Creations',
|
||||
location: 'Remote',
|
||||
appliedDate: '2025-03-18',
|
||||
status: 'Offer',
|
||||
nextStep: 'Review offer by April 10, 2025',
|
||||
notes: 'Salary negotiation pending. Discuss remote work policy.'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
jobTitle: 'Full Stack Engineer',
|
||||
company: 'WebSolutions Ltd.',
|
||||
location: 'New York, NY',
|
||||
appliedDate: '2025-03-20',
|
||||
status: 'Applied',
|
||||
nextStep: 'Waiting for response',
|
||||
notes: 'Follow up if no response by April 3.'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
jobTitle: 'Backend Developer',
|
||||
company: 'DataSystems Co.',
|
||||
location: 'Chicago, IL',
|
||||
appliedDate: '2025-03-22',
|
||||
status: 'Rejected',
|
||||
nextStep: 'N/A',
|
||||
notes: 'Received rejection email. Requested feedback.'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
jobTitle: 'Frontend Team Lead',
|
||||
company: 'InnovateTech Solutions',
|
||||
location: 'Boston, MA',
|
||||
appliedDate: '2025-03-25',
|
||||
status: 'Screening',
|
||||
nextStep: 'Phone screening on April 2, 2025',
|
||||
notes: 'Research company products before the call.'
|
||||
}
|
||||
]);
|
||||
|
||||
// Filter applications by status
|
||||
const statusFilter = ref('All');
|
||||
const statusOptions = ['All', 'Applied', 'Screening', 'Interview', 'Offer', 'Rejected'];
|
||||
|
||||
const filteredApplications = computed(() => {
|
||||
if (statusFilter.value === 'All') {
|
||||
return applications.value;
|
||||
}
|
||||
return applications.value.filter(app => app.status === statusFilter.value);
|
||||
});
|
||||
|
||||
// Application details modal
|
||||
const selectedApplication = ref(null);
|
||||
const showModal = ref(false);
|
||||
|
||||
const openApplicationDetails = (application) => {
|
||||
selectedApplication.value = application;
|
||||
showModal.value = true;
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
// Add note to application
|
||||
const newNote = ref('');
|
||||
|
||||
const addNote = (application) => {
|
||||
if (newNote.value.trim()) {
|
||||
application.notes += '\n' + newNote.value.trim();
|
||||
newNote.value = '';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="applications-container">
|
||||
<!-- Filters -->
|
||||
<div class="filters">
|
||||
<div class="status-filter">
|
||||
<label for="status-select">Filter by Status:</label>
|
||||
<select id="status-select" v-model="statusFilter">
|
||||
<option v-for="option in statusOptions" :key="option" :value="option">
|
||||
{{ option }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="search-applications">
|
||||
<input type="text" placeholder="Search applications..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Applications Table -->
|
||||
<div class="applications-table-container">
|
||||
<table class="applications-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Job Title</th>
|
||||
<th>Company</th>
|
||||
<th>Applied Date</th>
|
||||
<th>Status</th>
|
||||
<th>Next Step</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="application in filteredApplications" :key="application.id" :class="{ 'rejected': application.status === 'Rejected' }">
|
||||
<td class="job-title">{{ application.jobTitle }}</td>
|
||||
<td>{{ application.company }}</td>
|
||||
<td>{{ application.appliedDate }}</td>
|
||||
<td>
|
||||
<span class="status-badge" :class="application.status.toLowerCase()">
|
||||
{{ application.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ application.nextStep }}</td>
|
||||
<td>
|
||||
<button class="view-btn" @click="openApplicationDetails(application)">View Details</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="filteredApplications.length === 0">
|
||||
<td colspan="6" class="no-applications">No applications found matching the selected filter.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Application Details Modal -->
|
||||
<div class="modal-overlay" v-if="showModal" @click="closeModal">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>{{ selectedApplication?.jobTitle }}</h3>
|
||||
<button class="close-modal" @click="closeModal">×</button>
|
||||
</div>
|
||||
<div class="modal-body" v-if="selectedApplication">
|
||||
<div class="application-details">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Company:</span>
|
||||
<span class="detail-value">{{ selectedApplication.company }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Location:</span>
|
||||
<span class="detail-value">{{ selectedApplication.location }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Applied Date:</span>
|
||||
<span class="detail-value">{{ selectedApplication.appliedDate }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Status:</span>
|
||||
<span class="detail-value status-badge" :class="selectedApplication.status.toLowerCase()">
|
||||
{{ selectedApplication.status }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Next Step:</span>
|
||||
<span class="detail-value">{{ selectedApplication.nextStep }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notes-section">
|
||||
<h4>Notes</h4>
|
||||
<div class="notes-content">
|
||||
<p>{{ selectedApplication.notes }}</p>
|
||||
</div>
|
||||
<div class="add-note">
|
||||
<textarea v-model="newNote" placeholder="Add a new note..."></textarea>
|
||||
<button @click="addNote(selectedApplication)" :disabled="!newNote.trim()">Add Note</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="update-status">Update Status</button>
|
||||
<button class="withdraw-application" v-if="selectedApplication.status !== 'Rejected'">
|
||||
Withdraw Application
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.applications-container {
|
||||
width: 100%;
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.status-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--card-border);
|
||||
background-color: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.search-applications {
|
||||
input {
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--card-border);
|
||||
background-color: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
width: 250px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.applications-table-container {
|
||||
overflow-x: auto;
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--card-border);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.applications-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th, td {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
tr.rejected {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.job-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
|
||||
&.applied {
|
||||
background-color: rgba(52, 152, 219, 0.1);
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
&.screening {
|
||||
background-color: rgba(155, 89, 182, 0.1);
|
||||
color: #9b59b6;
|
||||
}
|
||||
|
||||
&.interview {
|
||||
background-color: rgba(241, 196, 15, 0.1);
|
||||
color: #f1c40f;
|
||||
}
|
||||
|
||||
&.offer {
|
||||
background-color: rgba(46, 204, 113, 0.1);
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
&.rejected {
|
||||
background-color: rgba(231, 76, 60, 0.1);
|
||||
color: #e74c3c;
|
||||
}
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
padding: 0.4rem 0.75rem;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.no-applications {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--text-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// Modal styles
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--text-color);
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
|
||||
.application-details {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
.detail-label {
|
||||
width: 120px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notes-section {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.notes-content {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
white-space: pre-line;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.add-note {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
textarea {
|
||||
padding: 0.75rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--card-border);
|
||||
background-color: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
button {
|
||||
align-self: flex-end;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
button {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.update-status {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.withdraw-application {
|
||||
background-color: transparent;
|
||||
border: 1px solid #e74c3c;
|
||||
color: #e74c3c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,471 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// Sample dashboard data
|
||||
const stats = ref({
|
||||
applications: 12,
|
||||
interviews: 4,
|
||||
offers: 1,
|
||||
savedJobs: 8
|
||||
});
|
||||
|
||||
const recentActivity = ref([
|
||||
{
|
||||
id: 1,
|
||||
type: 'application',
|
||||
company: 'Tech Innovations Inc.',
|
||||
position: 'Frontend Developer',
|
||||
date: '2025-03-28',
|
||||
status: 'Applied'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'interview',
|
||||
company: 'DataSystems Co.',
|
||||
position: 'Backend Engineer',
|
||||
date: '2025-03-25',
|
||||
status: 'Interview Scheduled'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'saved',
|
||||
company: 'WebSolutions Ltd.',
|
||||
position: 'Full Stack Developer',
|
||||
date: '2025-03-23',
|
||||
status: 'Saved'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'offer',
|
||||
company: 'Digital Creations',
|
||||
position: 'UI/UX Designer',
|
||||
date: '2025-03-20',
|
||||
status: 'Offer Received'
|
||||
}
|
||||
]);
|
||||
|
||||
const upcomingEvents = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: 'Technical Interview',
|
||||
company: 'DataSystems Co.',
|
||||
date: '2025-04-02',
|
||||
time: '10:00 AM'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Coding Challenge Due',
|
||||
company: 'Tech Innovations Inc.',
|
||||
date: '2025-04-05',
|
||||
time: '11:59 PM'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Follow-up Call',
|
||||
company: 'Digital Creations',
|
||||
date: '2025-04-07',
|
||||
time: '2:30 PM'
|
||||
}
|
||||
]);
|
||||
|
||||
const recommendedJobs = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: 'Senior Vue Developer',
|
||||
company: 'InnovateTech Solutions',
|
||||
location: 'Remote',
|
||||
salary: '$140,000 - $170,000',
|
||||
matchScore: 95
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Frontend Team Lead',
|
||||
company: 'WebWorks Inc.',
|
||||
location: 'San Francisco, CA',
|
||||
salary: '$160,000 - $190,000',
|
||||
matchScore: 88
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Full Stack Engineer',
|
||||
company: 'CloudSystems',
|
||||
location: 'New York, NY',
|
||||
salary: '$130,000 - $160,000',
|
||||
matchScore: 82
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<div class="dashboard-grid">
|
||||
<!-- Stats Cards -->
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ stats.applications }}</div>
|
||||
<div class="stat-label">Applications</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ stats.interviews }}</div>
|
||||
<div class="stat-label">Interviews</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ stats.offers }}</div>
|
||||
<div class="stat-label">Offers</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ stats.savedJobs }}</div>
|
||||
<div class="stat-label">Saved Jobs</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="dashboard-card activity-card">
|
||||
<h3 class="card-title">Recent Activity</h3>
|
||||
<div class="activity-list">
|
||||
<div v-for="activity in recentActivity" :key="activity.id" class="activity-item">
|
||||
<div class="activity-icon" :class="activity.type">
|
||||
<span v-if="activity.type === 'application'">📝</span>
|
||||
<span v-else-if="activity.type === 'interview'">🗣️</span>
|
||||
<span v-else-if="activity.type === 'saved'">⭐</span>
|
||||
<span v-else-if="activity.type === 'offer'">🎉</span>
|
||||
</div>
|
||||
<div class="activity-details">
|
||||
<div class="activity-title">{{ activity.position }}</div>
|
||||
<div class="activity-company">{{ activity.company }}</div>
|
||||
<div class="activity-meta">
|
||||
<span class="activity-date">{{ activity.date }}</span>
|
||||
<span class="activity-status" :class="activity.type">{{ activity.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upcoming Events -->
|
||||
<div class="dashboard-card events-card">
|
||||
<h3 class="card-title">Upcoming Events</h3>
|
||||
<div class="events-list">
|
||||
<div v-for="event in upcomingEvents" :key="event.id" class="event-item">
|
||||
<div class="event-date">
|
||||
<div class="event-day">{{ new Date(event.date).getDate() }}</div>
|
||||
<div class="event-month">{{ new Date(event.date).toLocaleString('default', { month: 'short' }) }}</div>
|
||||
</div>
|
||||
<div class="event-details">
|
||||
<div class="event-title">{{ event.title }}</div>
|
||||
<div class="event-company">{{ event.company }}</div>
|
||||
<div class="event-time">{{ event.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommended Jobs -->
|
||||
<div class="dashboard-card recommended-card">
|
||||
<h3 class="card-title">Recommended for You</h3>
|
||||
<div class="recommended-list">
|
||||
<div v-for="job in recommendedJobs" :key="job.id" class="recommended-item">
|
||||
<div class="match-score">{{ job.matchScore }}% Match</div>
|
||||
<div class="job-details">
|
||||
<div class="job-title">{{ job.title }}</div>
|
||||
<div class="job-company">{{ job.company }}</div>
|
||||
<div class="job-meta">
|
||||
<span class="job-location">{{ job.location }}</span>
|
||||
<span class="job-salary">{{ job.salary }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="view-job-btn">View</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dashboard {
|
||||
width: 100%;
|
||||
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: var(--card-bg, #fff);
|
||||
border: 1px solid var(--card-border, #ddd);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.stat-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
background-color: var(--card-bg, #fff);
|
||||
border: 1px solid var(--card-border, #ddd);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.card-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
border-bottom: 1px solid var(--card-border, #ddd);
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
.activity-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--card-border, #eee);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-icon {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
|
||||
&.application {
|
||||
background-color: rgba(52, 152, 219, 0.1);
|
||||
}
|
||||
|
||||
&.interview {
|
||||
background-color: rgba(155, 89, 182, 0.1);
|
||||
}
|
||||
|
||||
&.saved {
|
||||
background-color: rgba(241, 196, 15, 0.1);
|
||||
}
|
||||
|
||||
&.offer {
|
||||
background-color: rgba(46, 204, 113, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.activity-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.activity-company {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.activity-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.activity-status {
|
||||
font-weight: 600;
|
||||
|
||||
&.application {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
&.interview {
|
||||
color: #9b59b6;
|
||||
}
|
||||
|
||||
&.saved {
|
||||
color: #f1c40f;
|
||||
}
|
||||
|
||||
&.offer {
|
||||
color: #2ecc71;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.events-card {
|
||||
.events-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.event-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--card-border, #eee);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.event-date {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 8px;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
|
||||
.event-day {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.event-month {
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.event-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.event-company {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.25rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.event-time {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.recommended-card {
|
||||
.recommended-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.recommended-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--card-border, #eee);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.match-score {
|
||||
background-color: rgba(46, 204, 113, 0.1);
|
||||
color: #2ecc71;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
margin-right: 1rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.job-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.job-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.job-company {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.25rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.job-meta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.view-job-btn {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,950 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// User profile data
|
||||
const userProfile = ref({
|
||||
personal: {
|
||||
name: 'Alex Johnson',
|
||||
title: 'Senior Frontend Developer',
|
||||
email: 'alex.johnson@example.com',
|
||||
phone: '+1 (555) 123-4567',
|
||||
location: 'San Francisco, CA',
|
||||
about: 'Passionate frontend developer with 6+ years of experience building responsive and user-friendly web applications. Specialized in Vue.js and modern JavaScript frameworks.',
|
||||
avatar: null, // Will be generated from initials if null
|
||||
},
|
||||
experience: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Senior Frontend Developer',
|
||||
company: 'TechInnovate Solutions',
|
||||
location: 'San Francisco, CA',
|
||||
startDate: '2023-01',
|
||||
endDate: null, // null means current
|
||||
isCurrent: true,
|
||||
description: 'Lead the frontend development team in building modern web applications using Vue.js. Implemented component libraries and design systems that improved development efficiency by 35%.',
|
||||
achievements: [
|
||||
'Reduced page load time by 40% through code optimization and lazy loading',
|
||||
'Implemented comprehensive unit and integration testing, achieving 90% code coverage',
|
||||
'Mentored junior developers and conducted code reviews to maintain high code quality'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Frontend Developer',
|
||||
company: 'WebSolutions Inc.',
|
||||
location: 'Boston, MA',
|
||||
startDate: '2020-03',
|
||||
endDate: '2022-12',
|
||||
isCurrent: false,
|
||||
description: 'Developed responsive web applications using Vue.js and Nuxt. Collaborated with UX/UI designers to implement pixel-perfect interfaces.',
|
||||
achievements: [
|
||||
'Built a component library that was used across multiple projects',
|
||||
'Implemented state management using Vuex that simplified data flow',
|
||||
'Integrated third-party APIs and services to enhance application functionality'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Junior Web Developer',
|
||||
company: 'Digital Creations',
|
||||
location: 'Remote',
|
||||
startDate: '2018-06',
|
||||
endDate: '2020-02',
|
||||
isCurrent: false,
|
||||
description: 'Developed and maintained client websites using HTML, CSS, and JavaScript. Collaborated with the design team to implement responsive layouts.',
|
||||
achievements: [
|
||||
'Converted 15+ static websites to responsive designs',
|
||||
'Implemented SEO best practices that improved client search rankings',
|
||||
'Created custom WordPress themes and plugins for client websites'
|
||||
]
|
||||
}
|
||||
],
|
||||
education: [
|
||||
{
|
||||
id: 1,
|
||||
degree: 'Master of Science in Computer Science',
|
||||
institution: 'Stanford University',
|
||||
location: 'Stanford, CA',
|
||||
startDate: '2016-09',
|
||||
endDate: '2018-05',
|
||||
description: 'Focused on web technologies and human-computer interaction. Thesis on improving frontend performance in single-page applications.'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
degree: 'Bachelor of Science in Computer Science',
|
||||
institution: 'University of Washington',
|
||||
location: 'Seattle, WA',
|
||||
startDate: '2012-09',
|
||||
endDate: '2016-05',
|
||||
description: 'Graduated with honors. Coursework included web development, algorithms, data structures, and software engineering principles.'
|
||||
}
|
||||
],
|
||||
skills: [
|
||||
{ name: 'Vue.js', level: 95 },
|
||||
{ name: 'JavaScript', level: 90 },
|
||||
{ name: 'HTML/CSS', level: 95 },
|
||||
{ name: 'SCSS/SASS', level: 85 },
|
||||
{ name: 'TypeScript', level: 80 },
|
||||
{ name: 'React', level: 75 },
|
||||
{ name: 'Node.js', level: 70 },
|
||||
{ name: 'Git', level: 85 },
|
||||
{ name: 'Webpack', level: 75 },
|
||||
{ name: 'Jest', level: 80 },
|
||||
{ name: 'RESTful APIs', level: 85 },
|
||||
{ name: 'GraphQL', level: 70 }
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'E-commerce Dashboard',
|
||||
description: 'A comprehensive dashboard for e-commerce businesses to track sales, inventory, and customer data in real-time.',
|
||||
technologies: ['Vue.js', 'Vuex', 'Chart.js', 'Firebase'],
|
||||
link: 'https://github.com/alexjohnson/ecommerce-dashboard',
|
||||
image: null
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Task Management App',
|
||||
description: 'A collaborative task management application with real-time updates, file sharing, and team communication features.',
|
||||
technologies: ['Vue.js', 'Node.js', 'Socket.io', 'MongoDB'],
|
||||
link: 'https://github.com/alexjohnson/task-manager',
|
||||
image: null
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Weather Forecast App',
|
||||
description: 'A weather application that provides detailed forecasts, historical data, and customizable alerts based on user location.',
|
||||
technologies: ['Vue.js', 'OpenWeatherMap API', 'Geolocation API', 'PWA'],
|
||||
link: 'https://github.com/alexjohnson/weather-app',
|
||||
image: null
|
||||
}
|
||||
],
|
||||
certifications: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Vue.js Advanced Developer',
|
||||
issuer: 'Vue Mastery',
|
||||
date: '2023-04',
|
||||
expires: '2026-04',
|
||||
credentialId: 'VM-ADV-2023-1234',
|
||||
link: 'https://vuemastery.com/cert/VM-ADV-2023-1234'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Frontend Web Developer Nanodegree',
|
||||
issuer: 'Udacity',
|
||||
date: '2021-08',
|
||||
expires: null, // No expiration
|
||||
credentialId: 'UD-FWD-2021-5678',
|
||||
link: 'https://udacity.com/cert/UD-FWD-2021-5678'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'JavaScript Algorithms and Data Structures',
|
||||
issuer: 'freeCodeCamp',
|
||||
date: '2020-11',
|
||||
expires: null, // No expiration
|
||||
credentialId: 'FCC-JADS-2020-9012',
|
||||
link: 'https://freecodecamp.org/cert/FCC-JADS-2020-9012'
|
||||
}
|
||||
],
|
||||
languages: [
|
||||
{ name: 'English', proficiency: 'Native' },
|
||||
{ name: 'Spanish', proficiency: 'Intermediate' },
|
||||
{ name: 'French', proficiency: 'Basic' }
|
||||
],
|
||||
socialLinks: {
|
||||
github: 'https://github.com/alexjohnson',
|
||||
linkedin: 'https://linkedin.com/in/alexjohnson',
|
||||
twitter: 'https://twitter.com/alexjohnson',
|
||||
portfolio: 'https://alexjohnson.dev'
|
||||
},
|
||||
preferences: {
|
||||
jobTypes: ['Full-time', 'Remote'],
|
||||
locations: ['San Francisco, CA', 'Remote'],
|
||||
salary: '$120,000 - $150,000',
|
||||
availableFrom: '2025-06-01',
|
||||
willingToRelocate: true,
|
||||
industries: ['Technology', 'E-commerce', 'Healthcare', 'Education']
|
||||
},
|
||||
resume: {
|
||||
url: '/path/to/resume.pdf',
|
||||
lastUpdated: '2025-02-15'
|
||||
}
|
||||
});
|
||||
|
||||
// Computed properties for display
|
||||
const fullName = computed(() => userProfile.value.personal.name);
|
||||
const initials = computed(() => {
|
||||
const nameParts = userProfile.value.personal.name.split(' ');
|
||||
return nameParts.map(part => part[0]).join('');
|
||||
});
|
||||
|
||||
// Format date function
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return 'Present';
|
||||
|
||||
const date = new Date(dateString);
|
||||
return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short' }).format(date);
|
||||
};
|
||||
|
||||
// Calculate experience duration
|
||||
const calculateDuration = (startDate, endDate) => {
|
||||
const start = new Date(startDate);
|
||||
const end = endDate ? new Date(endDate) : new Date();
|
||||
|
||||
const years = end.getFullYear() - start.getFullYear();
|
||||
const months = end.getMonth() - start.getMonth();
|
||||
|
||||
let duration = '';
|
||||
if (years > 0) {
|
||||
duration += `${years} year${years !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
if (months > 0 || (months === 0 && years === 0)) {
|
||||
if (duration) duration += ', ';
|
||||
duration += `${months} month${months !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
return duration;
|
||||
};
|
||||
|
||||
// Total years of experience
|
||||
const totalExperience = computed(() => {
|
||||
let totalMonths = 0;
|
||||
|
||||
userProfile.value.experience.forEach(exp => {
|
||||
const startDate = new Date(exp.startDate);
|
||||
const endDate = exp.endDate ? new Date(exp.endDate) : new Date();
|
||||
|
||||
const years = endDate.getFullYear() - startDate.getFullYear();
|
||||
const months = endDate.getMonth() - startDate.getMonth();
|
||||
|
||||
totalMonths += years * 12 + months;
|
||||
});
|
||||
|
||||
const years = Math.floor(totalMonths / 12);
|
||||
const months = totalMonths % 12;
|
||||
|
||||
let result = '';
|
||||
if (years > 0) {
|
||||
result += `${years} year${years !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
if (months > 0) {
|
||||
if (result) result += ' ';
|
||||
result += `${months} month${months !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="profile-container">
|
||||
<div class="profile-header">
|
||||
<div class="profile-avatar" v-if="userProfile.personal.avatar">
|
||||
<img :src="userProfile.personal.avatar" alt="Profile avatar" />
|
||||
</div>
|
||||
<div class="profile-avatar profile-avatar-initials" v-else>
|
||||
{{ initials }}
|
||||
</div>
|
||||
|
||||
<div class="profile-header-info">
|
||||
<h1 class="profile-name">{{ userProfile.personal.name }}</h1>
|
||||
<h2 class="profile-title">{{ userProfile.personal.title }}</h2>
|
||||
<div class="profile-location">
|
||||
<span class="location-icon">📍</span>
|
||||
<span>{{ userProfile.personal.location }}</span>
|
||||
</div>
|
||||
|
||||
<div class="profile-contact">
|
||||
<div class="contact-item">
|
||||
<span class="contact-icon">📧</span>
|
||||
<a :href="`mailto:${userProfile.personal.email}`">{{ userProfile.personal.email }}</a>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<span class="contact-icon">📱</span>
|
||||
<a :href="`tel:${userProfile.personal.phone}`">{{ userProfile.personal.phone }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-social">
|
||||
<a v-if="userProfile.socialLinks.github" :href="userProfile.socialLinks.github" target="_blank" class="social-link">
|
||||
GitHub
|
||||
</a>
|
||||
<a v-if="userProfile.socialLinks.linkedin" :href="userProfile.socialLinks.linkedin" target="_blank" class="social-link">
|
||||
LinkedIn
|
||||
</a>
|
||||
<a v-if="userProfile.socialLinks.twitter" :href="userProfile.socialLinks.twitter" target="_blank" class="social-link">
|
||||
Twitter
|
||||
</a>
|
||||
<a v-if="userProfile.socialLinks.portfolio" :href="userProfile.socialLinks.portfolio" target="_blank" class="social-link">
|
||||
Portfolio
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-actions">
|
||||
<button class="action-button primary-button">
|
||||
<span class="action-icon">📝</span>
|
||||
<span>Edit Profile</span>
|
||||
</button>
|
||||
<button class="action-button secondary-button">
|
||||
<span class="action-icon">📄</span>
|
||||
<span>Download Resume</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-tabs">
|
||||
<div class="tab-navigation">
|
||||
<button class="tab-button active">Overview</button>
|
||||
<button class="tab-button">Experience</button>
|
||||
<button class="tab-button">Education</button>
|
||||
<button class="tab-button">Skills</button>
|
||||
<button class="tab-button">Projects</button>
|
||||
<button class="tab-button">Preferences</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<!-- Overview Tab -->
|
||||
<div class="tab-section">
|
||||
<div class="section-container">
|
||||
<h2 class="section-title">About Me</h2>
|
||||
<p class="about-text">{{ userProfile.personal.about }}</p>
|
||||
|
||||
<div class="overview-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ totalExperience }}</div>
|
||||
<div class="stat-label">Experience</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ userProfile.skills.length }}</div>
|
||||
<div class="stat-label">Skills</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ userProfile.projects.length }}</div>
|
||||
<div class="stat-label">Projects</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">{{ userProfile.certifications.length }}</div>
|
||||
<div class="stat-label">Certifications</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title">Experience Highlights</h2>
|
||||
<div class="experience-highlights">
|
||||
<div v-for="exp in userProfile.experience.slice(0, 2)" :key="exp.id" class="highlight-item">
|
||||
<div class="highlight-header">
|
||||
<h3 class="highlight-title">{{ exp.title }}</h3>
|
||||
<div class="highlight-company">{{ exp.company }}</div>
|
||||
<div class="highlight-duration">
|
||||
{{ formatDate(exp.startDate) }} - {{ formatDate(exp.endDate) }}
|
||||
<span class="duration-text">({{ calculateDuration(exp.startDate, exp.endDate) }})</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="highlight-description">{{ exp.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title">Top Skills</h2>
|
||||
<div class="skills-highlights">
|
||||
<div v-for="skill in userProfile.skills.slice(0, 6)" :key="skill.name" class="skill-item">
|
||||
<div class="skill-info">
|
||||
<div class="skill-name">{{ skill.name }}</div>
|
||||
<div class="skill-level">{{ skill.level }}%</div>
|
||||
</div>
|
||||
<div class="skill-bar">
|
||||
<div class="skill-progress" :style="{ width: `${skill.level}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title">Featured Projects</h2>
|
||||
<div class="projects-highlights">
|
||||
<div v-for="project in userProfile.projects.slice(0, 2)" :key="project.id" class="project-item">
|
||||
<div class="project-image" v-if="project.image">
|
||||
<img :src="project.image" alt="Project thumbnail" />
|
||||
</div>
|
||||
<div class="project-image project-placeholder" v-else>
|
||||
<span class="project-icon">🚀</span>
|
||||
</div>
|
||||
|
||||
<div class="project-info">
|
||||
<h3 class="project-title">{{ project.title }}</h3>
|
||||
<p class="project-description">{{ project.description }}</p>
|
||||
<div class="project-technologies">
|
||||
<span v-for="(tech, index) in project.technologies" :key="index" class="tech-tag">
|
||||
{{ tech }}
|
||||
</span>
|
||||
</div>
|
||||
<a v-if="project.link" :href="project.link" target="_blank" class="project-link">
|
||||
View Project
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use 'sass:color';
|
||||
|
||||
/* 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 {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
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);
|
||||
|
||||
.profile-header {
|
||||
display: flex;
|
||||
padding: 2rem;
|
||||
border-bottom: 1px solid var(--card-border, #eee);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 2rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&.profile-avatar-initials {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--primary-color, #3498db);
|
||||
color: white;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-header-info {
|
||||
flex: 1;
|
||||
|
||||
.profile-name {
|
||||
font-size: 1.8rem;
|
||||
margin: 0 0 0.5rem;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.profile-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-color-secondary, #555);
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.profile-location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-color-secondary, #777);
|
||||
|
||||
.location-icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-contact {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.contact-icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color, #3498db);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-social {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
.social-link {
|
||||
color: var(--primary-color, #3498db);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.action-icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&.primary-button {
|
||||
background-color: var(--primary-color, #3498db);
|
||||
color: white;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background-color: color.adjust(#3498db, $lightness: -10%);
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary-button {
|
||||
background-color: transparent;
|
||||
color: var(--primary-color, #3498db);
|
||||
border: 1px solid var(--primary-color, #3498db);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(52, 152, 219, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-tabs {
|
||||
.tab-navigation {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--card-border, #eee);
|
||||
overflow-x: auto;
|
||||
background-color: var(--card-bg, #fff);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
padding: 1rem 1.5rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-weight: 500;
|
||||
color: var(--text-color-secondary, #777);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.active {
|
||||
color: var(--primary-color, #3498db);
|
||||
border-bottom-color: var(--primary-color, #3498db);
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
color: var(--text-color, #333);
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 2rem;
|
||||
|
||||
.section-title {
|
||||
font-size: 1.4rem;
|
||||
margin: 0 0 1.5rem;
|
||||
color: var(--text-color, #333);
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--card-border, #eee);
|
||||
}
|
||||
|
||||
.about-text {
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color-secondary, #555);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.overview-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
background-color: var(--card-bg-secondary, #f9f9f9);
|
||||
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;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color, #3498db);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-color-tertiary, #777);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.experience-highlights {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.highlight-item {
|
||||
padding: 1.25rem;
|
||||
background-color: var(--card-bg-secondary, #f9f9f9);
|
||||
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;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.highlight-header {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.highlight-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 0.25rem;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.highlight-company {
|
||||
font-size: 0.95rem;
|
||||
color: var(--text-color-secondary, #555);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.highlight-duration {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-color-tertiary, #999);
|
||||
|
||||
.duration-text {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.highlight-description {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text-color-secondary, #555);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skills-highlights {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.skill-item {
|
||||
.skill-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.skill-name {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.skill-level {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-color-tertiary, #777);
|
||||
}
|
||||
}
|
||||
|
||||
.skill-bar {
|
||||
height: 8px;
|
||||
background-color: var(--card-bg-secondary, #f9f9f9);
|
||||
transition: background-color var(--theme-transition-duration) var(--theme-transition-timing);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.skill-progress {
|
||||
height: 100%;
|
||||
background-color: var(--primary-color, #3498db);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.projects-highlights {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
|
||||
.project-item {
|
||||
background-color: var(--card-bg-secondary, #f9f9f9);
|
||||
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: 1px solid var(--card-border, #eee);
|
||||
overflow: hidden;
|
||||
|
||||
.project-image {
|
||||
height: 160px;
|
||||
background-color: var(--card-bg-secondary, #f9f9f9);
|
||||
transition: background-color var(--theme-transition-duration) var(--theme-transition-timing),
|
||||
border-color var(--theme-transition-duration) var(--theme-transition-timing);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&.project-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--primary-color-light, rgba(52, 152, 219, 0.1));
|
||||
|
||||
.project-icon {
|
||||
font-size: 3rem;
|
||||
color: var(--primary-color, #3498db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-info {
|
||||
padding: 1.25rem;
|
||||
|
||||
.project-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 0.75rem;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.project-description {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
color: var(--text-color-secondary, #555);
|
||||
margin-bottom: 1rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.project-technologies {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.tech-tag {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 16px;
|
||||
background-color: var(--tag-bg, rgba(52, 152, 219, 0.1));
|
||||
color: var(--primary-color, #3498db);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: var(--primary-color-light, rgba(52, 152, 219, 0.2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-link {
|
||||
display: inline-block;
|
||||
color: var(--primary-color, #3498db);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode styles
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.profile-container {
|
||||
background-color: var(--card-bg, #222);
|
||||
color: var(--text-color, #eee);
|
||||
|
||||
.profile-header {
|
||||
border-bottom-color: var(--card-border, #444);
|
||||
}
|
||||
|
||||
.profile-header-info {
|
||||
.profile-name, .profile-title {
|
||||
color: var(--text-color, #eee);
|
||||
}
|
||||
|
||||
.profile-location, .contact-item {
|
||||
color: var(--text-color-secondary, #bbb);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-navigation {
|
||||
border-bottom-color: var(--card-border, #444);
|
||||
|
||||
.tab-button {
|
||||
color: var(--text-color-secondary, #bbb);
|
||||
|
||||
&:hover:not(.active) {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: var(--text-color, #eee);
|
||||
border-bottom-color: var(--card-border, #444);
|
||||
}
|
||||
|
||||
.about-text,
|
||||
.highlight-description,
|
||||
.project-description {
|
||||
color: var(--text-color-secondary, #bbb);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-color-secondary, #bbb) !important;
|
||||
}
|
||||
|
||||
.overview-stats .stat-item,
|
||||
.experience-highlights .highlight-item,
|
||||
.tab-content .stat-item {
|
||||
background-color: var(--card-bg, #2a2a2a);
|
||||
border-color: var(--card-border, #444);
|
||||
}
|
||||
|
||||
.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-title {
|
||||
color: var(--text-color, #eee);
|
||||
}
|
||||
|
||||
.highlight-company {
|
||||
color: var(--text-color-secondary, #bbb);
|
||||
}
|
||||
}
|
||||
|
||||
.skills-highlights {
|
||||
.skill-item .skill-info {
|
||||
.skill-name {
|
||||
color: var(--text-color, #eee);
|
||||
}
|
||||
|
||||
.skill-level {
|
||||
color: var(--text-color-secondary, #bbb);
|
||||
}
|
||||
}
|
||||
|
||||
.skill-item .skill-bar {
|
||||
background-color: var(--card-bg-secondary, rgba(255, 255, 255, 0.1));
|
||||
}
|
||||
}
|
||||
|
||||
.tech-tag {
|
||||
background-color: var(--primary-color-light, rgba(52, 152, 219, 0.2));
|
||||
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>
|
||||
@@ -0,0 +1,398 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// Sample saved jobs data
|
||||
const savedJobs = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: 'Senior Frontend Developer',
|
||||
company: 'Tech Innovations Inc.',
|
||||
location: 'San Francisco, CA (Remote)',
|
||||
salary: '$140,000 - $170,000',
|
||||
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.',
|
||||
dateAdded: '2025-03-20',
|
||||
tags: ['Vue.js', 'JavaScript', 'CSS', 'Senior']
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Full Stack Engineer',
|
||||
company: 'WebSolutions Ltd.',
|
||||
location: 'New York, NY',
|
||||
salary: '$130,000 - $160,000',
|
||||
description: 'Looking for a versatile developer who can work across the entire stack. Experience with Vue.js and Node.js is a plus.',
|
||||
dateAdded: '2025-03-22',
|
||||
tags: ['Full Stack', 'Node.js', 'Vue.js', 'MongoDB']
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'UI/UX Developer',
|
||||
company: 'Digital Creations',
|
||||
location: 'Remote',
|
||||
salary: '$120,000 - $145,000',
|
||||
description: 'Join our design team to create beautiful and functional user interfaces. Strong design skills and frontend development experience required.',
|
||||
dateAdded: '2025-03-23',
|
||||
tags: ['UI/UX', 'Design', 'Frontend', 'Figma']
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Frontend Team Lead',
|
||||
company: 'InnovateTech Solutions',
|
||||
location: 'Boston, MA (Hybrid)',
|
||||
salary: '$150,000 - $180,000',
|
||||
description: 'Lead a team of frontend developers in building modern web applications. Experience with team management and modern frontend frameworks required.',
|
||||
dateAdded: '2025-03-25',
|
||||
tags: ['Team Lead', 'Management', 'Vue.js', 'React']
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'JavaScript Developer',
|
||||
company: 'CodeCraft Inc.',
|
||||
location: 'Chicago, IL',
|
||||
salary: '$110,000 - $140,000',
|
||||
description: 'Develop and maintain JavaScript applications. Strong knowledge of modern JavaScript frameworks and libraries is essential.',
|
||||
dateAdded: '2025-03-27',
|
||||
tags: ['JavaScript', 'ES6', 'Frontend', 'Backend']
|
||||
}
|
||||
]);
|
||||
|
||||
// Search and filter functionality
|
||||
const searchQuery = ref('');
|
||||
const selectedTags = ref([]);
|
||||
|
||||
// Get all unique tags
|
||||
const allTags = computed(() => {
|
||||
const tags = new Set();
|
||||
savedJobs.value.forEach(job => {
|
||||
job.tags.forEach(tag => tags.add(tag));
|
||||
});
|
||||
return Array.from(tags).sort();
|
||||
});
|
||||
|
||||
// Filter jobs based on search query and selected tags
|
||||
const filteredJobs = computed(() => {
|
||||
return savedJobs.value.filter(job => {
|
||||
// Search query filter
|
||||
const matchesSearch = searchQuery.value === '' ||
|
||||
job.title.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
job.company.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
job.description.toLowerCase().includes(searchQuery.value.toLowerCase());
|
||||
|
||||
// Tags filter
|
||||
const matchesTags = selectedTags.value.length === 0 ||
|
||||
selectedTags.value.every(tag => job.tags.includes(tag));
|
||||
|
||||
return matchesSearch && matchesTags;
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle tag selection
|
||||
const toggleTag = (tag) => {
|
||||
if (selectedTags.value.includes(tag)) {
|
||||
selectedTags.value = selectedTags.value.filter(t => t !== tag);
|
||||
} else {
|
||||
selectedTags.value.push(tag);
|
||||
}
|
||||
};
|
||||
|
||||
// Remove job from saved
|
||||
const removeJob = (jobId) => {
|
||||
savedJobs.value = savedJobs.value.filter(job => job.id !== jobId);
|
||||
};
|
||||
|
||||
// Apply for job
|
||||
const applyForJob = (jobId) => {
|
||||
console.log(`Applied for job ID: ${jobId}`);
|
||||
// Here you would typically redirect to application form or mark as applied
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="saved-jobs-container">
|
||||
<div class="saved-header">
|
||||
<div class="tags-filter">
|
||||
<div class="tags-label">Filter by tags</div>
|
||||
<div class="tags-list">
|
||||
<button
|
||||
v-for="tag in allTags"
|
||||
:key="tag"
|
||||
@click="toggleTag(tag)"
|
||||
class="tag-button"
|
||||
:class="{ 'active': selectedTags.includes(tag) }"
|
||||
>
|
||||
{{ tag }}
|
||||
</button>
|
||||
</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
|
||||
v-for="job in filteredJobs"
|
||||
:key="job.id"
|
||||
class="saved-job-card"
|
||||
>
|
||||
<div class="job-header">
|
||||
<h3 class="job-title">{{ job.title }}</h3>
|
||||
<div class="job-actions">
|
||||
<button class="remove-btn" @click="removeJob(job.id)">
|
||||
<span class="icon">❌</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="job-company">{{ job.company }}</div>
|
||||
<div class="job-details">
|
||||
<div class="job-location">📍 {{ job.location }}</div>
|
||||
<div class="job-salary">💰 {{ job.salary }}</div>
|
||||
</div>
|
||||
|
||||
<div class="job-description">{{ job.description }}</div>
|
||||
|
||||
<div class="job-tags">
|
||||
<span
|
||||
v-for="tag in job.tags"
|
||||
:key="`${job.id}-${tag}`"
|
||||
class="job-tag"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="job-footer">
|
||||
<div class="date-saved">Saved on {{ job.dateAdded }}</div>
|
||||
<button class="apply-btn" @click="applyForJob(job.id)">Apply Now</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-jobs-message" v-else>
|
||||
<div class="message-icon">🔍</div>
|
||||
<h3>No saved jobs found</h3>
|
||||
<p>Try adjusting your search or filters to see more results.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.saved-jobs-container {
|
||||
width: 100%;
|
||||
|
||||
.saved-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
.search-input {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--card-border);
|
||||
background-color: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
width: 300px;
|
||||
font-size: 0.9rem;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tags-label {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tags-filter {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.tags-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
.tag-button {
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
background-color: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
color: var(--text-color);
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.saved-jobs-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.saved-job-card {
|
||||
background-color: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.job-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.job-title {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.job-actions {
|
||||
.remove-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-company {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.job-details {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.job-description {
|
||||
margin-bottom: 1.25rem;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.job-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
|
||||
.job-tag {
|
||||
background-color: rgba(52, 152, 219, 0.1);
|
||||
color: var(--primary-color);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.job-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.date-saved {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.6rem 1.25rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-jobs-message {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
background-color: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
|
||||
.message-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user