sozsoft-platform/ui/src/components/layouts/PublicLayout.tsx

511 lines
19 KiB
TypeScript
Raw Normal View History

2026-02-24 20:44:16 +00:00
import View from '@/views/Views'
import React, { useEffect, useState } from 'react'
import { useLocation, Link } from 'react-router-dom'
2026-05-15 21:06:58 +00:00
import { FaFacebook, FaTwitter, FaLinkedin, FaInstagram } from 'react-icons/fa';
import {
LuMenu,
LuX,
LuHouse,
LuInfo,
LuBriefcase,
LuPackage,
LuBookOpen,
LuMonitorPlay,
LuPhone,
LuMapPin,
LuMail,
} from 'react-icons/lu';
2026-02-24 20:44:16 +00:00
import Logo from '@/views/public/Logo'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import LanguageSelector from '../template/LanguageSelector'
import Demo from '@/views/public/Demo'
import { ScrollContext } from '@/contexts/ScrollContext'
interface NavLink {
resourceKey?: string
name: string
path?: string
action?: () => void
icon?: React.ComponentType<any>
}
const PublicLayout = () => {
const location = useLocation()
const { translate } = useLocalization()
const [isOpen, setIsOpen] = useState(false)
const [scrolled, setScrolled] = useState(false)
const [isDemoOpen, setIsDemoOpen] = useState(false)
const currentYear = new Date().getFullYear()
useEffect(() => {
window.scrollTo(0, 0)
}, [location.pathname])
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10
if (isScrolled !== scrolled) {
setScrolled(isScrolled)
}
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [scrolled])
const toggleMenu = () => setIsOpen(!isOpen)
const navLinks: NavLink[] = [
{
resourceKey: 'App.Home',
name: translate('::App.Home'),
path: ROUTES_ENUM.public.home,
2026-05-15 21:06:58 +00:00
icon: LuHouse,
2026-02-24 20:44:16 +00:00
},
{
resourceKey: 'App.About',
name: translate('::App.About'),
path: ROUTES_ENUM.public.about,
2026-05-15 21:06:58 +00:00
icon: LuInfo,
2026-02-24 20:44:16 +00:00
},
{
resourceKey: 'App.Services',
name: translate('::App.Services'),
path: ROUTES_ENUM.public.services,
2026-05-15 21:06:58 +00:00
icon: LuBriefcase,
2026-02-24 20:44:16 +00:00
},
{
resourceKey: 'Public.nav.products',
name: translate('::App.Orders'),
path: ROUTES_ENUM.public.products,
2026-05-15 21:06:58 +00:00
icon: LuPackage,
2026-02-24 20:44:16 +00:00
},
{
resourceKey: 'Public.nav.blog',
name: translate('::App.BlogManagement'),
path: ROUTES_ENUM.public.blog,
2026-05-15 21:06:58 +00:00
icon: LuBookOpen,
2026-02-24 20:44:16 +00:00
},
{
resourceKey: 'App.Demos',
name: translate('::App.Demos'),
action: () => setIsDemoOpen(true),
2026-05-15 21:06:58 +00:00
icon: LuMonitorPlay,
2026-02-24 20:44:16 +00:00
},
{
resourceKey: 'Public.nav.contact',
name: translate('::App.Contact'),
path: ROUTES_ENUM.public.contact,
2026-05-15 21:06:58 +00:00
icon: LuPhone,
2026-02-24 20:44:16 +00:00
},
{
resourceKey: 'Public.nav.giris',
name: translate('::Public.nav.giris'),
path: ROUTES_ENUM.authenticated.login,
},
]
2026-05-15 21:06:58 +00:00
const isLoginLink = (resourceKey?: string) => resourceKey === 'Public.nav.giris'
const isActiveLink = (path?: string) => {
if (!path) return false
if (path === ROUTES_ENUM.public.home) return location.pathname === path
return location.pathname.startsWith(path)
2026-02-24 20:44:16 +00:00
}
return (
<ScrollContext.Provider value={scrolled}>
<div className="flex flex-col min-h-screen">
{/* HEADER */}
<header
2026-05-15 21:06:58 +00:00
className={`fixed w-full z-50 transition-all duration-500 ${
2026-02-24 20:44:16 +00:00
scrolled
2026-05-15 21:06:58 +00:00
? 'bg-gray-950 shadow-lg shadow-black/30 py-2 border-b border-white/10'
: 'bg-gray-950/80 backdrop-blur-sm py-4 border-b border-white/5'
2026-02-24 20:44:16 +00:00
}`}
>
2026-05-15 21:06:58 +00:00
<div className="container mx-auto px-6 flex items-center justify-between">
2026-02-24 20:44:16 +00:00
<Logo mode="dark" />
2026-05-15 21:06:58 +00:00
2026-02-24 20:44:16 +00:00
{/* Desktop Navigation */}
2026-05-15 21:06:58 +00:00
<nav className="hidden md:flex items-center gap-1">
{navLinks
.filter((l) => !isLoginLink(l.resourceKey))
.map((link) => {
const active = isActiveLink(link.path)
const baseClass =
'relative flex items-center gap-1.5 px-3 py-2 text-sm font-medium rounded-md transition-all duration-200 group'
const activeClass = active
? 'text-white'
: 'text-gray-300 hover:text-white'
return link.path ? (
<Link
key={link.path}
to={link.path}
className={`${baseClass} ${activeClass}`}
>
{link.icon && (
<link.icon
size={22}
strokeWidth={1.75}
className={active ? 'text-blue-400' : 'text-gray-400 group-hover:text-blue-400 transition-colors'}
/>
)}
{link.name}
<span
className={`absolute bottom-0 left-3 right-3 h-0.5 rounded-full bg-blue-500 transition-all duration-200 ${
active ? 'opacity-100' : 'opacity-0 group-hover:opacity-60'
}`}
/>
</Link>
) : (
<button
key={link.name}
onClick={link.action}
className={`${baseClass} ${activeClass}`}
>
{link.icon && (
<link.icon
size={22}
strokeWidth={1.75}
className="text-gray-400 group-hover:text-blue-400 transition-colors"
/>
)}
{link.name}
<span className="absolute bottom-0 left-3 right-3 h-0.5 rounded-full bg-blue-500 opacity-0 group-hover:opacity-60 transition-all duration-200" />
</button>
)
})}
2026-02-24 20:44:16 +00:00
</nav>
2026-05-15 21:06:58 +00:00
{/* Right side: Language + Login */}
<div className="hidden md:flex items-center gap-3">
<LanguageSelector />
<div className="w-px h-5 bg-white/20" />
{navLinks
.filter((l) => isLoginLink(l.resourceKey))
.map((link) =>
link.path ? (
<Link
key={link.path}
to={link.path}
className="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-lg shadow-md shadow-blue-900/30 transition-all duration-200 hover:shadow-blue-700/40 hover:-translate-y-px"
>
{link.name}
</Link>
) : null,
)}
</div>
2026-02-24 20:44:16 +00:00
{/* Mobile Menu Button */}
2026-05-15 21:06:58 +00:00
<button
className="md:hidden flex items-center justify-center w-9 h-9 text-white rounded-md hover:bg-white/10 transition-colors"
onClick={toggleMenu}
aria-label="Toggle menu"
>
{isOpen ? <LuX size={20} strokeWidth={2} /> : <LuMenu size={20} strokeWidth={2} />}
2026-02-24 20:44:16 +00:00
</button>
</div>
{/* Mobile Navigation */}
2026-05-15 21:06:58 +00:00
<div
className={`md:hidden overflow-hidden transition-all duration-300 ease-in-out ${
isOpen ? 'max-h-screen opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div className="bg-gray-950/98 backdrop-blur-md border-t border-white/10">
<div className="container mx-auto px-6 py-3">
<nav className="flex flex-col gap-1">
{navLinks.map((link) => {
const active = isActiveLink(link.path)
const isLogin = isLoginLink(link.resourceKey)
if (isLogin) {
return link.path ? (
<Link
key={link.path}
to={link.path}
onClick={toggleMenu}
className="mt-2 flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-lg transition-colors"
>
{link.name}
</Link>
) : null
}
return link.path ? (
2026-02-24 20:44:16 +00:00
<Link
key={link.path}
to={link.path}
onClick={toggleMenu}
2026-05-15 21:06:58 +00:00
className={`flex items-center gap-2.5 px-3 py-2.5 text-sm font-medium rounded-md transition-colors ${
active
? 'bg-white/10 text-white'
: 'text-gray-300 hover:bg-white/5 hover:text-white'
}`}
2026-02-24 20:44:16 +00:00
>
2026-05-15 21:06:58 +00:00
{link.icon && (
<link.icon
size={16}
strokeWidth={1.75}
className={active ? 'text-blue-400' : 'text-gray-400'}
/>
)}
2026-02-24 20:44:16 +00:00
{link.name}
</Link>
) : (
<button
key={link.name}
onClick={() => {
link.action?.()
toggleMenu()
}}
2026-05-15 21:06:58 +00:00
className="flex items-center gap-2.5 px-3 py-2.5 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white rounded-md transition-colors text-left"
2026-02-24 20:44:16 +00:00
>
2026-05-15 21:06:58 +00:00
{link.icon && <link.icon size={16} strokeWidth={1.75} className="text-gray-400" />}
2026-02-24 20:44:16 +00:00
{link.name}
</button>
2026-05-15 21:06:58 +00:00
)
})}
<div className="pt-2 pb-1 border-t border-white/10 mt-1">
<LanguageSelector />
</div>
2026-02-24 20:44:16 +00:00
</nav>
</div>
</div>
2026-05-15 21:06:58 +00:00
</div>
2026-02-24 20:44:16 +00:00
</header>
<main className="flex-grow">
<View />
</main>
<footer className="bg-gray-900 text-white pt-16 pb-8">
<div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<div>
<Logo mode="dark" />
<p className="mt-4 text-gray-400 text-sm">
{translate('::Public.footer.companyInfo')}
</p>
<div className="flex space-x-4 mt-6">
<a
href="https://facebook.com/sozsoft"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-white transition-colors"
>
<FaFacebook size={20} />
</a>
<a
href="https://twitter.com/sozsoft"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-white transition-colors"
>
<FaTwitter size={20} />
</a>
<a
href="https://linkedin.com/sozsoft"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-white transition-colors"
>
<FaLinkedin size={20} />
</a>
<a
href="https://instagram.com/sozsoft"
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-white transition-colors"
>
<FaInstagram size={20} />
</a>
</div>
</div>
{/* Quick Links */}
<div>
<h3 className="text-lg font-bold mb-4 text-white">
{translate('::Public.footer.quickLinksTitle')}
</h3>
<ul className="space-y-2">
<li>
<Link
to={ROUTES_ENUM.public.home}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::App.Home')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.products}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::App.Orders')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.services}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::App.Services')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.about}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::App.About')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.blog}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::App.BlogManagement')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.contact}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::App.Contact')}
</Link>
</li>
</ul>
</div>
{/* Services */}
<div>
<h3 className="text-lg font-bold mb-4 text-white">
{translate('::Public.services.title')}
</h3>
<ul className="space-y-2">
<li>
<Link
to={ROUTES_ENUM.public.services}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::Public.services.software.title')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.services}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::Public.services.web.title')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.services}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::Public.services.mobile.title')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.services}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::Public.services.database.title')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.services}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::Public.services.integration.title')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.services}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::Public.services.consulting.title')}
</Link>
</li>
</ul>
</div>
{/* Contact Info */}
<div>
<h3 className="text-lg font-bold mb-4 text-white">
{translate('::App.Contact')}
</h3>
<ul className="space-y-3">
<li className="flex items-start space-x-3">
2026-05-15 21:06:58 +00:00
<LuMapPin size={18} strokeWidth={1.75} className="text-gray-400 mt-1 flex-shrink-0" />
2026-02-24 20:44:16 +00:00
<span className="text-gray-400">{translate('::Public.footer.address')}</span>
</li>
<li className="flex items-center space-x-3">
2026-05-15 21:06:58 +00:00
<LuPhone size={18} strokeWidth={1.75} className="text-gray-400 flex-shrink-0" />
2026-02-24 20:44:16 +00:00
<a
href="tel:+905447697638"
className="text-gray-400 hover:text-white transition-colors"
>
+90 (544) 769 7 638
</a>
</li>
<li className="flex items-center space-x-3">
2026-05-15 21:06:58 +00:00
<LuMail size={18} strokeWidth={1.75} className="text-gray-400 flex-shrink-0" />
2026-02-24 20:44:16 +00:00
<a
href="mailto:destek@sozsoft.com"
className="text-gray-400 hover:text-white transition-colors"
>
destek@sozsoft.com
</a>
</li>
</ul>
</div>
</div>
<div className="border-t border-gray-800 mt-12 pt-8">
<div className="flex flex-col md:flex-row justify-between items-center">
<p className="text-gray-400 text-sm">
2026-02-28 21:37:34 +00:00
&copy; {currentYear} Sözsoft Platform. {translate('::Public.footer.copyright')}
2026-02-24 20:44:16 +00:00
</p>
<div className="mt-4 md:mt-0">
<ul className="flex space-x-6 text-sm">
<li>
<Link
to={ROUTES_ENUM.public.about}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::Public.footer.privacyPolicy')}
</Link>
</li>
<li>
<Link
to={ROUTES_ENUM.public.about}
className="text-gray-400 hover:text-white transition-colors"
>
{translate('::Public.footer.termsOfUse')}
</Link>
</li>
</ul>
</div>
</div>
</div>
</div>
</footer>
{/* Demo Modal */}
<Demo isOpen={isDemoOpen} onClose={() => setIsDemoOpen(false)} />
</div>
</ScrollContext.Provider>
)
}
export default PublicLayout