497 lines
24 KiB
TypeScript
497 lines
24 KiB
TypeScript
|
|
|
|||
|
|
|
|||
|
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|||
|
|
import Sidebar from './components/Sidebar';
|
|||
|
|
import HeroSection from './components/HeroSection';
|
|||
|
|
import BlogSection from './components/BlogSection';
|
|||
|
|
import NewsSection from './components/NewsSection';
|
|||
|
|
import ResearchSection from './components/ResearchSection';
|
|||
|
|
import BusinessSection from './components/BusinessSection';
|
|||
|
|
import ConnectSection from './components/ConnectSection';
|
|||
|
|
import AboutUsSection from './components/AboutUsSection';
|
|||
|
|
import OurMissionSection from './components/OurMissionSection';
|
|||
|
|
import CareersSection from './components/CareersSection';
|
|||
|
|
import BusinessLandingSection from './components/BusinessLandingSection';
|
|||
|
|
import BusinessServicesSection from './components/BusinessServicesSection';
|
|||
|
|
import EducationBusinessSection from './components/EducationBusinessSection';
|
|||
|
|
import EducationStudentsSection from './components/EducationStudentsSection';
|
|||
|
|
import ResearchAllSection from './components/ResearchAllSection';
|
|||
|
|
import NewsAllSection from './components/NewsAllSection';
|
|||
|
|
import StoriesAllSection from './components/StoriesAllSection';
|
|||
|
|
import AcceleratorAboutSection from './components/AcceleratorAboutSection';
|
|||
|
|
import AcceleratorProjectsSection from './components/AcceleratorProjectsSection';
|
|||
|
|
import AcceleratorInvestmentSection from './components/AcceleratorInvestmentSection';
|
|||
|
|
import TermsOfUseSection from './components/TermsOfUseSection';
|
|||
|
|
import PrivacyPolicySection from './components/PrivacyPolicySection';
|
|||
|
|
import Logo from './components/Logo';
|
|||
|
|
import { Bars3Icon, XMarkIcon, SidebarToggleIcon } from './components/icons';
|
|||
|
|
import { CurrentView, Post, NewsArticle, ResearchPaper, BusinessStory, ServiceItemData, ClientLogo, BusinessCourse, StudentProgram, AcceleratorProject, InvestmentProject, Vacancy } from './types';
|
|||
|
|
import { SECTION_IDS, FOOTER_CONTENT, UI_TEXTS } from './constants';
|
|||
|
|
import ChatInput from './components/ChatInput';
|
|||
|
|
|
|||
|
|
|
|||
|
|
// Import Strapi service
|
|||
|
|
import {
|
|||
|
|
fetchPosts, fetchNewsArticles, fetchResearchPapers, fetchBusinessStories,
|
|||
|
|
fetchServiceItems, fetchClientLogos, fetchBusinessCourses, fetchStudentPrograms,
|
|||
|
|
fetchAcceleratorProjects, fetchInvestmentProjects, fetchVacancies
|
|||
|
|
} from './strapiService';
|
|||
|
|
|
|||
|
|
// Import Detail Page Components
|
|||
|
|
import BlogPostDetail from './components/BlogPostDetail';
|
|||
|
|
import NewsArticleDetail from './components/NewsArticleDetail';
|
|||
|
|
import ResearchPaperDetail from './components/ResearchPaperDetail';
|
|||
|
|
import BusinessStoryDetail from './components/BusinessStoryDetail';
|
|||
|
|
import VacancyDetail from './components/VacancyDetail';
|
|||
|
|
|
|||
|
|
|
|||
|
|
const App: React.FC = () => {
|
|||
|
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
|||
|
|
const [isDesktopSidebarCollapsed, setIsDesktopSidebarCollapsed] = useState(false);
|
|||
|
|
const [currentView, setCurrentView] = useState<CurrentView>('main');
|
|||
|
|
const [selectedItemId, setSelectedItemId] = useState<string | null>(null);
|
|||
|
|
const [showStickyChat, setShowStickyChat] = useState(false);
|
|||
|
|
const [isStickyChatExpanded, setIsStickyChatExpanded] = useState(false);
|
|||
|
|
const stickyChatRef = useRef<HTMLDivElement>(null);
|
|||
|
|
|
|||
|
|
|
|||
|
|
// State for Strapi data
|
|||
|
|
const [posts, setPosts] = useState<Post[]>([]);
|
|||
|
|
const [newsArticles, setNewsArticles] = useState<NewsArticle[]>([]);
|
|||
|
|
const [researchPapers, setResearchPapers] = useState<ResearchPaper[]>([]);
|
|||
|
|
const [businessStories, setBusinessStories] = useState<BusinessStory[]>([]);
|
|||
|
|
const [serviceItems, setServiceItems] = useState<ServiceItemData[]>([]);
|
|||
|
|
const [clientLogos, setClientLogos] = useState<ClientLogo[]>([]);
|
|||
|
|
const [businessCourses, setBusinessCourses] = useState<BusinessCourse[]>([]);
|
|||
|
|
const [studentPrograms, setStudentPrograms] = useState<StudentProgram[]>([]);
|
|||
|
|
const [acceleratorProjects, setAcceleratorProjects] = useState<AcceleratorProject[]>([]);
|
|||
|
|
const [investmentProjects, setInvestmentProjects] = useState<InvestmentProject[]>([]);
|
|||
|
|
const [vacancies, setVacancies] = useState<Vacancy[]>([]);
|
|||
|
|
const [isLoading, setIsLoading] = useState(true);
|
|||
|
|
const [error, setError] = useState<string | null>(null);
|
|||
|
|
|
|||
|
|
|
|||
|
|
const toggleSidebar = useCallback(() => {
|
|||
|
|
setIsSidebarOpen(prev => !prev);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const handleSetCurrentView = useCallback((view: CurrentView) => {
|
|||
|
|
setCurrentView(view);
|
|||
|
|
if (!view.endsWith('Detail') && view !== 'termsOfUse' && view !== 'privacyPolicy') {
|
|||
|
|
setSelectedItemId(null);
|
|||
|
|
}
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
const handleSetSelectedItemId = useCallback((id: string | null) => {
|
|||
|
|
setSelectedItemId(id);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const loadData = async () => {
|
|||
|
|
setIsLoading(true);
|
|||
|
|
setError(null);
|
|||
|
|
try {
|
|||
|
|
const [
|
|||
|
|
fetchedPosts,
|
|||
|
|
fetchedNews,
|
|||
|
|
fetchedResearch,
|
|||
|
|
fetchedBusinessStories,
|
|||
|
|
fetchedServiceItems,
|
|||
|
|
fetchedClientLogos,
|
|||
|
|
fetchedBusinessCourses,
|
|||
|
|
fetchedStudentPrograms,
|
|||
|
|
fetchedAcceleratorProjects,
|
|||
|
|
fetchedInvestmentProjects,
|
|||
|
|
fetchedVacanciesData
|
|||
|
|
] = await Promise.all([
|
|||
|
|
fetchPosts(),
|
|||
|
|
fetchNewsArticles(),
|
|||
|
|
fetchResearchPapers(),
|
|||
|
|
fetchBusinessStories(),
|
|||
|
|
fetchServiceItems(),
|
|||
|
|
fetchClientLogos(),
|
|||
|
|
fetchBusinessCourses(),
|
|||
|
|
fetchStudentPrograms(),
|
|||
|
|
fetchAcceleratorProjects(),
|
|||
|
|
fetchInvestmentProjects(),
|
|||
|
|
fetchVacancies(),
|
|||
|
|
]);
|
|||
|
|
setPosts(fetchedPosts);
|
|||
|
|
setNewsArticles(fetchedNews);
|
|||
|
|
setResearchPapers(fetchedResearch);
|
|||
|
|
setBusinessStories(fetchedBusinessStories);
|
|||
|
|
setServiceItems(fetchedServiceItems);
|
|||
|
|
setClientLogos(fetchedClientLogos);
|
|||
|
|
setBusinessCourses(fetchedBusinessCourses);
|
|||
|
|
setStudentPrograms(fetchedStudentPrograms);
|
|||
|
|
setAcceleratorProjects(fetchedAcceleratorProjects);
|
|||
|
|
setInvestmentProjects(fetchedInvestmentProjects);
|
|||
|
|
setVacancies(fetchedVacanciesData);
|
|||
|
|
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error("Failed to load data from Strapi:", err);
|
|||
|
|
setError("Не удалось загрузить данные. Пожалуйста, попробуйте позже.");
|
|||
|
|
} finally {
|
|||
|
|
setIsLoading(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
loadData();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const handleResize = () => {
|
|||
|
|
if (window.innerWidth >= 768) {
|
|||
|
|
setIsSidebarOpen(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
window.addEventListener('resize', handleResize);
|
|||
|
|
return () => window.removeEventListener('resize', handleResize);
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
// On non-main pages, the sticky chat should always be visible.
|
|||
|
|
if (currentView !== 'main') {
|
|||
|
|
setShowStickyChat(true);
|
|||
|
|
// We don't need a scroll listener on these pages for this feature.
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// On the main page, show the sticky chat only after scrolling past the hero.
|
|||
|
|
const handleScroll = () => {
|
|||
|
|
const heroSection = document.getElementById(SECTION_IDS.hero);
|
|||
|
|
if (heroSection) {
|
|||
|
|
const shouldShow = heroSection.getBoundingClientRect().bottom < 0;
|
|||
|
|
setShowStickyChat(current => current === shouldShow ? current : shouldShow);
|
|||
|
|
} else {
|
|||
|
|
// Fallback if hero section is not found on main page for some reason
|
|||
|
|
setShowStickyChat(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Set initial state for 'main' view (in case user reloads scrolled down)
|
|||
|
|
handleScroll();
|
|||
|
|
|
|||
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|||
|
|
return () => {
|
|||
|
|
window.removeEventListener('scroll', handleScroll);
|
|||
|
|
};
|
|||
|
|
}, [currentView]); // Dependency on currentView ensures this logic re-runs on page change
|
|||
|
|
|
|||
|
|
// Hook for handling clicks outside of the sticky chat input to collapse it
|
|||
|
|
useEffect(() => {
|
|||
|
|
function handleClickOutside(event: MouseEvent) {
|
|||
|
|
if (isStickyChatExpanded && stickyChatRef.current && !stickyChatRef.current.contains(event.target as Node)) {
|
|||
|
|
setIsStickyChatExpanded(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|||
|
|
return () => {
|
|||
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|||
|
|
};
|
|||
|
|
}, [isStickyChatExpanded]);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
let targetId: string | null = null;
|
|||
|
|
const baseTitle = 'iiEasy';
|
|||
|
|
let newTitle = `iiEasy: Разработка Сайтов, Приложений, ИИ-Решений | ИИ Исследования и Обучение Уфа`; // Default
|
|||
|
|
|
|||
|
|
const findItemTitle = (items: {id: string; title: string}[], id: string | null) => items.find(item => item.id === id)?.title;
|
|||
|
|
|
|||
|
|
switch(currentView) {
|
|||
|
|
case 'about': targetId = SECTION_IDS.aboutUsPage; newTitle = `О нас | ${baseTitle}`; break;
|
|||
|
|
case 'mission': targetId = SECTION_IDS.ourMissionPage; newTitle = `Наша Миссия | ${baseTitle}`; break;
|
|||
|
|
case 'careers': targetId = SECTION_IDS.careersPage; newTitle = `Карьера | ${baseTitle}`; break;
|
|||
|
|
case 'businessLanding': targetId = SECTION_IDS.businessLandingPage; newTitle = `Кейсы для Бизнеса | ${baseTitle}`; break;
|
|||
|
|
case 'businessServices': targetId = SECTION_IDS.businessServicesPage; newTitle = `Услуги для Бизнеса | ${baseTitle}`; break;
|
|||
|
|
case 'educationBusiness': targetId = SECTION_IDS.educationBusinessPage; newTitle = `Обучение для Бизнеса | ${baseTitle}`; break;
|
|||
|
|
case 'educationStudents': targetId = SECTION_IDS.educationStudentsPage; newTitle = `Обучение для Студентов | ${baseTitle}`; break;
|
|||
|
|
case 'researchAll': targetId = SECTION_IDS.researchAllPage; newTitle = `Все Исследования | ${baseTitle}`; break;
|
|||
|
|
case 'newsAll': targetId = SECTION_IDS.newsAllPage; newTitle = `Все Новости | ${baseTitle}`; break;
|
|||
|
|
case 'storiesAll': targetId = SECTION_IDS.storiesAllPage; newTitle = `Все Истории | ${baseTitle}`; break;
|
|||
|
|
case 'acceleratorAbout': targetId = SECTION_IDS.acceleratorAboutPage; newTitle = `Об Акселераторе | ${baseTitle}`; break;
|
|||
|
|
case 'acceleratorProjects': targetId = SECTION_IDS.acceleratorProjectsPage; newTitle = `Проекты Акселератора | ${baseTitle}`; break;
|
|||
|
|
case 'acceleratorInvestment': targetId = SECTION_IDS.acceleratorInvestmentPage; newTitle = `Инвестиции в стартапы | ${baseTitle}`; break;
|
|||
|
|
case 'blogPostDetail':
|
|||
|
|
targetId = SECTION_IDS.blogPostDetailPage;
|
|||
|
|
newTitle = `${findItemTitle(posts, selectedItemId) || 'История'} | ${baseTitle}`;
|
|||
|
|
break;
|
|||
|
|
case 'newsArticleDetail':
|
|||
|
|
targetId = SECTION_IDS.newsArticleDetailPage;
|
|||
|
|
newTitle = `${findItemTitle(newsArticles, selectedItemId) || 'Новость'} | ${baseTitle}`;
|
|||
|
|
break;
|
|||
|
|
case 'researchPaperDetail':
|
|||
|
|
targetId = SECTION_IDS.researchPaperDetailPage;
|
|||
|
|
newTitle = `${findItemTitle(researchPapers, selectedItemId) || 'Исследование'} | ${baseTitle}`;
|
|||
|
|
break;
|
|||
|
|
case 'businessStoryDetail':
|
|||
|
|
targetId = SECTION_IDS.businessStoryDetail;
|
|||
|
|
newTitle = `${findItemTitle(businessStories, selectedItemId) || 'Кейс'} | ${baseTitle}`;
|
|||
|
|
break;
|
|||
|
|
case 'vacancyDetail':
|
|||
|
|
targetId = SECTION_IDS.vacancyDetailPage;
|
|||
|
|
newTitle = `${findItemTitle(vacancies, selectedItemId) || 'Вакансия'} | ${baseTitle}`;
|
|||
|
|
break;
|
|||
|
|
case 'termsOfUse': targetId = SECTION_IDS.termsOfUsePage; newTitle = `Правила использования | ${baseTitle}`; break;
|
|||
|
|
case 'privacyPolicy': targetId = SECTION_IDS.privacyPolicyPage; newTitle = `Политика конфиденциальности | ${baseTitle}`; break;
|
|||
|
|
case 'main':
|
|||
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
document.title = newTitle;
|
|||
|
|
|
|||
|
|
if (targetId) {
|
|||
|
|
const element = document.getElementById(targetId);
|
|||
|
|
if (element) {
|
|||
|
|
element.scrollIntoView({behavior: 'smooth', block: 'start'});
|
|||
|
|
} else {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
document.getElementById(targetId)?.scrollIntoView({behavior: 'smooth', block: 'start'});
|
|||
|
|
}, 150);
|
|||
|
|
}
|
|||
|
|
} else if (currentView !== 'main') {
|
|||
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|||
|
|
}
|
|||
|
|
}, [currentView, selectedItemId, posts, newsArticles, researchPapers, businessStories, vacancies]);
|
|||
|
|
|
|||
|
|
if (isLoading) {
|
|||
|
|
return (
|
|||
|
|
<div className="min-h-screen flex items-center justify-center bg-white">
|
|||
|
|
<div className="text-center">
|
|||
|
|
<Logo width={150} height={112} />
|
|||
|
|
<p className="mt-4 text-xl font-semibold text-slate-700 animate-pulse">Загрузка данных...</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
return (
|
|||
|
|
<div className="min-h-screen flex items-center justify-center bg-white p-4">
|
|||
|
|
<div className="text-center bg-white p-8 rounded-lg">
|
|||
|
|
<Logo width={100} height={75} className="mx-auto mb-4 filter grayscale" />
|
|||
|
|
<h2 className="text-2xl font-bold text-red-600 mb-3">Ошибка загрузки</h2>
|
|||
|
|
<p className="text-slate-700 mb-6">{error}</p>
|
|||
|
|
<button
|
|||
|
|
onClick={() => window.location.reload()}
|
|||
|
|
className="px-6 py-2 bg-white text-red-600 border border-red-500 rounded-lg hover:bg-red-50 transition-colors font-semibold"
|
|||
|
|
>
|
|||
|
|
Попробовать снова
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="min-h-screen text-slate-800 font-inter bg-white">
|
|||
|
|
<Sidebar
|
|||
|
|
isOpen={isSidebarOpen}
|
|||
|
|
onClose={() => setIsSidebarOpen(false)}
|
|||
|
|
currentView={currentView}
|
|||
|
|
setCurrentView={handleSetCurrentView}
|
|||
|
|
isDesktopCollapsed={isDesktopSidebarCollapsed}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* Desktop toggle button */}
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={() => setIsDesktopSidebarCollapsed(prev => !prev)}
|
|||
|
|
className={`fixed top-6 left-4 z-50 p-2 bg-white border border-slate-200 text-slate-600 hover:text-black hover:bg-slate-100 rounded-lg shadow-sm transition-all duration-300 ease-in-out hidden md:block
|
|||
|
|
${isDesktopSidebarCollapsed ? '' : 'md:translate-x-72'}`}
|
|||
|
|
aria-label="Toggle navigation sidebar"
|
|||
|
|
aria-expanded={!isDesktopSidebarCollapsed}
|
|||
|
|
>
|
|||
|
|
<SidebarToggleIcon className={`w-5 h-5 transition-transform duration-300 ${isDesktopSidebarCollapsed ? 'rotate-180' : ''}`} />
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<div className={`transition-all duration-300 ease-in-out ${isDesktopSidebarCollapsed ? 'md:pl-0' : 'md:pl-72'}`}>
|
|||
|
|
<div className="flex flex-col min-h-screen">
|
|||
|
|
|
|||
|
|
{isSidebarOpen && (
|
|||
|
|
<div
|
|||
|
|
className="fixed inset-0 z-30 bg-black/30 md:hidden"
|
|||
|
|
onClick={toggleSidebar}
|
|||
|
|
aria-hidden="true"
|
|||
|
|
></div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
id="sidebar-toggle-button"
|
|||
|
|
onClick={toggleSidebar}
|
|||
|
|
className="fixed top-4 left-4 z-50 p-2 bg-white/80 rounded-full text-slate-800 hover:text-black focus:ring-slate-500 transition-all focus:outline-none focus:ring-2 md:hidden"
|
|||
|
|
aria-label={isSidebarOpen ? UI_TEXTS.sidebarToggleCloseAriaLabel : UI_TEXTS.sidebarToggleOpenAriaLabel}
|
|||
|
|
aria-expanded={isSidebarOpen}
|
|||
|
|
>
|
|||
|
|
{isSidebarOpen ? <XMarkIcon className="h-7 w-7" /> : <Bars3Icon className="h-7 w-7" />}
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<main className="flex-grow">
|
|||
|
|
{currentView === 'main' && (
|
|||
|
|
<>
|
|||
|
|
<HeroSection setCurrentView={handleSetCurrentView} isChatHidden={showStickyChat} />
|
|||
|
|
<BlogSection posts={posts} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />
|
|||
|
|
<NewsSection newsArticles={newsArticles} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />
|
|||
|
|
<ResearchSection researchPapers={researchPapers} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />
|
|||
|
|
<BusinessSection businessStories={businessStories} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />
|
|||
|
|
<ConnectSection />
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
{currentView === 'about' && <AboutUsSection />}
|
|||
|
|
{currentView === 'mission' && <OurMissionSection />}
|
|||
|
|
{currentView === 'careers' && <CareersSection vacancies={vacancies} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />}
|
|||
|
|
|
|||
|
|
{currentView === 'businessLanding' && <BusinessLandingSection businessStories={businessStories} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />}
|
|||
|
|
{currentView === 'businessServices' && <BusinessServicesSection serviceItems={serviceItems} clientLogos={clientLogos} />}
|
|||
|
|
|
|||
|
|
{currentView === 'educationBusiness' && <EducationBusinessSection businessCourses={businessCourses} />}
|
|||
|
|
{currentView === 'educationStudents' && <EducationStudentsSection studentPrograms={studentPrograms} />}
|
|||
|
|
|
|||
|
|
{currentView === 'researchAll' && <ResearchAllSection researchPapers={researchPapers} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />}
|
|||
|
|
{currentView === 'newsAll' && <NewsAllSection newsArticles={newsArticles} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />}
|
|||
|
|
{currentView === 'storiesAll' && <StoriesAllSection posts={posts} setCurrentView={handleSetCurrentView} setSelectedItemId={handleSetSelectedItemId} />}
|
|||
|
|
|
|||
|
|
{currentView === 'acceleratorAbout' && <AcceleratorAboutSection />}
|
|||
|
|
{currentView === 'acceleratorProjects' && <AcceleratorProjectsSection acceleratorProjects={acceleratorProjects} />}
|
|||
|
|
{currentView === 'acceleratorInvestment' && <AcceleratorInvestmentSection investmentProjects={investmentProjects} />}
|
|||
|
|
|
|||
|
|
{/* Detail Pages */}
|
|||
|
|
{currentView === 'blogPostDetail' && selectedItemId && (
|
|||
|
|
<BlogPostDetail
|
|||
|
|
itemId={selectedItemId}
|
|||
|
|
allPosts={posts}
|
|||
|
|
setCurrentView={handleSetCurrentView}
|
|||
|
|
setSelectedItemId={handleSetSelectedItemId}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
{currentView === 'newsArticleDetail' && selectedItemId && (
|
|||
|
|
<NewsArticleDetail
|
|||
|
|
itemId={selectedItemId}
|
|||
|
|
allNews={newsArticles}
|
|||
|
|
setCurrentView={handleSetCurrentView}
|
|||
|
|
setSelectedItemId={handleSetSelectedItemId}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
{currentView === 'researchPaperDetail' && selectedItemId && (
|
|||
|
|
<ResearchPaperDetail
|
|||
|
|
itemId={selectedItemId}
|
|||
|
|
allResearchPapers={researchPapers}
|
|||
|
|
setCurrentView={handleSetCurrentView}
|
|||
|
|
setSelectedItemId={handleSetSelectedItemId}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
{currentView === 'businessStoryDetail' && selectedItemId && (
|
|||
|
|
<BusinessStoryDetail
|
|||
|
|
itemId={selectedItemId}
|
|||
|
|
allBusinessStories={businessStories}
|
|||
|
|
setCurrentView={handleSetCurrentView}
|
|||
|
|
setSelectedItemId={handleSetSelectedItemId}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
{currentView === 'vacancyDetail' && selectedItemId && (
|
|||
|
|
<VacancyDetail
|
|||
|
|
itemId={selectedItemId}
|
|||
|
|
allVacancies={vacancies}
|
|||
|
|
setCurrentView={handleSetCurrentView}
|
|||
|
|
setSelectedItemId={handleSetSelectedItemId}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
{/* Legal Pages */}
|
|||
|
|
{currentView === 'termsOfUse' && <TermsOfUseSection setCurrentView={handleSetCurrentView} />}
|
|||
|
|
{currentView === 'privacyPolicy' && <PrivacyPolicySection setCurrentView={handleSetCurrentView} />}
|
|||
|
|
</main>
|
|||
|
|
|
|||
|
|
<footer className="py-6 bg-white text-slate-600">
|
|||
|
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center md:text-left">
|
|||
|
|
<div className="md:text-left">
|
|||
|
|
<h3 className="text-sm font-semibold text-slate-500 mb-3 font-quicksand">{FOOTER_CONTENT.legalTitle}</h3>
|
|||
|
|
<ul className="space-y-2 text-xs">
|
|||
|
|
{FOOTER_CONTENT.legalLinks.map(link => (
|
|||
|
|
<li key={link.href}>
|
|||
|
|
<a
|
|||
|
|
href={link.href}
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
handleSetCurrentView(link.targetView);
|
|||
|
|
}}
|
|||
|
|
className="text-slate-600 hover:text-black transition-colors cursor-pointer"
|
|||
|
|
>
|
|||
|
|
{link.text}
|
|||
|
|
</a>
|
|||
|
|
</li>
|
|||
|
|
))}
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex flex-col items-center text-center">
|
|||
|
|
<Logo width={60} height={60} className="mb-3" />
|
|||
|
|
<p className="font-quicksand text-sm font-semibold text-slate-800">{FOOTER_CONTENT.copyrightText(new Date().getFullYear())}</p>
|
|||
|
|
<p className="text-xs mt-2 text-slate-500">{FOOTER_CONTENT.tagline}</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="md:text-right">
|
|||
|
|
<h3 className="text-sm font-semibold text-slate-500 mb-3 font-quicksand">{FOOTER_CONTENT.socialTitle}</h3>
|
|||
|
|
<div className="flex justify-center md:justify-end space-x-4">
|
|||
|
|
{FOOTER_CONTENT.socialLinks.map(social => (
|
|||
|
|
<a
|
|||
|
|
key={social.name}
|
|||
|
|
href={social.href}
|
|||
|
|
target="_blank"
|
|||
|
|
rel="noopener noreferrer"
|
|||
|
|
aria-label={social.ariaLabel}
|
|||
|
|
className="text-slate-600 hover:text-black transition-colors"
|
|||
|
|
>
|
|||
|
|
<social.icon className="w-5 h-5" />
|
|||
|
|
</a>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
<div className="mt-6 text-xs">
|
|||
|
|
<p>{FOOTER_CONTENT.address}</p>
|
|||
|
|
<p className="mt-1">
|
|||
|
|
<a href={FOOTER_CONTENT.emailHref} className="text-slate-600 hover:text-black transition-colors">
|
|||
|
|
{FOOTER_CONTENT.email}
|
|||
|
|
</a>
|
|||
|
|
</p>
|
|||
|
|
{FOOTER_CONTENT.companyName && FOOTER_CONTENT.companyInn && (
|
|||
|
|
<p className="mt-1">
|
|||
|
|
{FOOTER_CONTENT.companyName} <span className="text-slate-500">ИНН:</span> {FOOTER_CONTENT.companyInn}
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</footer>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{/* Sticky Chat Area */}
|
|||
|
|
<div
|
|||
|
|
ref={stickyChatRef}
|
|||
|
|
className={`fixed bottom-0 left-0 right-0 z-20 transition-all duration-500 ease-in-out ${showStickyChat ? 'translate-y-0' : 'translate-y-full'}`}
|
|||
|
|
>
|
|||
|
|
<div className={`transition-all duration-300 ease-in-out ${isDesktopSidebarCollapsed ? 'md:pl-0' : 'md:pl-72'}`}>
|
|||
|
|
{isStickyChatExpanded ? (
|
|||
|
|
<div className="p-4 bg-gradient-to-t from-white via-white/95 to-transparent pointer-events-none">
|
|||
|
|
<div className="w-full max-w-3xl mx-auto pointer-events-auto">
|
|||
|
|
<ChatInput setCurrentView={setCurrentView} isSticky={true} autoFocusOnMount={true} />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
<div className="w-full p-4 flex justify-center pointer-events-none">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setIsStickyChatExpanded(true)}
|
|||
|
|
className="pointer-events-auto bg-slate-100 text-slate-800 font-quicksand font-semibold px-6 py-3 rounded-full shadow-lg hover:bg-slate-200 transition-all duration-300 transform hover:-translate-y-1 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2"
|
|||
|
|
aria-label="Открыть чат"
|
|||
|
|
>
|
|||
|
|
Поможем найти
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default App;
|