Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions .eslintrc.js

This file was deleted.

3 changes: 0 additions & 3 deletions .babelrc.cjs

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ dist-ssr
*.sln
*.sw?
._*

# Ignore the files generated by Intlayer
.intlayer
14 changes: 14 additions & 0 deletions apps/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dotenv from 'dotenv';
import path from "path";
import { fileURLToPath } from "url"
import cors from 'cors';
import paymentRoutes from './routes/payments.js';
import jobsRoutes from './routes/jobs.js';
import citiesRoutes from './routes/cities.js';
Expand All @@ -27,6 +28,19 @@
const app = express();
const PORT = process.env.PORT || 3001;

// CORS configuration
app.use(cors({
origin: [
'http://localhost:3000',

Check notice

Code scanning / devskim

Accessing localhost could indicate debug code, or could hinder scaling. Note

Do not leave debug code in production
'http://localhost:3001',

Check notice

Code scanning / devskim

Accessing localhost could indicate debug code, or could hinder scaling. Note

Do not leave debug code in production
'https://worknow.co.il',
'https://www.worknow.co.il'
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'x-intlayer-locale']
}));

// Проверяем важные переменные окружения
if (!process.env.DATABASE_URL) {
console.error('❌ Missing DATABASE_URL!');
Expand Down
26 changes: 0 additions & 26 deletions apps/client/src/18n.ts

This file was deleted.

50 changes: 30 additions & 20 deletions apps/client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { useMemo, Suspense, useEffect } from "react";
import { Toaster } from "react-hot-toast";
import { ClerkProvider } from "@clerk/clerk-react";
import { baseTheme } from "@clerk/themes";
import { ruRU, enUS, heIL, arSA, ukUA } from "@clerk/localizations";
import { RouterProvider, Outlet, createBrowserRouter } from "react-router-dom";
import useLanguageStore from "./store/languageStore.ts";
import { HelmetProvider, Helmet } from "react-helmet-async"; // 🔹 SEO
import { ImageUploadProvider } from "./contexts/ImageUploadContext.jsx";
import { LoadingProvider } from "./contexts/LoadingContext.jsx";
import { IntlayerProvider, useIntlayer, useLocale } from "react-intlayer";
import { useI18nHTMLAttributes } from "./hooks/useI18nHTMLAttributes";
import ProgressBar from "./components/ui/ProgressBar.jsx";
import Home from "./pages/Home.jsx";
import MyAds from "./pages/MyAds.jsx";
Expand All @@ -25,7 +27,6 @@ import SeekerDetails from "./pages/SeekerDetails.jsx";
import PremiumPage from "./components/PremiumPage.jsx";
import { Navbar } from "./components/Navbar.jsx";
import { Footer } from "./components/Footer.jsx";
import "./18n.ts";
import "./css/ripple.css";
import CancelSubscription from "./components/CancelSubscription.jsx";
import BillingPage from "./components/BillingPage.jsx";
Expand Down Expand Up @@ -109,12 +110,23 @@ if (!PUBLISHABLE_KEY || !PUBLISHABLE_KEY.startsWith('pk_')) {
console.error('❌ Ошибка: Некорректный или отсутствует VITE_CLERK_PUBLISHABLE_KEY! Проверьте .env и перезапустите dev-сервер.');
}

const App = () => {
const localization = useLanguageStore((state) => state.localization);
const loading = useLanguageStore((state) => state.loading);
const currentLang = useLanguageStore((state) => state.language) || 'ru';
// Clerk localization mapping
const clerkLocalizationMap = {
'ru': ruRU,
'en': enUS,
'he': heIL,
'ar': arSA,
'uk': ukUA,
};

const memoizedLocalization = useMemo(() => localization ?? {}, [localization]);
const AppContent = () => {
const { locale } = useLocale();

// Apply the hook to update the <html> tag's lang and dir attributes based on the locale.
useI18nHTMLAttributes();

// Get the appropriate Clerk localization based on current locale
const clerkLocalization = clerkLocalizationMap[locale] || ruRU; // Default to Russian

// Initialize Google Analytics on app load
useEffect(() => {
Expand Down Expand Up @@ -146,28 +158,18 @@ const App = () => {
return <div style={{color: 'red', fontWeight: 'bold', padding: 24}}>❌ Ошибка: Некорректный или отсутствует VITE_CLERK_PUBLISHABLE_KEY!<br/>Проверьте .env и перезапустите dev-сервер.<br/>Текущий ключ: <code>{String(PUBLISHABLE_KEY)}</code></div>;
}

if (loading) {
return <div style={{display: 'flex', justifyContent: 'center', alignItems: 'center', height: '60vh'}}>
<div className="ripple">
<div></div>
<div></div>
</div>
</div>;
}

return (
<ClerkProvider
appearance={{ baseTheme: baseTheme }}
publishableKey={PUBLISHABLE_KEY}
afterSignOutUrl="/"
localization={memoizedLocalization}
locale={currentLang}
localization={clerkLocalization}
>
<LoadingProvider>
<ImageUploadProvider>
<HelmetProvider>
{/* 🔹 Глобальная SEO-оптимизация */}
<Helmet>
{/* 🔹 Глобальная SEO-оптимизация */}
<Helmet>
<title>WorkNow – Работа в Израиле | Поиск вакансий</title>
<meta name="description" content="Найти работу в Израиле стало проще! Поиск свежих вакансий в Тель-Авиве, Иерусалиме, Хайфе. Начните карьеру с WorkNow!" />
<meta name="keywords" content="работа в Израиле, вакансии в Израиле, поиск работы Израиль, работа Тель-Авив, работа Хайфа" />
Expand Down Expand Up @@ -241,4 +243,12 @@ const App = () => {
);
};

const App = () => {
return (
<IntlayerProvider>
<AppContent />
</IntlayerProvider>
);
};

export default App;
32 changes: 16 additions & 16 deletions apps/client/src/components/BillingPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { useClerk } from "@clerk/clerk-react";
import axios from "axios";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useTranslation } from 'react-i18next';
import { useIntlayer } from "react-intlayer";

const API_URL = import.meta.env.VITE_API_URL;
const PAGE_SIZE = 10;

const BillingPage = () => {
const { t } = useTranslation();
const content = useIntlayer("billingPage");
const { user } = useUser();
const { redirectToSignIn } = useClerk();
const [history, setHistory] = useState([]);
Expand All @@ -32,7 +32,7 @@ const BillingPage = () => {
setHasNext((res.data.payments || []).length === PAGE_SIZE);
setHasPrev(page > 0);
} catch {
setError("Ошибка загрузки истории платежей");
setError(content.error_loading_history);
setHistory([]);
setHasNext(false);
setHasPrev(page > 0);
Expand Down Expand Up @@ -67,40 +67,40 @@ const BillingPage = () => {

return (
<div className="container" style={{ maxWidth: 600, margin: '0 auto', paddingTop: 60 }}>
<h2 className="text-center mb-4">Выставление счетов</h2>
<h2 className="text-center mb-4">{content.billing_title}</h2>
<div className="mb-4 text-center">
<Link to="/cancel-subscription" className="text-secondary" style={{ textDecoration: 'underline', cursor: 'pointer' }}>
Отмена подписки
{content.cancel_subscription}
</Link>
</div>
<h5 className="mb-3">История платежей</h5>
<h5 className="mb-3">{content.payment_history}</h5>
{!user ? (
<div className="alert alert-info text-center">
Нет истории транзакций. Пожалуйста,{' '}
{content.no_transaction_history}{' '}
<button
className="btn btn-primary ms-2"
onClick={() => redirectToSignIn()}
type="button"
>
Войдите на сайт
{content.sign_in}
</button>
</div>
) : loading ? (
<div>{t('loading')}</div>
<div>{content.loading}</div>
) : error ? (
<div className="alert alert-danger">{error}</div>
) : history.length === 0 ? (
<div className="alert alert-info">Нет платежей</div>
<div className="alert alert-info">{content.no_payments}</div>
) : (
<>
<div className="table-responsive">
<table className="table table-striped table-hover table-bordered shadow rounded text-center align-middle">
<thead className="table-primary">
<tr>
<th className="align-middle">Месяц</th>
<th className="align-middle">Сумма</th>
<th className="align-middle">Тип подписки</th>
<th className="align-middle">Дата</th>
<th className="align-middle">{content.month}</th>
<th className="align-middle">{content.amount}</th>
<th className="align-middle">{content.subscription_type}</th>
<th className="align-middle">{content.date}</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -131,12 +131,12 @@ const BillingPage = () => {
<ul className="pagination mb-0">
<li className="page-item">
<button className="page-link" onClick={handlePrev} disabled={!hasPrev}>
&laquo; Предыдущая
&laquo; {content.previous}
</button>
</li>
<li className="page-item">
<button className="page-link" onClick={handleNext} disabled={!hasNext}>
Следующая &raquo;
{content.next} &raquo;
</button>
</li>
</ul>
Expand Down
24 changes: 12 additions & 12 deletions apps/client/src/components/CancelSubscription.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { useUser } from "@clerk/clerk-react";
import { useClerk } from "@clerk/clerk-react";
import axios from "axios";
import { useState, useEffect } from "react";
import { useTranslation } from 'react-i18next';
import { useIntlayer } from "react-intlayer";

const API_URL = import.meta.env.VITE_API_URL;

const CancelSubscription = () => {
const { t } = useTranslation();
const content = useIntlayer("cancelSubscription");
const { user } = useUser();
const { redirectToSignIn } = useClerk();
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -46,7 +46,7 @@ const CancelSubscription = () => {
});
await fetchUserStatus();
} catch (e) {
setError("Ошибка при отмене подписки. Попробуйте позже.");
setError(content.error_cancel_subscription);
} finally {
setLoading(false);
}
Expand All @@ -62,56 +62,56 @@ const CancelSubscription = () => {
});
await fetchUserStatus();
} catch (e) {
setRenewError("Ошибка при возобновлении подписки. Попробуйте позже.");
setRenewError(content.error_renew_subscription);
} finally {
setRenewLoading(false);
}
};

return (
<div className="container" style={{ maxWidth: 480, margin: '0 auto', paddingTop: 60 }}>
<h2 className="text-center mb-4">Отмена подписки</h2>
<h2 className="text-center mb-4">{content.cancel_subscription_title}</h2>
{!user ? (
<div className="alert alert-info text-center" style={{ background: '#d1f3fa' }}>
Нет действующей подписки. Пожалуйста,{' '}
{content.no_active_subscription}{' '}
<button
className="btn btn-primary ms-2"
onClick={() => redirectToSignIn()}
type="button"
>
Войдите в аккаунт
{content.sign_in_to_account}
</button>
</div>
) : (
<>
{isPremium && isAutoRenewal === false && (
<div className="alert alert-success text-center mb-4">Автопродление успешно отключено!</div>
<div className="alert alert-success text-center mb-4">{content.auto_renewal_disabled}</div>
)}
{isPremium && isAutoRenewal === false && (
<button
className="btn btn-primary w-100 mb-3"
onClick={handleRenew}
disabled={renewLoading}
>
{renewLoading ? "Включение..." : "Возобновить подписку"}
{renewLoading ? content.enable_loading : content.renew_subscription}
</button>
)}
{renewError && <div className="alert alert-danger text-center mb-3">{renewError}</div>}
{isPremium && isAutoRenewal && (
<>
<p className="mb-4 text-center">Вы действительно хотите отменить автопродление подписки?</p>
<p className="mb-4 text-center">{content.confirm_cancel_auto_renewal}</p>
<button
className="btn btn-danger w-100"
onClick={handleCancel}
disabled={loading || !user || !isPremium}
>
{loading ? "Отмена..." : "Отменить подписку"}
{loading ? content.cancel_loading : content.cancel_subscription_button}
</button>
{error && <div className="alert alert-danger mt-3 text-center">{error}</div>}
</>
)}
{!isPremium && isPremium !== null && (
<div className="alert alert-info mt-3 text-center">{t('no_premium_subscription')}</div>
<div className="alert alert-info mt-3 text-center">{content.no_premium_subscription}</div>
)}
</>
)}
Expand Down
Loading
Loading