diff --git a/assets/pasted-20260228-230814-fcc0de29.png b/assets/pasted-20260228-230814-fcc0de29.png new file mode 100644 index 0000000..3b39312 Binary files /dev/null and b/assets/pasted-20260228-230814-fcc0de29.png differ diff --git a/assets/pasted-20260228-231441-5d159024.png b/assets/pasted-20260228-231441-5d159024.png new file mode 100644 index 0000000..5e555d9 Binary files /dev/null and b/assets/pasted-20260228-231441-5d159024.png differ diff --git a/backend/src/db/migrations/20260228000000-public-permissions.js b/backend/src/db/migrations/20260228000000-public-permissions.js new file mode 100644 index 0000000..9da1cff --- /dev/null +++ b/backend/src/db/migrations/20260228000000-public-permissions.js @@ -0,0 +1,41 @@ + +module.exports = { + async up(queryInterface, Sequelize) { + const createdAt = new Date(); + const updatedAt = new Date(); + + const [publicRole] = await queryInterface.sequelize.query( + `SELECT id FROM "roles" WHERE name = 'Public' LIMIT 1;` + ); + + if (publicRole && publicRole.length > 0) { + const publicRoleId = publicRole[0].id; + + const permissionsToGrant = [ + 'CREATE_LEADS', + 'READ_SERVICES', + 'READ_PROJECTS', + 'READ_TESTIMONIALS', + 'READ_MEDIA_ASSETS', + 'READ_FAQS' + ]; + + const [permissions] = await queryInterface.sequelize.query( + `SELECT id FROM "permissions" WHERE name IN (${permissionsToGrant.map(p => `'${p}'`).join(',')});` + ); + + const rolesPermissions = permissions.map(permission => ({ + createdAt, + updatedAt, + roles_permissionsId: publicRoleId, + permissionId: permission.id + })); + + await queryInterface.bulkInsert('rolesPermissionsPermissions', rolesPermissions); + } + }, + + async down(queryInterface, Sequelize) { + // Optional: remove permissions from Public role + } +}; diff --git a/backend/src/index.js b/backend/src/index.js index 254c3aa..18d1119 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,4 +1,3 @@ - const express = require('express'); const cors = require('cors'); const app = express(); @@ -117,19 +116,19 @@ app.use('/api/pages', passport.authenticate('jwt', {session: false}), pagesRoute app.use('/api/service_categories', passport.authenticate('jwt', {session: false}), service_categoriesRoutes); -app.use('/api/services', passport.authenticate('jwt', {session: false}), servicesRoutes); +app.use('/api/services', servicesRoutes); -app.use('/api/projects', passport.authenticate('jwt', {session: false}), projectsRoutes); +app.use('/api/projects', projectsRoutes); -app.use('/api/testimonials', passport.authenticate('jwt', {session: false}), testimonialsRoutes); +app.use('/api/testimonials', testimonialsRoutes); -app.use('/api/faqs', passport.authenticate('jwt', {session: false}), faqsRoutes); +app.use('/api/faqs', faqsRoutes); -app.use('/api/leads', passport.authenticate('jwt', {session: false}), leadsRoutes); +app.use('/api/leads', leadsRoutes); -app.use('/api/media_assets', passport.authenticate('jwt', {session: false}), media_assetsRoutes); +app.use('/api/media_assets', media_assetsRoutes); -app.use('/api/home_sections', passport.authenticate('jwt', {session: false}), home_sectionsRoutes); +app.use('/api/home_sections', home_sectionsRoutes); app.use( '/api/openai', @@ -175,4 +174,4 @@ db.sequelize.sync().then(function () { }); }); -module.exports = app; +module.exports = app; \ No newline at end of file diff --git a/frontend/public/logo-beka.png b/frontend/public/logo-beka.png new file mode 100644 index 0000000..3b39312 Binary files /dev/null and b/frontend/public/logo-beka.png differ diff --git a/frontend/src/components/Beka/About.tsx b/frontend/src/components/Beka/About.tsx new file mode 100644 index 0000000..da86ea4 --- /dev/null +++ b/frontend/src/components/Beka/About.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { mdiCheckBold, mdiShieldCheck, mdiLightbulbOn, mdiStar } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; +import ScrollReveal from './ScrollReveal'; + +const features = [ + { + title: 'Garantili İşçilik', + description: 'Tüm uygulamalarımızda uzun ömürlü ve garantili işçilik sunuyoruz.', + icon: mdiShieldCheck + }, + { + title: 'Modern Tasarımlar', + description: 'En son trendlere uygun, estetik ve modern mekanlar tasarlıyoruz.', + icon: mdiLightbulbOn + }, + { + title: 'En İyi Malzeme', + description: 'Sektördeki en kaliteli ve dayanıklı malzemeleri kullanıyoruz.', + icon: mdiStar + } +]; + +const About = () => { + return ( +
+
+
+
+ +
+ Beka Kaplama Hakkımızda +
+
+ + {/* Experience Badge */} + +
+
15+
+
Yıllık Sektörel
Tecrübe
+
+
+ + {/* Background Decoration */} +
+
+
+ +
+ +

Biz Kimiz?

+

Mekanlarınıza Değer
Katan Vizyon

+
+ + +

+ "Beka Kaplama olarak, sadece duvarları kaplamıyor, yaşam alanlarınızın ruhunu değiştiriyoruz." +

+
+ + +

+ Kurulduğumuz günden bu yana, modern mimari trendlerini takip ederek müşterilerimize en kaliteli kaplama çözümlerini sunuyoruz. Akustik panel sistemlerinden PU cephe lambri uygulamalarına kadar geniş ürün yelpazemiz ve uzman ekibimizle projelerinize estetik ve dayanıklılık katıyoruz. +

+
+ +
+ + {['Müşteri Odaklılık', 'Kaliteli Malzeme', 'Hızlı Uygulama'].map((item, i) => ( +
+
+ +
+ {item} +
+ ))} +
+ + {['Estetik Tasarım', 'Ekonomik Çözümler', 'Uzman Kadro'].map((item, i) => ( +
+
+ +
+ {item} +
+ ))} +
+
+ + + {features.map((feature, index) => ( +
+
+ +
+

{feature.title}

+
+ ))} +
+
+
+
+
+ ); +}; + +export default About; diff --git a/frontend/src/components/Beka/ContactForm.tsx b/frontend/src/components/Beka/ContactForm.tsx new file mode 100644 index 0000000..eeff479 --- /dev/null +++ b/frontend/src/components/Beka/ContactForm.tsx @@ -0,0 +1,217 @@ +import React, { useState } from 'react'; +import axios from 'axios'; +import { mdiSend, mdiPhone, mdiEmail, mdiMapMarker, mdiCheckCircleOutline } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; +import ScrollReveal from './ScrollReveal'; + +const ContactForm = () => { + const [formData, setFormData] = useState({ + full_name: '', + email: '', + phone: '', + message: '', + city: '', + preferred_contact_method: 'phone' + }); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + const [error, setError] = useState(''); + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value + }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + setError(''); + + try { + await axios.post('/leads', { + data: { + ...formData, + source: 'website_form', + requested_at: new Date().toISOString(), + consent_to_contact: true + } + }); + setIsSuccess(true); + setFormData({ + full_name: '', + email: '', + phone: '', + message: '', + city: '', + preferred_contact_method: 'phone' + }); + } catch (err) { + console.error(err); + setError('Mesajınız iletilemedi. Lütfen daha sonra tekrar deneyiniz.'); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+ {/* Background Decor */} +
+ +
+
+
+ +

Hemen İletişime Geçin

+

Projeniz İçin Ücretsiz
Keşif ve Fiyat Alın

+ +

+ Uzman ekibimizle en kısa sürede size geri dönüş sağlayacağız. İster arayın, ister formu doldurun. +

+
+ +
+ {[ + { icon: mdiPhone, label: 'Telefon', value: '+90 500 000 00 00', delay: 100 }, + { icon: mdiEmail, label: 'E-Posta', value: 'info@bekakaplama.com', delay: 200 }, + { icon: mdiMapMarker, label: 'Adres', value: 'İstanbul, Türkiye', delay: 300 } + ].map((item, i) => ( + +
+
+ +
+
+

{item.label}

+

{item.value}

+
+
+
+ ))} +
+
+ + +
+ {/* Decorative circle */} +
+ + {isSuccess ? ( +
+
+ +
+

Mesajınız Alındı!

+

En kısa sürede uzman ekibimiz tarafından aranacaksınız.

+ +
+ ) : ( +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ + {error &&

{error}

} + + +
+ )} +
+
+
+
+
+ ); +}; + +export default ContactForm; diff --git a/frontend/src/components/Beka/Footer.tsx b/frontend/src/components/Beka/Footer.tsx new file mode 100644 index 0000000..3815a4c --- /dev/null +++ b/frontend/src/components/Beka/Footer.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import Link from 'next/link'; +import { mdiFacebook, mdiInstagram, mdiLinkedin, mdiTwitter } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; +import ScrollReveal from './ScrollReveal'; + +const Footer = () => { + return ( + + ); +}; + +export default Footer; diff --git a/frontend/src/components/Beka/Hero.tsx b/frontend/src/components/Beka/Hero.tsx new file mode 100644 index 0000000..b0460aa --- /dev/null +++ b/frontend/src/components/Beka/Hero.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import Link from 'next/link'; +import { mdiArrowRight, mdiWhatsapp } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; + +const Hero = () => { + return ( +
+ {/* Background with Animation */} +
+ Beka Kaplama Hero +
+
+ +
+
+ {/* Badge with Animation */} +
+ + Modern Kaplama Sistemleri +
+ + {/* Headline with Animation */} +

+ Mekanlarınıza
+ Estetik ve Güç
+ Katıyoruz. +

+ + {/* Subtext with Animation */} +

+ Akustik panelden PVC mermer plakaya, 3D uygulamalardan PU cephe lambri sistemlerine kadar geniş ürün yelpazemizle hayalinizdeki mekanları gerçeğe dönüştürüyoruz. +

+ + {/* Buttons with Animation */} +
+ + Ücretsiz Teklif Al + + + + + + WhatsApp Destek + +
+ + {/* Stats with Animation */} +
+
+
100+
+
Tamamlanan Proje
+
+
+
15+
+
Yıllık Tecrübe
+
+
+
%100
+
Müşteri Memnuniyeti
+
+
+
+
+ + {/* Scroll Down Indicator */} +
+
+
+
+
+
+ ); +}; + +export default Hero; diff --git a/frontend/src/components/Beka/Navbar.tsx b/frontend/src/components/Beka/Navbar.tsx new file mode 100644 index 0000000..8822736 --- /dev/null +++ b/frontend/src/components/Beka/Navbar.tsx @@ -0,0 +1,172 @@ +import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { mdiMenu, mdiClose, mdiPhone } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; + +const Navbar = () => { + const [isOpen, setIsOpen] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); + const router = useRouter(); + + useEffect(() => { + const handleScroll = () => { + if (window.scrollY > 50) { + setIsScrolled(true); + } else { + setIsScrolled(false); + } + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + // Determine if we are on the home page + const isHome = router.pathname === '/'; + + const navLinks = [ + { name: 'Anasayfa', href: '/' }, + { name: 'Hakkımızda', href: '/hakkimizda' }, + { name: 'Hizmetler', href: '/hizmetler' }, + { name: 'Projeler', href: '/projeler' }, + { name: 'İletişim', href: '/iletisim' }, + ]; + + return ( + + ); +}; + +export default Navbar; diff --git a/frontend/src/components/Beka/PageHero.tsx b/frontend/src/components/Beka/PageHero.tsx new file mode 100644 index 0000000..02e675d --- /dev/null +++ b/frontend/src/components/Beka/PageHero.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import ScrollReveal from './ScrollReveal'; + +interface PageHeroProps { + title: string; + subtitle: string; + image?: string; +} + +const PageHero = ({ + title, + subtitle, + image = "https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" +}: PageHeroProps) => { + return ( +
+ {/* Background with subtle animation */} +
+ {title} +
+
+ +
+ +

+ {title} +

+
+ + +
+
+

+ {subtitle} +

+
+
+
+ + +
+ Anasayfa + / + {title} +
+
+
+ + {/* Accent decoration */} +
+
+ ); +}; + +export default PageHero; \ No newline at end of file diff --git a/frontend/src/components/Beka/Projects.tsx b/frontend/src/components/Beka/Projects.tsx new file mode 100644 index 0000000..27c5d0e --- /dev/null +++ b/frontend/src/components/Beka/Projects.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import { mdiArrowRight, mdiMagnifyPlusOutline } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; +import ScrollReveal from './ScrollReveal'; + +const projects = [ + { + title: 'Modern Ofis Akustik Panel', + category: 'Akustik Panel', + image: 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800' + }, + { + title: 'Dış Cephe PU Lambri', + category: 'PU Lambri', + image: 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800' + }, + { + title: 'Mutfak PVC Mermer Kaplama', + category: 'PVC Mermer', + image: 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800' + }, + { + title: 'Oturma Odası 3D Duvar', + category: '3D Uygulamalar', + image: 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800' + }, + { + title: 'Restoran Ses Yalıtımı', + category: 'Akustik Çözümler', + image: 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800' + }, + { + title: 'Villa Dış Cephe Kaplama', + category: 'PU Cephe', + image: 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800' + } +]; + +const categories = ['Tümü', 'Akustik Panel', 'PU Lambri', 'PVC Mermer', '3D Uygulamalar']; + +const Projects = () => { + const [activeCategory, setActiveCategory] = useState('Tümü'); + + const filteredProjects = activeCategory === 'Tümü' + ? projects + : projects.filter(p => p.category === activeCategory); + + return ( +
+ {/* Background Decoration */} +
+ + + + + + + + +
+ +
+
+ +
+

Referans Projelerimiz

+

Hayata Geçirdiğimiz
En Seçkin Projeler

+
+
+ + +
+ {categories.map((cat) => ( + + ))} +
+
+
+ +
+ {filteredProjects.map((project, index) => ( + +
+
+ {project.title} +
+ + {/* Overlay */} +
+
+ + {project.category} + +

+ {project.title} +

+
+
+ +
+ Projeyi İncele +
+
+
+
+
+ ))} +
+ + +
+

Daha fazla proje ve detaylı bilgi için bize ulaşın.

+ + Tüm Projeleri WhatsApp'tan İste + + +
+
+
+
+ ); +}; + +export default Projects; diff --git a/frontend/src/components/Beka/ScrollReveal.tsx b/frontend/src/components/Beka/ScrollReveal.tsx new file mode 100644 index 0000000..475b664 --- /dev/null +++ b/frontend/src/components/Beka/ScrollReveal.tsx @@ -0,0 +1,43 @@ +import React, { useRef, ReactNode } from 'react'; +import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; + +interface ScrollRevealProps { + children: ReactNode; + animation?: 'fade-in-up' | 'fade-in-left' | 'fade-in-right' | 'scale-in' | 'fade-in'; + delay?: number; + className?: string; + threshold?: number; +} + +const ScrollReveal = ({ + children, + animation = 'fade-in-up', + delay = 0, + className = '', + threshold = 0.1 +}: ScrollRevealProps) => { + const ref = useRef(null); + const entry = useIntersectionObserver(ref, { + threshold, + freezeOnceVisible: true + }); + + const isVisible = !!entry?.isIntersecting; + + const animationClass = isVisible ? `animate-${animation}` : 'opacity-0'; + + return ( +
+ {children} +
+ ); +}; + +export default ScrollReveal; diff --git a/frontend/src/components/Beka/Services.tsx b/frontend/src/components/Beka/Services.tsx new file mode 100644 index 0000000..72e94bf --- /dev/null +++ b/frontend/src/components/Beka/Services.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import Link from 'next/link'; +import BaseIcon from '../BaseIcon'; +import ScrollReveal from './ScrollReveal'; +import { bekaServices } from '../../data/bekaServices'; + +const Services = () => { + return ( +
+ {/* Background Decor */} +
+
+ +
+ +
+

Uzmanlık Alanlarımız

+

Mekanlarınıza Hayat Veren
Uygulamalar

+

+ Beka Kaplama olarak en kaliteli malzemeleri uzman işçilikle birleştirerek yaşam alanlarınıza değer katıyoruz. +

+
+
+ +
+ {bekaServices.map((service, index) => ( + +
+
+ {service.title} +
+
+ +
+
+
+

+ {service.title} +

+

+ {service.shortDescription} +

+
+ + Detayları İncele +
+ + + +
+ +
+
+
+
+ ))} +
+
+
+ ); +}; + +export default Services; \ No newline at end of file diff --git a/frontend/src/components/Beka/WhatsAppButton.tsx b/frontend/src/components/Beka/WhatsAppButton.tsx new file mode 100644 index 0000000..dc69ba4 --- /dev/null +++ b/frontend/src/components/Beka/WhatsAppButton.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { mdiWhatsapp } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; + +const WhatsAppButton = () => { + return ( + +
+ + + {/* Tooltip */} + + WhatsApp Destek Hattı +
+
+
+ ); +}; + +export default WhatsAppButton; \ No newline at end of file diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 72935e6..123ccf7 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, { useEffect, useRef, useState } from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} +} \ No newline at end of file diff --git a/frontend/src/css/main.css b/frontend/src/css/main.css index f061e28..0d925e0 100644 --- a/frontend/src/css/main.css +++ b/frontend/src/css/main.css @@ -12,6 +12,9 @@ @import "_theme.css"; @import '_rich-text.css'; +body { + @apply font-body; +} .introjs-tooltip { @apply min-w-[400px] max-w-[480px] p-2 !important; @@ -32,4 +35,4 @@ } .introjs-prevbutton{ @apply bg-transparent border border-blue-600 text-blue-600 !important; -} +} \ No newline at end of file diff --git a/frontend/src/data/bekaServices.ts b/frontend/src/data/bekaServices.ts new file mode 100644 index 0000000..5e9ace5 --- /dev/null +++ b/frontend/src/data/bekaServices.ts @@ -0,0 +1,88 @@ +import { mdiViewDashboardOutline, mdiWall, mdiHomeCityOutline, mdiTexture } from '@mdi/js'; + +export const bekaServices = [ + { + id: 'akustik-panel', + slug: 'akustik-panel', + title: 'Akustik Panel', + shortDescription: 'Ses yalıtımı ve estetiği bir araya getiren modern panel çözümleri.', + description: 'Beka Kaplama olarak sunduğumuz akustik panel çözümleri, mekanlarınızdaki ses yankısını minimize ederken estetik bir görünüm sağlar. Ofisler, ev sinema odaları, stüdyolar ve geniş yaşam alanları için özel olarak tasarlanan panellerimiz, farklı renk ve doku seçenekleriyle her türlü dekorasyona uyum sağlar.', + details: [ + 'Yüksek ses yutma katsayısı (NRC)', + 'Alev geciktirici özellikli kumaş kaplamalar', + 'Kolay montaj ve uzun ömürlü kullanım', + 'Kişiselleştirilebilir boyut ve renk seçenekleri', + 'Çevre dostu ve sağlığa zararsız materyaller' + ], + icon: mdiViewDashboardOutline, + image: 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2', + gallery: [ + 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800', + 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800', + 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800' + ] + }, + { + id: 'pu-cephe-lambri', + slug: 'pu-cephe-lambri', + title: 'PU Cephe Lambri', + shortDescription: 'Dayanıklı, ısı yalıtımlı ve hafif poliüretan cephe sistemleri.', + description: 'Poliüretan (PU) cephe lambri sistemlerimiz, binalarınızın dış cephesine modern bir dokunuş katarken aynı zamanda üstün ısı yalıtımı sağlar. Hafif yapısı sayesinde binaya ek yük getirmez ve her türlü hava koşuluna karşı dayanıklıdır. Doğal ahşap ve taş görünümlü modellerimizle estetikten ödün vermeden koruma sağlıyoruz.', + details: [ + 'Mükemmel ısı ve ses yalıtımı', + 'Suya, neme ve darbelere dayanıklı yapı', + 'Hafif malzeme ile hızlı ve kolay uygulama', + 'Boyanabilir yüzey ve zengin model seçenekleri', + 'Bakım gerektirmeyen uzun ömürlü çözüm' + ], + icon: mdiHomeCityOutline, + image: 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2', + gallery: [ + 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800', + 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800', + 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800' + ] + }, + { + id: 'pvc-mermer-plaka', + slug: 'pvc-mermer-plaka', + title: 'PVC Mermer Plaka', + shortDescription: 'Gerçek mermer görünümünde, ekonomik kaplama çözümleri.', + description: 'PVC mermer plaka uygulamalarımız, gerçek mermerin ağırlığı ve yüksek maliyeti olmadan lüks bir görünüm elde etmenizi sağlar. Islak hacimler başta olmak üzere tüm iç mekanlarda güvenle kullanılabilir. Kolay temizlenebilir ve antibakteriyel yapısıyla modern yaşam alanlarının vazgeçilmezidir.', + details: [ + '%100 su ve nem dayanıklılığı', + 'Gerçekçi mermer ve doğal taş dokusu', + 'Antibakteriyel ve kolay temizlenebilir yüzey', + 'Hızlı uygulama ve derz gerektirmeyen yapı', + 'Yüksek parlaklık ve çizilme direnci' + ], + icon: mdiWall, + image: 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2', + gallery: [ + 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800', + 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800', + 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800' + ] + }, + { + id: '3d-uygulamalar', + slug: '3d-uygulamalar', + title: '3D Uygulamalar', + shortDescription: 'Mekanlarınıza derinlik katan üç boyutlu duvar kaplamaları.', + description: '3D duvar paneli uygulamalarımızla monoton duvarları sanat eserine dönüştürüyoruz. Işık oyunlarıyla birleşen geometrik ve organik desenler, mekanınıza benzersiz bir derinlik ve karakter katar. Hem ticari mekanlarda hem de evlerde odak noktası oluşturmak için ideal bir tercihtir.', + details: [ + 'Üç boyutlu görsel derinlik ve doku', + 'Özel aydınlatma ile vurgulanabilir desenler', + 'Akustik performansa katkı sağlayan yapı', + 'Farklı malzeme seçenekleri (Alçı, PU, Polimer)', + 'Hızlı dönüşüm sağlayan pratik montaj' + ], + icon: mdiTexture, + image: 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2', + gallery: [ + 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800', + 'https://images.pexels.com/photos/1571460/pexels-photo-1571460.jpeg?auto=compress&cs=tinysrgb&w=800', + 'https://images.pexels.com/photos/1571470/pexels-photo-1571470.jpeg?auto=compress&cs=tinysrgb&w=800' + ] + } +]; diff --git a/frontend/src/hooks/useIntersectionObserver.ts b/frontend/src/hooks/useIntersectionObserver.ts new file mode 100644 index 0000000..627b85a --- /dev/null +++ b/frontend/src/hooks/useIntersectionObserver.ts @@ -0,0 +1,41 @@ +import { useState, useEffect, RefObject } from 'react'; + +interface Options extends IntersectionObserverInit { + freezeOnceVisible?: boolean; +} + +export function useIntersectionObserver( + elementRef: RefObject, + { + threshold = 0, + root = null, + rootMargin = '0%', + freezeOnceVisible = false, + }: Options +): IntersectionObserverEntry | undefined { + const [entry, setEntry] = useState(); + + const frozen = entry?.isIntersecting && freezeOnceVisible; + + const updateEntry = ([entry]: IntersectionObserverEntry[]): void => { + setEntry(entry); + }; + + useEffect(() => { + const node = elementRef?.current; // DOM Ref + const hasIOSupport = !!window.IntersectionObserver; + + if (!hasIOSupport || frozen || !node) return; + + const observerParams = { threshold, root, rootMargin }; + const observer = new IntersectionObserver(updateEntry, observerParams); + + observer.observe(node); + + return () => observer.disconnect(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [elementRef?.current, JSON.stringify(threshold), root, rootMargin, frozen]); + + return entry; +} diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -126,4 +125,4 @@ export default function LayoutAuthenticated({ ) -} +} \ No newline at end of file diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index a0c0846..79c92a0 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -180,6 +180,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { + + + + @@ -198,4 +202,4 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ) } -export default appWithTranslation(MyApp); +export default appWithTranslation(MyApp); \ No newline at end of file diff --git a/frontend/src/pages/hakkimizda.tsx b/frontend/src/pages/hakkimizda.tsx new file mode 100644 index 0000000..eb0cea2 --- /dev/null +++ b/frontend/src/pages/hakkimizda.tsx @@ -0,0 +1,39 @@ +import React, { ReactElement } from 'react'; +import Head from 'next/head'; +import LayoutGuest from '../layouts/Guest'; +import { getPageTitle } from '../config'; +import Navbar from '../components/Beka/Navbar'; +import About from '../components/Beka/About'; +import PageHero from '../components/Beka/PageHero'; +import Footer from '../components/Beka/Footer'; +import WhatsAppButton from '../components/Beka/WhatsAppButton'; + +export default function Hakkimizda() { + return ( +
+ + {getPageTitle('Hakkımızda | Beka Kaplama')} + + + + + +
+ + +
+ +
+ + +
+ ); +} + +Hakkimizda.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; \ No newline at end of file diff --git a/frontend/src/pages/hizmet/[slug].tsx b/frontend/src/pages/hizmet/[slug].tsx new file mode 100644 index 0000000..4c2eb81 --- /dev/null +++ b/frontend/src/pages/hizmet/[slug].tsx @@ -0,0 +1,166 @@ +import React, { ReactElement } from 'react'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import Link from 'next/link'; +import { mdiArrowLeft, mdiChevronRight, mdiCheckCircleOutline } from '@mdi/js'; +import LayoutGuest from '../../layouts/Guest'; +import { getPageTitle } from '../../config'; +import Navbar from '../../components/Beka/Navbar'; +import Footer from '../../components/Beka/Footer'; +import WhatsAppButton from '../../components/Beka/WhatsAppButton'; +import PageHero from '../../components/Beka/PageHero'; +import ScrollReveal from '../../components/Beka/ScrollReveal'; +import BaseIcon from '../../components/BaseIcon'; +import { bekaServices } from '../../data/bekaServices'; + +export default function ServiceDetail() { + const router = useRouter(); + const { slug } = router.query; + + const service = bekaServices.find((s) => s.slug === slug); + + if (!service && router.isReady) { + return ( +
+
+

Hizmet Bulunamadı

+ + Tüm Hizmetlerimize Geri Dön + +
+
+ ); + } + + if (!service) return null; + + return ( +
+ + {getPageTitle(`${service.title} | Beka Kaplama`)} + + + + + +
+ + + {/* Breadcrumb */} +
+
+ +
+
+ +
+
+
+ {/* Left Side: Content */} + +
+

Neler Yapıyoruz?

+

+ {service.title} Çözümlerimiz +

+ +
+

+ {service.description} +

+
+ +
+ {service.details.map((detail, idx) => ( +
+
+ +
+ {detail} +
+ ))} +
+ + + Fiyat Teklifi Alın + + +
+
+ + {/* Right Side: Visuals */} + +
+
+ {service.title} +
+
+ +
+ {service.gallery.slice(1).map((img, idx) => ( +
+ {`${service.title} +
+ ))} +
+
+
+
+
+
+ + {/* Other Services Section */} +
+
+
+

Diğer Hizmetlerimiz

+
+
+ {bekaServices.filter(s => s.id !== service.id).slice(0, 3).map((other) => ( + +
+ +
+

{other.title}

+

{other.shortDescription}

+ + ))} +
+
+
+
+ +
+ +
+ ); +} + +ServiceDetail.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; diff --git a/frontend/src/pages/hizmetler.tsx b/frontend/src/pages/hizmetler.tsx new file mode 100644 index 0000000..79704da --- /dev/null +++ b/frontend/src/pages/hizmetler.tsx @@ -0,0 +1,39 @@ +import React, { ReactElement } from 'react'; +import Head from 'next/head'; +import LayoutGuest from '../layouts/Guest'; +import { getPageTitle } from '../config'; +import Navbar from '../components/Beka/Navbar'; +import Services from '../components/Beka/Services'; +import PageHero from '../components/Beka/PageHero'; +import Footer from '../components/Beka/Footer'; +import WhatsAppButton from '../components/Beka/WhatsAppButton'; + +export default function Hizmetler() { + return ( +
+ + {getPageTitle('Hizmetlerimiz | Beka Kaplama')} + + + + + +
+ + +
+ +
+ + +
+ ); +} + +Hizmetler.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; \ No newline at end of file diff --git a/frontend/src/pages/iletisim.tsx b/frontend/src/pages/iletisim.tsx new file mode 100644 index 0000000..4ed6cfd --- /dev/null +++ b/frontend/src/pages/iletisim.tsx @@ -0,0 +1,39 @@ +import React, { ReactElement } from 'react'; +import Head from 'next/head'; +import LayoutGuest from '../layouts/Guest'; +import { getPageTitle } from '../config'; +import Navbar from '../components/Beka/Navbar'; +import ContactForm from '../components/Beka/ContactForm'; +import PageHero from '../components/Beka/PageHero'; +import Footer from '../components/Beka/Footer'; +import WhatsAppButton from '../components/Beka/WhatsAppButton'; + +export default function Iletisim() { + return ( +
+ + {getPageTitle('İletişim | Beka Kaplama')} + + + + + +
+ + +
+ +
+ + +
+ ); +} + +Iletisim.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 53a3687..9c24378 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,41 @@ - -import React, { useEffect, useState } from 'react'; -import type { ReactElement } from 'react'; +import React, { ReactElement } from 'react'; import Head from 'next/head'; -import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; - - -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('image'); - const [contentPosition, setContentPosition] = useState('right'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'Beka Kaplama Website' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( - - ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- - -
) - } - }; +import Navbar from '../components/Beka/Navbar'; +import Hero from '../components/Beka/Hero'; +import Services from '../components/Beka/Services'; +import About from '../components/Beka/About'; +import Projects from '../components/Beka/Projects'; +import ContactForm from '../components/Beka/ContactForm'; +import Footer from '../components/Beka/Footer'; +import WhatsAppButton from '../components/Beka/WhatsAppButton'; +export default function Home() { return ( -
+
- {getPageTitle('Starter Page')} + {getPageTitle('Beka Kaplama | Modern Kaplama Sistemleri')} + - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

-
- - - - - -
-
-
-
-
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+ + +
+ + + + + +
+
+ +
); } -Starter.getLayout = function getLayout(page: ReactElement) { +Home.getLayout = function getLayout(page: ReactElement) { return {page}; -}; - +}; \ No newline at end of file diff --git a/frontend/src/pages/projeler.tsx b/frontend/src/pages/projeler.tsx new file mode 100644 index 0000000..bc8d546 --- /dev/null +++ b/frontend/src/pages/projeler.tsx @@ -0,0 +1,39 @@ +import React, { ReactElement } from 'react'; +import Head from 'next/head'; +import LayoutGuest from '../layouts/Guest'; +import { getPageTitle } from '../config'; +import Navbar from '../components/Beka/Navbar'; +import Projects from '../components/Beka/Projects'; +import PageHero from '../components/Beka/PageHero'; +import Footer from '../components/Beka/Footer'; +import WhatsAppButton from '../components/Beka/WhatsAppButton'; + +export default function Projeler() { + return ( +
+ + {getPageTitle('Projelerimiz | Beka Kaplama')} + + + + + +
+ + +
+ +
+ + +
+ ); +} + +Projeler.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index a06c7d1..e7189c5 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -13,6 +13,11 @@ module.exports = { gray: 'gray' }, extend: { + fontFamily: { + sans: ['Montserrat', 'ui-sans-serif', 'system-ui'], + serif: ['Playfair Display', 'ui-serif', 'Georgia'], + body: ['Lato', 'ui-sans-serif', 'system-ui'], + }, zIndex: { '-1': '-1' }, @@ -35,11 +40,66 @@ module.exports = { 'fade-in': { from: { opacity: 0 }, to: { opacity: 1 } + }, + 'fade-in-up': { + '0%': { + opacity: '0', + transform: 'translateY(20px)' + }, + '100%': { + opacity: '1', + transform: 'translateY(0)' + }, + }, + 'fade-in-left': { + '0%': { + opacity: '0', + transform: 'translateX(-20px)' + }, + '100%': { + opacity: '1', + transform: 'translateX(0)' + }, + }, + 'fade-in-right': { + '0%': { + opacity: '0', + transform: 'translateX(20px)' + }, + '100%': { + opacity: '1', + transform: 'translateX(0)' + }, + }, + 'scale-in': { + '0%': { + opacity: '0', + transform: 'scale(0.95)' + }, + '100%': { + opacity: '1', + transform: 'scale(1)' + }, + }, + 'bounce-soft': { + '0%, 100%': { + transform: 'translateY(-5%)', + animationTimingFunction: 'cubic-bezier(0.8, 0, 1, 1)' + }, + '50%': { + transform: 'translateY(0)', + animationTimingFunction: 'cubic-bezier(0, 0, 0.2, 1)' + } } }, animation: { 'fade-out': 'fade-out 250ms ease-in-out', - 'fade-in': 'fade-in 250ms ease-in-out' + 'fade-in': 'fade-in 250ms ease-in-out', + 'fade-in-up': 'fade-in-up 0.6s ease-out forwards', + 'fade-in-left': 'fade-in-left 0.6s ease-out forwards', + 'fade-in-right': 'fade-in-right 0.6s ease-out forwards', + 'scale-in': 'scale-in 0.5s ease-out forwards', + 'bounce-soft': 'bounce-soft 3s infinite', }, colors: { dark: { @@ -107,4 +167,4 @@ module.exports = { ); }), ], -} +} \ No newline at end of file