497 lines
24 KiB
TypeScript
Executable File
497 lines
24 KiB
TypeScript
Executable File
|
||
|
||
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; |