// Application state
let appState = {
jobs: [],
currentUser: null,
favorites: [],
userJobs: [],
notifications: [],
messages: [],
currentView: 'home'
};
// DOM elements
const elements = {
// Views
homeView: document.getElementById('home-view'),
allJobsView: document.getElementById('all-jobs-view'),
favoritesView: document.getElementById('favorites-view'),
employersView: document.getElementById('employers-view'),
postJobView: document.getElementById('post-job-view'),
// Job containers
jobCards: document.getElementById('job-cards'),
allJobsCards: document.getElementById('all-jobs-cards'),
favoritesCards: document.getElementById('favorites-cards'),
employerJobsCards: document.getElementById('employer-jobs-cards'),
// Empty states
emptyFavorites: document.getElementById('empty-favorites'),
emptyEmployerJobs: document.getElementById('empty-employer-jobs'),
// Notifications and messages
notificationsView: document.getElementById('notifications-view'),
notificationsContainer: document.getElementById('notifications-container'),
emptyNotifications: document.getElementById('empty-notifications'),
messagesView: document.getElementById('messages-view'),
messagesContainer: document.getElementById('messages-container'),
emptyMessages: document.getElementById('empty-messages'),
// Badges
notificationBadge: document.getElementById('notification-badge'),
messageBadge: document.getElementById('message-badge'),
// Modals
loginModal: document.getElementById('login-modal'),
registerModal: document.getElementById('register-modal'),
// Forms
loginForm: document.getElementById('login-form'),
registerForm: document.getElementById('register-form'),
postJobForm: document.getElementById('post-job-form'),
// Toast
toast: document.getElementById('toast'),
toastMessage: document.getElementById('toast-message'),
// Counters
jobsCount: document.getElementById('jobs-count'),
favoritesCount: document.getElementById('favorites-count')
};
// Load data from localStorage on initialization
async function loadFromLocalStorage() {
try {
// Restore favorites
const savedFavorites = localStorage.getItem('jobHubFavorites');
if (savedFavorites) {
appState.favorites = JSON.parse(savedFavorites);
}
// Restore user jobs
const savedUserJobs = localStorage.getItem('jobHubUserJobs');
if (savedUserJobs) {
appState.userJobs = JSON.parse(savedUserJobs);
}
// Restore notifications
const savedNotifications = localStorage.getItem('jobHubNotifications');
if (savedNotifications) {
appState.notifications = JSON.parse(savedNotifications);
}
// Restore messages
const savedMessages = localStorage.getItem('jobHubMessages');
if (savedMessages) {
appState.messages = JSON.parse(savedMessages);
}
// Restore current user
const savedUser = localStorage.getItem('jobHubCurrentUser');
if (savedUser) {
appState.currentUser = JSON.parse(savedUser);
// If user exists, reload their data from server
if (appState.currentUser && appState.currentUser.id) {
await loadUserFavorites(appState.currentUser.id);
if (appState.currentUser.type === 'employer') {
await loadUserJobs(appState.currentUser.id);
}
// Load notifications and messages from server
await loadUserNotifications(appState.currentUser.id);
await loadUserMessages(appState.currentUser.id);
}
}
console.log('Data loaded from localStorage');
} catch (error) {
console.error('Error loading from localStorage:', error);
}
}
// Save data to localStorage
function saveToLocalStorage() {
try {
localStorage.setItem('jobHubFavorites', JSON.stringify(appState.favorites));
localStorage.setItem('jobHubUserJobs', JSON.stringify(appState.userJobs));
localStorage.setItem('jobHubNotifications', JSON.stringify(appState.notifications));
localStorage.setItem('jobHubMessages', JSON.stringify(appState.messages));
if (appState.currentUser) {
localStorage.setItem('jobHubCurrentUser', JSON.stringify(appState.currentUser));
} else {
localStorage.removeItem('jobHubCurrentUser');
}
console.log('Data saved to localStorage');
} catch (error) {
console.error('Error saving to localStorage:', error);
}
}
// Initialize the app
async function initApp() {
await loadJobsFromServer();
updateCounters();
updateNotificationBadge();
updateMessageBadge();
switchView('home');
updateAuthButtons();
// If user is logged in, make sure their data is properly loaded
if (appState.currentUser) {
if (appState.currentUser.id) {
await loadUserFavorites(appState.currentUser.id);
if (appState.currentUser.type === 'employer') {
await loadUserJobs(appState.currentUser.id);
}
}
}
}
// Load jobs from server
async function loadJobsFromServer() {
try {
const response = await fetch('/api/jobs');
if (response.ok) {
appState.jobs = await response.json();
} else {
console.error('Failed to load jobs from server');
// Fallback to sample data if server is not available
appState.jobs = getSampleJobs();
}
} catch (error) {
console.error('Error loading jobs:', error);
appState.jobs = getSampleJobs();
}
}
// Get sample jobs for fallback
function getSampleJobs() {
return [
{
id: 1,
title: "Senior Frontend Разработчик",
company: "TechNova",
location: "Москва",
salary: "180 000 - 250 000 ₽",
description: "Требуется опытный frontend разработчик для работы с современным стеком: React, TypeScript, Next.js. Опыт работы от 5 лет, знание Redux, GraphQL будет плюсом.",
tags: ["React", "TypeScript", "Next.js", "Redux", "GraphQL"],
type: "full-time"
},
{
id: 2,
title: "Backend Разработчик (Node.js)",
company: "CloudSolutions",
location: "Санкт-Петербург",
salary: "200 000 - 280 000 ₽",
description: "Ищем backend разработчика с опытом работы с Node.js, Express и микросервисной архитектурой. Знание Docker, Kubernetes и AWS обязательно.",
tags: ["Node.js", "Express", "Docker", "Kubernetes", "AWS"],
type: "full-time"
},
{
id: 3,
title: "Full Stack Разработчик",
company: "DigitalAgency Pro",
location: "Екатеринбург",
salary: "150 000 - 220 000 ₽",
description: "Нужен full stack разработчик для работы над веб-приложениями. Опыт с React и Node.js обязателен. Знание TypeScript будет преимуществом.",
tags: ["React", "Node.js", "TypeScript", "Full Stack"],
type: "full-time"
},
{
id: 4,
title: "Python Разработчик (ML)",
company: "DataScience Inc",
location: "Новосибирск",
salary: "160 000 - 240 000 ₽",
description: "Требуется Python разработчик для работы с данными и машинным обучением. Опыт с Django/Flask, TensorFlow/PyTorch обязателен.",
tags: ["Python", "Django", "Machine Learning", "TensorFlow"],
type: "full-time"
},
{
id: 5,
title: "DevOps Инженер",
company: "CloudTech",
location: "Казань",
salary: "220 000 - 300 000 ₽",
description: "Ищем DevOps инженера с опытом работы с AWS, Kubernetes и CI/CD. Знание Terraform и Ansible будет плюсом.",
tags: ["DevOps", "AWS", "Kubernetes", "CI/CD"],
type: "full-time"
},
{
id: 6,
title: "UI/UX Дизайнер",
company: "CreativeMinds",
location: "Москва",
salary: "120 000 - 180 000 ₽",
description: "Требуется UI/UX дизайнер для создания пользовательских интерфейсов. Опыт с Figma, Adobe XD обязателен.",
tags: ["UI/UX", "Figma", "Adobe XD", "Design"],
type: "full-time"
}
];
}
// Display jobs
function displayJobs(jobs, container) {
container.innerHTML = '';
if (jobs.length === 0) {
container.innerHTML = `
🔍
Вакансий не найдено
Попробуйте изменить параметры поиска
`;
return;
}
jobs.forEach(job => {
const jobCard = document.createElement('div');
jobCard.className = 'job-card';
// Check if job is favorited
const isFavorited = appState.favorites.some(fav => fav.id === job.id);
jobCard.innerHTML = `
${job.description}
${job.tags.map(tag => `${tag}`).join('')}
`;
container.appendChild(jobCard);
});
}
// Switch between views
function switchView(view) {
// Hide all views
document.querySelectorAll('.view').forEach(v => v.classList.add('hidden'));
document.querySelectorAll('nav a').forEach(a => a.classList.remove('active'));
// Show selected view
const viewElement = document.getElementById(`${view}-view`);
if (viewElement) {
viewElement.classList.remove('hidden');
appState.currentView = view;
// Update active nav link
const navLink = document.querySelector(`nav a[onclick="switchView('${view}')"]`);
if (navLink) {
navLink.classList.add('active');
}
// Handle specific view logic
switch (view) {
case 'home':
displayJobs(appState.jobs.slice(0, 6), elements.jobCards);
break;
case 'all-jobs':
displayJobs(appState.jobs, elements.allJobsCards);
elements.jobsCount.textContent = `${appState.jobs.length} вакансий`;
break;
case 'favorites':
displayFavorites();
break;
case 'notifications':
displayNotifications();
break;
case 'messages':
displayMessages();
break;
case 'employers':
displayEmployerJobs();
break;
case 'post-job':
// Clear form
elements.postJobForm.reset();
break;
}
}
}
// Filter jobs based on search criteria
function filterJobs() {
const jobSearchTerm = document.getElementById('job-search').value.trim().toLowerCase();
const locationSearchTerm = document.getElementById('location-search').value.trim().toLowerCase();
let filteredJobs = appState.jobs;
// Filter by job title, description, or tags if search term is provided
if (jobSearchTerm) {
filteredJobs = filteredJobs.filter(job => {
return job.title.toLowerCase().includes(jobSearchTerm) ||
job.description.toLowerCase().includes(jobSearchTerm) ||
job.tags.some(tag => tag.toLowerCase().includes(jobSearchTerm));
});
}
// Filter by location if location term is provided
if (locationSearchTerm) {
filteredJobs = filteredJobs.filter(job => {
return job.location.toLowerCase().includes(locationSearchTerm);
});
}
// Sort results by relevance (prioritize matches in title)
filteredJobs.sort((a, b) => {
const aTitleMatch = a.title.toLowerCase().includes(jobSearchTerm);
const bTitleMatch = b.title.toLowerCase().includes(jobSearchTerm);
if (aTitleMatch && !bTitleMatch) return -1;
if (!aTitleMatch && bTitleMatch) return 1;
return 0;
});
if (appState.currentView === 'home') {
displayJobs(filteredJobs.slice(0, 6), elements.jobCards);
} else if (appState.currentView === 'all-jobs') {
displayJobs(filteredJobs, elements.allJobsCards);
elements.jobsCount.textContent = `${filteredJobs.length} вакансий`;
}
}
// Advanced search functionality
function advancedFilterJobs(filters) {
let filteredJobs = appState.jobs;
// Apply keyword filter
if (filters.keyword) {
const keyword = filters.keyword.toLowerCase();
filteredJobs = filteredJobs.filter(job => {
return job.title.toLowerCase().includes(keyword) ||
job.description.toLowerCase().includes(keyword) ||
job.tags.some(tag => tag.toLowerCase().includes(keyword));
});
}
// Apply location filter
if (filters.location) {
const location = filters.location.toLowerCase();
filteredJobs = filteredJobs.filter(job => {
return job.location.toLowerCase().includes(location);
});
}
// Apply salary range filter
if (filters.minSalary || filters.maxSalary) {
filteredJobs = filteredJobs.filter(job => {
// Extract numeric salary values for comparison
const salaryText = job.salary.replace(/[^\d\s]/g, '').trim();
if (!salaryText) return true; // If no salary info, include by default
const salaryNumbers = salaryText.match(/\d+/g);
if (!salaryNumbers) return true;
const avgSalary = salaryNumbers.reduce((sum, num) => sum + parseInt(num), 0) / salaryNumbers.length;
if (filters.minSalary && avgSalary < filters.minSalary) return false;
if (filters.maxSalary && avgSalary > filters.maxSalary) return false;
return true;
});
}
// Apply job type filter
if (filters.jobType && filters.jobType !== 'all') {
filteredJobs = filteredJobs.filter(job => job.type === filters.jobType);
}
// Apply tags filter
if (filters.tags && filters.tags.length > 0) {
filteredJobs = filteredJobs.filter(job => {
return filters.tags.every(tag =>
job.tags.some(jobTag =>
jobTag.toLowerCase().includes(tag.toLowerCase())
)
);
});
}
// Sort results by relevance
if (filters.keyword) {
filteredJobs.sort((a, b) => {
const keyword = filters.keyword.toLowerCase();
const aTitleMatch = a.title.toLowerCase().includes(keyword);
const bTitleMatch = b.title.toLowerCase().includes(keyword);
if (aTitleMatch && !bTitleMatch) return -1;
if (!aTitleMatch && bTitleMatch) return 1;
return 0;
});
}
return filteredJobs;
}
// Debounced search function to improve performance
let searchTimeout;
function debouncedSearch() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
filterJobs();
}, 300); // Wait 300ms after user stops typing
}
// Display favorites
function displayFavorites() {
if (appState.favorites.length === 0) {
elements.favoritesCards.innerHTML = '';
elements.emptyFavorites.classList.remove('hidden');
} else {
elements.emptyFavorites.classList.add('hidden');
displayJobs(appState.favorites, elements.favoritesCards);
}
updateCounters();
}
// Display notifications
async function displayNotifications() {
// Load notifications from server if user is logged in
if (appState.currentUser && appState.currentUser.id) {
try {
const response = await fetch(`/api/users/${appState.currentUser.id}/notifications`);
if (response.ok) {
const serverNotifications = await response.json();
// Update local state with server data
appState.notifications = serverNotifications.map(n => ({
id: n.id,
title: n.title,
text: n.text,
icon: n.icon,
timestamp: n.created_at,
read: n.read_status
}));
}
} catch (error) {
console.error('Error loading notifications from server:', error);
}
}
elements.notificationsContainer.innerHTML = '';
if (appState.notifications.length === 0) {
elements.emptyNotifications.classList.remove('hidden');
return;
}
elements.emptyNotifications.classList.add('hidden');
appState.notifications.forEach(notification => {
const notificationItem = document.createElement('div');
notificationItem.className = `notification-item ${!notification.read ? 'unread' : ''}`;
notificationItem.innerHTML = `
${notification.icon || '🔔'}
${notification.title}
${notification.text}
${formatTimeAgo(notification.timestamp)}
`;
// Mark as read when displayed
if (!notification.read) {
notification.read = true;
updateNotificationBadge();
// Update server as well
if (appState.currentUser && appState.currentUser.id) {
fetch(`/api/users/${appState.currentUser.id}/notifications/${notification.id}/read`, {
method: 'PUT'
}).catch(console.error);
}
}
elements.notificationsContainer.appendChild(notificationItem);
});
}
// Display messages
async function displayMessages() {
// Load messages from server if user is logged in
if (appState.currentUser && appState.currentUser.id) {
try {
const response = await fetch(`/api/users/${appState.currentUser.id}/messages`);
if (response.ok) {
const serverMessages = await response.json();
// Update local state with server data
appState.messages = serverMessages.map(m => ({
id: m.id,
sender: {
name: m.sender_name || 'Unknown',
avatar: '👤'
},
content: m.content,
timestamp: m.created_at,
read: m.read_status
}));
}
} catch (error) {
console.error('Error loading messages from server:', error);
}
}
elements.messagesContainer.innerHTML = '';
if (appState.messages.length === 0) {
elements.emptyMessages.classList.remove('hidden');
return;
}
elements.emptyMessages.classList.add('hidden');
appState.messages.forEach(message => {
const messageItem = document.createElement('div');
messageItem.className = `message-item ${!message.read ? 'unread' : ''}`;
messageItem.innerHTML = `
${message.sender.avatar || '👤'}
${message.sender.name}
${message.content}
${formatTimeAgo(message.timestamp)}
`;
// Mark as read when displayed
if (!message.read) {
message.read = true;
updateMessageBadge();
// Update server as well
if (appState.currentUser && appState.currentUser.id) {
fetch(`/api/users/${appState.currentUser.id}/messages/${message.id}/read`, {
method: 'PUT'
}).catch(console.error);
}
}
elements.messagesContainer.appendChild(messageItem);
});
}
// Add notification
async function addNotification(title, text, icon = '🔔') {
const notification = {
id: Date.now(),
title: title,
text: text,
icon: icon,
timestamp: new Date().toISOString(),
read: false
};
// Add to local state
appState.notifications.unshift(notification);
updateNotificationBadge();
saveToLocalStorage();
// Try to add to server if user is logged in
if (appState.currentUser && appState.currentUser.id) {
try {
await fetch(`/api/users/${appState.currentUser.id}/notifications`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ title, text, icon })
});
} catch (error) {
console.error('Error adding notification to server:', error);
}
}
// Show toast notification as well
showToast(text);
}
// Add message
async function addMessage(sender, content) {
const message = {
id: Date.now(),
sender: sender,
content: content,
timestamp: new Date().toISOString(),
read: false
};
appState.messages.unshift(message);
updateMessageBadge();
saveToLocalStorage();
// Show notification about new message
await addNotification('Новое сообщение', `Сообщение от ${sender.name}`, '💬');
}
// Format time ago
function formatTimeAgo(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const seconds = Math.floor((now - date) / 1000);
if (seconds < 60) return 'только что';
if (seconds < 3600) return `${Math.floor(seconds / 60)} мин назад`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)} ч назад`;
return `${Math.floor(seconds / 86400)} дн назад`;
}
// Update notification badge
function updateNotificationBadge() {
const unreadCount = appState.notifications.filter(n => !n.read).length;
if (unreadCount > 0) {
elements.notificationBadge.textContent = unreadCount;
elements.notificationBadge.classList.remove('hidden');
} else {
elements.notificationBadge.classList.add('hidden');
}
}
// Update message badge
function updateMessageBadge() {
const unreadCount = appState.messages.filter(m => !m.read).length;
if (unreadCount > 0) {
elements.messageBadge.textContent = unreadCount;
elements.messageBadge.classList.remove('hidden');
} else {
elements.messageBadge.classList.add('hidden');
}
}
// Clear all notifications
async function clearNotifications() {
appState.notifications = [];
updateNotificationBadge();
saveToLocalStorage();
// Clear notifications on server if user is logged in
if (appState.currentUser && appState.currentUser.id) {
try {
await fetch(`/api/users/${appState.currentUser.id}/notifications`, {
method: 'DELETE'
});
} catch (error) {
console.error('Error clearing notifications from server:', error);
}
}
displayNotifications();
}
// Reply to message
function replyToMessage(messageId) {
if (!appState.currentUser) {
showToast('Для ответа на сообщение нужно авторизоваться');
showLoginModal();
return;
}
// For a real implementation, we would need to know who sent the original message
// For now, we'll just allow sending a message to another user by ID
const recipientId = prompt('Введите ID получателя (для тестирования используйте ID другого пользователя):');
if (recipientId) {
const reply = prompt('Введите ваш ответ:');
if (reply) {
sendMessageToUser(parseInt(recipientId), reply);
}
}
}
// Send message to user
async function sendMessageToUser(receiverId, content) {
if (!appState.currentUser || !appState.currentUser.id) {
showToast('Для отправки сообщения нужно авторизоваться');
showLoginModal();
return;
}
try {
const response = await fetch('/api/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
sender_id: appState.currentUser.id,
receiver_id: receiverId,
content: content
})
});
if (response.ok) {
showToast('Сообщение отправлено');
} else {
const errorData = await response.json();
showToast(errorData.error || 'Ошибка отправки сообщения');
}
} catch (error) {
console.error('Error sending message:', error);
showToast('Ошибка отправки сообщения');
}
}
// Delete message
async function deleteMessage(messageId) {
appState.messages = appState.messages.filter(m => m.id !== messageId);
updateMessageBadge();
saveToLocalStorage();
// Delete message on server if user is logged in
if (appState.currentUser && appState.currentUser.id) {
try {
await fetch(`/api/users/${appState.currentUser.id}/messages/${messageId}`, {
method: 'DELETE'
});
} catch (error) {
console.error('Error deleting message from server:', error);
}
}
displayMessages();
}
// Display employer's jobs
function displayEmployerJobs() {
if (appState.userJobs.length === 0) {
elements.employerJobsCards.innerHTML = '';
elements.emptyEmployerJobs.classList.remove('hidden');
} else {
elements.emptyEmployerJobs.classList.add('hidden');
displayJobs(appState.userJobs, elements.employerJobsCards);
}
}
// Update counters
function updateCounters() {
elements.favoritesCount.textContent = `${appState.favorites.length} избранных`;
}
// Show login modal
function showLoginModal() {
elements.loginModal.classList.add('active');
}
// Hide login modal
function hideLoginModal() {
elements.loginModal.classList.remove('active');
elements.loginForm.reset();
}
// Show register modal
function showRegisterModal() {
elements.registerModal.classList.add('active');
}
// Hide register modal
function hideRegisterModal() {
elements.registerModal.classList.remove('active');
elements.registerForm.reset();
}
// Handle login
async function handleLogin(e) {
e.preventDefault();
const email = document.getElementById('login-email').value;
const password = document.getElementById('login-password').value;
const userType = document.getElementById('login-type').value;
// Simple validation
if (!email || !password) {
showToast('Пожалуйста, заполните все поля');
return;
}
try {
// Try to login via server API
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
if (response.ok) {
const userData = await response.json();
appState.currentUser = {
id: userData.id,
name: userData.name,
email: userData.email,
type: userData.type
};
// Load user's favorites, jobs, notifications and messages
await loadUserFavorites(userData.id);
if (userData.type === 'employer') {
await loadUserJobs(userData.id);
}
await loadUserNotifications(userData.id);
await loadUserMessages(userData.id);
hideLoginModal();
showToast(`Добро пожаловать, ${appState.currentUser.name}!`);
updateAuthButtons();
saveToLocalStorage();
} else {
// Fallback to client-side login simulation
appState.currentUser = {
name: 'Пользователь',
email: email,
type: userType
};
hideLoginModal();
showToast(`Добро пожаловать, ${appState.currentUser.name}!`);
updateAuthButtons();
saveToLocalStorage();
}
} catch (error) {
console.error('Login error:', error);
// Fallback to client-side login simulation
appState.currentUser = {
name: 'Пользователь',
email: email,
type: userType
};
hideLoginModal();
showToast(`Добро пожаловать, ${appState.currentUser.name}!`);
updateAuthButtons();
saveToLocalStorage();
}
}
// Load user favorites from server
async function loadUserFavorites(userId) {
try {
const response = await fetch(`/api/users/${userId}/favorites`);
if (response.ok) {
appState.favorites = await response.json();
}
} catch (error) {
console.error('Error loading favorites:', error);
}
}
// Load user jobs from server
async function loadUserJobs(userId) {
try {
const response = await fetch(`/api/users/${userId}/jobs`);
if (response.ok) {
appState.userJobs = await response.json();
}
} catch (error) {
console.error('Error loading user jobs:', error);
}
}
// Load user notifications from server
async function loadUserNotifications(userId) {
try {
const response = await fetch(`/api/users/${userId}/notifications`);
if (response.ok) {
const serverNotifications = await response.json();
appState.notifications = serverNotifications.map(n => ({
id: n.id,
title: n.title,
text: n.text,
icon: n.icon,
timestamp: n.created_at,
read: n.read_status
}));
updateNotificationBadge();
}
} catch (error) {
console.error('Error loading user notifications:', error);
}
}
// Load user messages from server
async function loadUserMessages(userId) {
try {
const response = await fetch(`/api/users/${userId}/messages`);
if (response.ok) {
const serverMessages = await response.json();
appState.messages = serverMessages.map(m => ({
id: m.id,
sender: {
name: m.sender_name || 'Unknown',
avatar: '👤'
},
content: m.content,
timestamp: m.created_at,
read: m.read_status
}));
updateMessageBadge();
}
} catch (error) {
console.error('Error loading user messages:', error);
}
}
// Handle registration
async function handleRegister(e) {
e.preventDefault();
const name = document.getElementById('register-name').value;
const email = document.getElementById('register-email').value;
const password = document.getElementById('register-password').value;
const userType = document.getElementById('register-type').value;
// Simple validation
if (!name || !email || !password) {
showToast('Пожалуйста, заполните все поля');
return;
}
try {
// Try to register via server API
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, email, password, type: userType })
});
if (response.ok) {
const userData = await response.json();
appState.currentUser = {
id: userData.id,
name: userData.name,
email: userData.email,
type: userData.type
};
// Initialize empty notifications and messages arrays for new user
appState.notifications = [];
appState.messages = [];
hideRegisterModal();
showToast(`Регистрация успешна! Добро пожаловать, ${name}!`);
updateAuthButtons();
saveToLocalStorage();
} else {
const errorData = await response.json();
showToast(errorData.error || 'Ошибка регистрации');
}
} catch (error) {
console.error('Registration error:', error);
// Fallback to client-side registration simulation
appState.currentUser = {
name: name,
email: email,
type: userType
};
// Initialize empty notifications and messages arrays for new user
appState.notifications = [];
appState.messages = [];
hideRegisterModal();
showToast(`Регистрация успешна! Добро пожаловать, ${name}!`);
updateAuthButtons();
saveToLocalStorage();
}
}
// Handle post job
async function handlePostJob(e) {
e.preventDefault();
if (!appState.currentUser || appState.currentUser.type !== 'employer') {
showToast('Только работодатели могут размещать вакансии');
return;
}
const job = {
title: document.getElementById('job-title').value,
company: document.getElementById('job-company').value,
location: document.getElementById('job-location').value,
salary: document.getElementById('job-salary').value || 'По договоренности',
description: document.getElementById('job-description').value,
tags: document.getElementById('job-tags').value.split(',').map(tag => tag.trim()).filter(tag => tag),
type: document.getElementById('job-type').value,
user_id: appState.currentUser.id
};
try {
// Try to post job via server API
const response = await fetch('/api/jobs', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(job)
});
if (response.ok) {
const newJob = await response.json();
// Add to jobs and user jobs
appState.jobs.unshift(newJob);
appState.userJobs.unshift(newJob);
showToast('Вакансия успешно размещена!');
// Add notification about successful job posting
addNotification('Вакансия опубликована', `Ваша вакансия "${job.title}" успешно опубликована`, '✅');
elements.postJobForm.reset();
switchView('employers');
saveToLocalStorage();
} else {
const errorData = await response.json();
showToast(errorData.error || 'Ошибка размещения вакансии');
}
} catch (error) {
console.error('Error posting job:', error);
// Fallback to client-side addition
job.id = Date.now();
job.created_at = new Date().toISOString();
// Add to jobs and user jobs
appState.jobs.unshift(job);
appState.userJobs.unshift(job);
showToast('Вакансия успешно размещена!');
// Add notification about successful job posting
addNotification('Вакансия опубликована', `Ваша вакансия "${job.title}" успешно опубликована`, '✅');
elements.postJobForm.reset();
switchView('employers');
saveToLocalStorage();
}
}
// Toggle favorite
async function toggleFavorite(jobId) {
if (!appState.currentUser) {
showToast('Для добавления в избранное нужно авторизоваться');
showLoginModal();
return;
}
const job = appState.jobs.find(j => j.id === jobId);
if (!job) return;
const isFavorite = appState.favorites.some(fav => fav.id === jobId);
try {
if (isFavorite) {
// Remove from favorites via server API
const response = await fetch(`/api/users/${appState.currentUser.id}/favorites/${jobId}`, {
method: 'DELETE'
});
if (response.ok || response.status === 404) {
appState.favorites = appState.favorites.filter(fav => fav.id !== jobId);
showToast('Вакансия удалена из избранного');
// Add notification
addNotification('Избранное обновлено', `Вакансия "${job.title}" удалена из избранного`, '🗑️');
} else {
throw new Error('Failed to remove from favorites');
}
} else {
// Add to favorites via server API
const response = await fetch(`/api/users/${appState.currentUser.id}/favorites`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ jobId })
});
if (response.ok) {
appState.favorites.push(job);
showToast('Вакансия добавлена в избранное');
// Add notification
addNotification('Избранное обновлено', `Вакансия "${job.title}" добавлена в избранное`, '❤️');
} else {
throw new Error('Failed to add to favorites');
}
}
} catch (error) {
console.error('Favorite toggle error:', error);
// Fallback to client-side toggle
if (isFavorite) {
appState.favorites = appState.favorites.filter(fav => fav.id !== jobId);
showToast('Вакансия удалена из избранного');
// Add notification
addNotification('Избранное обновлено', `Вакансия "${job.title}" удалена из избранного`, '🗑️');
} else {
appState.favorites.push(job);
showToast('Вакансия добавлена в избранное');
// Add notification
addNotification('Избранное обновлено', `Вакансия "${job.title}" добавлена в избранное`, '❤️');
}
}
// Update UI
if (appState.currentView === 'home') {
displayJobs(appState.jobs.slice(0, 6), elements.jobCards);
} else if (appState.currentView === 'all-jobs') {
displayJobs(appState.jobs, elements.allJobsCards);
} else if (appState.currentView === 'favorites') {
displayFavorites();
}
updateCounters();
saveToLocalStorage();
}
// Apply for job
function applyForJob(jobId) {
if (!appState.currentUser) {
showToast('Для отклика на вакансию нужно авторизоваться');
showLoginModal();
return;
}
const job = appState.jobs.find(j => j.id === jobId);
if (job) {
showToast('Ваш отклик отправлен работодателю!');
// Add notification about job application
addNotification('Отклик отправлен', `Ваш отклик на вакансию "${job.title}" отправлен работодателю`, '📤');
} else {
showToast('Ваш отклик отправлен работодателю!');
}
// Here would be the logic to actually submit an application
// For now, we'll just show a success message and add a notification
}
// Update auth buttons
function updateAuthButtons() {
const authButtons = document.getElementById('auth-buttons');
if (appState.currentUser) {
authButtons.innerHTML = `
Привет, ${appState.currentUser.name}! (${appState.currentUser.type === 'employer' ? 'Работодатель' : 'Соискатель'})
`;
} else {
authButtons.innerHTML = `
`;
}
}
// Handle logout
function handleLogout() {
appState.currentUser = null;
appState.favorites = [];
appState.userJobs = [];
appState.notifications = [];
appState.messages = [];
updateAuthButtons();
updateNotificationBadge();
updateMessageBadge();
showToast('Вы успешно вышли из аккаунта');
switchView('home');
saveToLocalStorage();
// Also remove from localStorage
localStorage.removeItem('jobHubCurrentUser');
localStorage.removeItem('jobHubFavorites');
localStorage.removeItem('jobHubUserJobs');
localStorage.removeItem('jobHubNotifications');
localStorage.removeItem('jobHubMessages');
}
// Show toast notification
function showToast(message) {
elements.toastMessage.textContent = message;
elements.toast.classList.add('active');
setTimeout(() => {
elements.toast.classList.remove('active');
}, 3000);
}
// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', async function() {
await loadFromLocalStorage();
await initApp();
// Add event listeners for search inputs
document.getElementById('job-search').addEventListener('input', debouncedSearch);
document.getElementById('location-search').addEventListener('input', debouncedSearch);
// Add event listeners for Enter key submission
document.getElementById('job-search').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
filterJobs();
}
});
document.getElementById('location-search').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
filterJobs();
}
});
});