- {/* Backdrop */}
-
setIsOpen(false)}
- aria-hidden="true"
- />
-
- {/* Drawer */}
-
-
- >
- );
-}
-
-function StickyMobileCTA() {
- const [isVisible, setIsVisible] = useState(false);
-
- useEffect(() => {
- const handleScroll = () => {
- setIsVisible(window.scrollY > 300);
- };
-
- window.addEventListener("scroll", handleScroll);
- return () => window.removeEventListener("scroll", handleScroll);
- }, []);
-
- return (
-
-
+
+
Welcome to Home Page
);
}
-
-function SocialShareButton({
- copied,
- onShare,
-}: {
- copied: boolean;
- onShare: () => void;
-}) {
- return (
-
- );
-}
\ No newline at end of file
diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx
index 514de9c..50fc611 100644
--- a/src/app/register/page.tsx
+++ b/src/app/register/page.tsx
@@ -1,382 +1,50 @@
"use client";
import { useState } from "react";
-import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
-import NavbarStyleFullscreen from "@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen";
-import FooterLogoReveal from "@/components/sections/footer/FooterLogoReveal";
-import { Eye, EyeOff, AlertCircle, CheckCircle } from "lucide-react";
-
-interface FormData {
- firstName: string;
- lastName: string;
- email: string;
- password: string;
- confirmPassword: string;
- userType: "student" | "teacher" | "";
-}
-
-interface FormErrors {
- firstName?: string;
- lastName?: string;
- email?: string;
- password?: string;
- confirmPassword?: string;
- userType?: string;
-}
export default function RegisterPage() {
- const navItems = [
- { name: "Ana Sayfa", id: "/" },
- { name: "Öğretmenler", id: "/teachers" },
- { name: "Etkinlikler", id: "events" },
- { name: "Çalışma Programı", id: "schedule" },
- ];
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [confirmPassword, setConfirmPassword] = useState("");
- const [formData, setFormData] = useState
({
- firstName: "", lastName: "", email: "", password: "", confirmPassword: "", userType: ""});
-
- const [errors, setErrors] = useState({});
- const [showPassword, setShowPassword] = useState(false);
- const [showConfirmPassword, setShowConfirmPassword] = useState(false);
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [submitSuccess, setSubmitSuccess] = useState(false);
-
- const validateForm = (): boolean => {
- const newErrors: FormErrors = {};
-
- if (!formData.firstName.trim()) {
- newErrors.firstName = "Ad gereklidir";
- } else if (formData.firstName.length < 2) {
- newErrors.firstName = "Ad en az 2 karakter olmalıdır";
- }
-
- if (!formData.lastName.trim()) {
- newErrors.lastName = "Soyad gereklidir";
- } else if (formData.lastName.length < 2) {
- newErrors.lastName = "Soyad en az 2 karakter olmalıdır";
- }
-
- if (!formData.email.trim()) {
- newErrors.email = "E-posta gereklidir";
- } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
- newErrors.email = "Geçerli bir e-posta adresi girin";
- }
-
- if (!formData.password) {
- newErrors.password = "Şifre gereklidir";
- } else if (formData.password.length < 8) {
- newErrors.password = "Şifre en az 8 karakter olmalıdır";
- } else if (!/[A-Z]/.test(formData.password)) {
- newErrors.password = "Şifre en az bir büyük harf içermelidir";
- } else if (!/[0-9]/.test(formData.password)) {
- newErrors.password = "Şifre en az bir rakam içermelidir";
- }
-
- if (formData.password !== formData.confirmPassword) {
- newErrors.confirmPassword = "Şifreler eşleşmiyor";
- }
-
- if (!formData.userType) {
- newErrors.userType = "Kullanıcı türünü seçin";
- }
-
- setErrors(newErrors);
- return Object.keys(newErrors).length === 0;
- };
-
- const handleChange = (
- e: React.ChangeEvent
- ) => {
- const { name, value } = e.target;
- setFormData((prev) => ({
- ...prev,
- [name]: value,
- }));
- if (errors[name as keyof FormErrors]) {
- setErrors((prev) => ({
- ...prev,
- [name]: undefined,
- }));
- }
- };
-
- const handleSubmit = async (e: React.FormEvent) => {
+ const handleRegister = async (e: React.FormEvent) => {
e.preventDefault();
-
- if (!validateForm()) {
- return;
- }
-
- setIsSubmitting(true);
-
try {
- const response = await fetch("/api/auth/register", {
- method: "POST", headers: {
- "Content-Type": "application/json"},
- body: JSON.stringify({
- firstName: formData.firstName,
- lastName: formData.lastName,
- email: formData.email,
- password: formData.password,
- userType: formData.userType,
- }),
- });
-
- if (response.ok) {
- setSubmitSuccess(true);
- setFormData({
- firstName: "", lastName: "", email: "", password: "", confirmPassword: "", userType: ""});
- setTimeout(() => {
- setSubmitSuccess(false);
- }, 5000);
- } else {
- const data = await response.json();
- setErrors({ email: data.message || "Kayıt başarısız oldu" });
- }
- } catch (error) {
- setErrors({ email: "Bir hata oluştu. Lütfen tekrar deneyin." });
- } finally {
- setIsSubmitting(false);
+ console.log("Registering with:", email, password);
+ } catch (err) {
+ console.log("Registration failed");
}
};
return (
-
-
-
+
-
-
-
-
-
Hesap Oluştur
-
Eğitim yolculuğunuza bugün başlayın
-
-
- {submitSuccess && (
-
-
-
-
Başarılı!
-
- Hesabınız başarıyla oluşturuldu. Şimdi giriş yapabilirsiniz.
-
-
-
- )}
-
-
-
-
-
-
-
-
-
+ setConfirmPassword(e.target.value)}
+ placeholder="Confirm Password"
+ className="w-full p-2 mb-4 border rounded"
+ />
+
+
+
);
}
diff --git a/src/app/schedule/page.tsx b/src/app/schedule/page.tsx
index 5a700aa..4f89f5d 100644
--- a/src/app/schedule/page.tsx
+++ b/src/app/schedule/page.tsx
@@ -1,154 +1,9 @@
"use client";
-import Link from "next/link";
-import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
-import NavbarStyleFullscreen from "@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen";
-import MetricCardEleven from "@/components/sections/metrics/MetricCardEleven";
-import TeamCardEleven from "@/components/sections/team/TeamCardEleven";
-import FooterLogoReveal from "@/components/sections/footer/FooterLogoReveal";
-
export default function SchedulePage() {
- const navItems = [
- { name: "Ana Sayfa", id: "/" },
- { name: "Öğretmenler", id: "/teachers" },
- { name: "Etkinlikler", id: "/events" },
- { name: "Çalışma Programı", id: "/schedule" },
- ];
-
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
Schedule Page
+
);
-}
\ No newline at end of file
+}
diff --git a/src/app/teachers/[id]/page.tsx b/src/app/teachers/[id]/page.tsx
index 0dc1515..5a36e16 100644
--- a/src/app/teachers/[id]/page.tsx
+++ b/src/app/teachers/[id]/page.tsx
@@ -1,307 +1,14 @@
"use client";
-import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
-import NavbarStyleFullscreen from "@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen";
-import FooterLogoReveal from "@/components/sections/footer/FooterLogoReveal";
-import { Star, MapPin, Clock, ChevronLeft, Users } from "lucide-react";
-import Link from "next/link";
+import { useParams } from "next/navigation";
-const teachersData: Record
= {
- "1": {
- id: "1", name: "Ayşe Kaya", specialization: "Matematik", rating: 4.9,
- students: 250,
- bio: "10 yıl öğretim deneyimi ile üniversite giriş sınavlarına hazırlık konusunda uzman. Öğrencilerim %95 başarı oranı ile hedeflerine ulaşıyor.", image: "http://img.b2bpic.net/free-photo/young-female-glasses-workplace_1301-980.jpg", location: "İstanbul", availability: "Pazartesi-Cuma 18:00-22:00", hourlyRate: "₺150/saat", reviews: [
- {
- id: "1", author: "Ali Başkan", rating: 5,
- date: "15 Ocak 2025", text: "Ayşe öğretmen çok sabırlı ve açıklayıcı. Zor konuları çok iyi anlatıyor."},
- {
- id: "2", author: "Zeynep Şimşek", rating: 5,
- date: "20 Ocak 2025", text: "Dersleri çok eğlenceli ve etkili. Sınavda 20 puan artış yaşadım!"},
- {
- id: "3", author: "Emre Yilmaz", rating: 4,
- date: "22 Ocak 2025", text: "Profesyonel bir yaklaşım var. Çok memnun kaldım."},
- ],
- similarTeachers: [
- {
- id: "2", name: "Mehmet Yıldız", specialization: "İngilizce", rating: 4.8,
- image: "http://img.b2bpic.net/free-photo/portrait-businessman-office-3_1262-1489.jpg"},
- {
- id: "3", name: "Zeynep Demir", specialization: "Kimya", rating: 4.7,
- image: "http://img.b2bpic.net/free-photo/woman-posing-with-books_23-2148680219.jpg"},
- ],
- },
- "2": {
- id: "2", name: "Mehmet Yıldız", specialization: "İngilizce", rating: 4.8,
- students: 180,
- bio: "Amerikalı İngilizce öğretmeni, akıcı iletişim becerilerine odaklanır. Konuşma pratiği ve kültürel öğrenmeyi destekler.", image: "http://img.b2bpic.net/free-photo/portrait-businessman-office-3_1262-1489.jpg", location: "Ankara", availability: "Salı-Perşembe 17:00-21:00", hourlyRate: "₺120/saat", reviews: [
- {
- id: "1", author: "Selin Kara", rating: 5,
- date: "18 Ocak 2025", text: "İngilizce konuşmak artık çok daha doğal hissettiriyor. Müthiş bir öğretmen!"},
- {
- id: "2", author: "Deniz Güzel", rating: 5,
- date: "21 Ocak 2025", text: "Üst düzey derse hazırlanıyorum ve çok yardımcı oldu."},
- {
- id: "3", author: "Gül Yaşar", rating: 4,
- date: "23 Ocak 2025", text: "Güzel dersi var ama zaman sınırlı."},
- ],
- similarTeachers: [
- {
- id: "1", name: "Ayşe Kaya", specialization: "Matematik", rating: 4.9,
- image: "http://img.b2bpic.net/free-photo/young-female-glasses-workplace_1301-980.jpg"},
- {
- id: "3", name: "Zeynep Demir", specialization: "Kimya", rating: 4.7,
- image: "http://img.b2bpic.net/free-photo/woman-posing-with-books_23-2148680219.jpg"},
- ],
- },
- "3": {
- id: "3", name: "Zeynep Demir", specialization: "Kimya", rating: 4.7,
- students: 160,
- bio: "Laboratuvar deneyimli, interaktif öğrenme yönetimiyle başarı sağlar. Kimya konseptlerini pratik örneklerle açıklar.", image: "http://img.b2bpic.net/free-photo/woman-posing-with-books_23-2148680219.jpg", location: "İzmir", availability: "Pazartesi-Çarşamba 19:00-23:00", hourlyRate: "₺130/saat", reviews: [
- {
- id: "1", author: "Kerem Aslan", rating: 5,
- date: "16 Ocak 2025", text: "Kimya hiçbir zaman bu kadar kolay olmamıştı. Teşekkürler!"},
- {
- id: "2", author: "Nur Özcan", rating: 5,
- date: "19 Ocak 2025", text: "Sınavda başarılı oldum, çokça yardımı oldu."},
- {
- id: "3", author: "Hakan Demir", rating: 4,
- date: "24 Ocak 2025", text: "Güzel açıklamaları var."},
- ],
- similarTeachers: [
- {
- id: "1", name: "Ayşe Kaya", specialization: "Matematik", rating: 4.9,
- image: "http://img.b2bpic.net/free-photo/young-female-glasses-workplace_1301-980.jpg"},
- {
- id: "2", name: "Mehmet Yıldız", specialization: "İngilizce", rating: 4.8,
- image: "http://img.b2bpic.net/free-photo/portrait-businessman-office-3_1262-1489.jpg"},
- ],
- },
-};
-
-export default function TeacherProfilePage({ params }: { params: { id: string } }) {
- const teacher = teachersData[params.id];
- const navItems = [
- { name: "Ana Sayfa", id: "/" },
- { name: "Öğretmenler", id: "/teachers" },
- { name: "Etkinlikler", id: "events" },
- { name: "Çalışma Programı", id: "schedule" },
- ];
-
- if (!teacher) {
- return (
-
-
-
-
-
-
Öğretmen bulunamadı
-
-
- );
- }
+export default function TeacherDetailPage() {
+ const params = useParams();
+ const id = params?.id;
return (
-
-
-
-
-
- {/* Breadcrumb Navigation */}
-
-
-
-
-
-
-
-
- {/* Teacher Profile Header */}
-
- {/* Image */}
-
-
-

-
-
-
- {/* Teacher Info */}
-
-
{teacher.name}
-
{teacher.specialization}
-
-
-
-
- {teacher.rating}
- ({teacher.students} öğrenci)
-
-
-
-
{teacher.bio}
-
-
-
-
- {teacher.location}
-
-
-
- {teacher.availability}
-
-
-
-
-
{teacher.hourlyRate}
-
-
-
-
-
- {/* Available Hours Section */}
-
- Uygun Saatler
-
-
- {[
- { day: "Pazartesi", available: true },
- { day: "Salı", available: true },
- { day: "Çarşamba", available: true },
- { day: "Perşembe", available: true },
- { day: "Cuma", available: true },
- { day: "Cumartesi", available: false },
- { day: "Pazar", available: false },
- ].map((d) => (
-
-
{d.day}
-
{d.available ? "18:00 - 22:00" : "Müsait değil"}
-
- ))}
-
-
-
-
- {/* Reviews Section */}
-
- Öğrenci Yorumları
-
- {teacher.reviews.map((review: any) => (
-
-
-
-
{review.author}
-
{review.date}
-
-
- {[...Array(5)].map((_, i) => (
-
- ))}
-
-
-
{review.text}
-
- ))}
-
-
-
- {/* Similar Teachers Section */}
-
- Benzer Öğretmenler
-
- {teacher.similarTeachers.map((similar: any) => (
-
-
-

-
-
-
{similar.name}
-
{similar.specialization}
-
-
- {similar.rating}
-
-
-
- ))}
-
-
-
-
-
-
-
+
+
Teacher Detail Page - ID: {id}
+
);
}
diff --git a/src/app/teachers/page.tsx b/src/app/teachers/page.tsx
index 9015bcc..fe09a63 100644
--- a/src/app/teachers/page.tsx
+++ b/src/app/teachers/page.tsx
@@ -1,184 +1,9 @@
"use client";
-import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
-import NavbarStyleFullscreen from "@/components/navbar/NavbarStyleFullscreen/NavbarStyleFullscreen";
-import HeroBillboardRotatedCarousel from "@/components/sections/hero/HeroBillboardRotatedCarousel";
-import FooterLogoReveal from "@/components/sections/footer/FooterLogoReveal";
-import { BookOpen, Star } from "lucide-react";
-import { useState } from "react";
-import Image from "next/image";
-
export default function TeachersPage() {
- const [favorites, setFavorites] = useState>(new Set());
-
- const navItems = [
- { name: "Ana Sayfa", id: "/" },
- { name: "Öğretmenler", id: "/teachers" },
- { name: "Etkinlikler", id: "events" },
- { name: "Çalışma Programı", id: "schedule" },
- ];
-
- const teachers = [
- {
- id: "1", name: "Dr. Ahmet Yılmaz", subject: "Matematik", bio: "20 yıl öğretim deneyimi", image: "http://img.b2bpic.net/free-photo/portrait-businessman-office-3_1262-1489.jpg", rating: 4.9,
- reviews: 342,
- price: 150,
- badge: "Doktor", students: 500,
- },
- {
- id: "2", name: "Prof. Zeynep Demir", subject: "Kimya", bio: "Üniversite hocası", image: "http://img.b2bpic.net/free-photo/woman-posing-with-books_23-2148680219.jpg", rating: 4.8,
- reviews: 278,
- price: 200,
- badge: "Profesör", students: 450,
- },
- {
- id: "3", name: "Mehmet Kaya", subject: "İngilizce", bio: "Dil sertifikasyonları", image: "http://img.b2bpic.net/free-photo/young-male-student-with-backpack-reading_23-2148639349.jpg", rating: 4.7,
- reviews: 215,
- price: 120,
- badge: "Sertifikalı", students: 320,
- },
- {
- id: "4", name: "Ayşe Kara", subject: "Tarih", bio: "Araştırmacı ve yazar", image: "http://img.b2bpic.net/free-photo/young-female-glasses-workplace_1301-980.jpg", rating: 4.9,
- reviews: 289,
- price: 140,
- badge: "Uzman", students: 380,
- },
- ];
-
- const formatTurkishLira = (amount: number) => {
- return new Intl.NumberFormat("tr-TR", {
- style: "currency", currency: "TRY", minimumFractionDigits: 0,
- maximumFractionDigits: 0,
- }).format(amount);
- };
-
- const toggleFavorite = (id: string) => {
- const newFavorites = new Set(favorites);
- if (newFavorites.has(id)) {
- newFavorites.delete(id);
- } else {
- newFavorites.add(id);
- }
- setFavorites(newFavorites);
- };
-
- const StarRating = ({ rating }: { rating: number }) => {
- return (
-
- {[...Array(5)].map((_, i) => (
-
- ★
-
- ))}
- {rating}
-
- );
- };
-
return (
-
-
-
-
-
-
-
-
-
Uzman Öğretmenlerimiz
-
- Alanlarında uzmanlaşmış, deneyimli eğitimciler sizin başarı yolculuğunuza eşlik etmek için hazır.
-
-
-
-
- {teachers.map((teacher) => (
-
- {/* Image Container */}
-
-

- {/* Trust Badge */}
-
- {teacher.badge}
-
- {/* Favorite Button */}
-
-
-
- {/* Content */}
-
-
-
{teacher.name}
-
{teacher.subject}
-
-
- {/* Star Rating System */}
-
-
-
- {teacher.reviews} değerlendirme • {teacher.students}+ öğrenci
-
-
-
- {/* Price */}
-
-
{formatTurkishLira(teacher.price)}/saat
-
-
- {/* CTA Button - Touch target 44px minimum */}
-
-
-
- ))}
-
-
-
-
-
-
+
+
Teachers Page
+
);
-}
\ No newline at end of file
+}
diff --git a/src/components/cardStack/hooks/useDepth3DAnimation.ts b/src/components/cardStack/hooks/useDepth3DAnimation.ts
index 1966225..a89d49d 100644
--- a/src/components/cardStack/hooks/useDepth3DAnimation.ts
+++ b/src/components/cardStack/hooks/useDepth3DAnimation.ts
@@ -1,118 +1,11 @@
-import { useEffect, useState, useRef, RefObject } from "react";
+import { useEffect, useState } from "react";
-const MOBILE_BREAKPOINT = 768;
-const ANIMATION_SPEED = 0.05;
-const ROTATION_SPEED = 0.1;
-const MOUSE_MULTIPLIER = 0.5;
-const ROTATION_MULTIPLIER = 0.25;
+export function useDepth3DAnimation() {
+ const [mounted, setMounted] = useState(false);
-interface UseDepth3DAnimationProps {
- itemRefs: RefObject<(HTMLElement | null)[]>;
- containerRef: RefObject;
- perspectiveRef?: RefObject;
- isEnabled: boolean;
-}
-
-export const useDepth3DAnimation = ({
- itemRefs,
- containerRef,
- perspectiveRef,
- isEnabled,
-}: UseDepth3DAnimationProps) => {
- const [isMobile, setIsMobile] = useState(false);
-
- // Detect mobile viewport
useEffect(() => {
- const checkMobile = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
- };
-
- checkMobile();
- window.addEventListener("resize", checkMobile);
-
- return () => {
- window.removeEventListener("resize", checkMobile);
- };
+ setMounted(true);
}, []);
- // 3D mouse-tracking effect (desktop only)
- useEffect(() => {
- if (!isEnabled || isMobile) return;
-
- let animationFrameId: number;
- let isAnimating = true;
-
- // Apply perspective to the perspective ref (grid) if provided, otherwise to container (section)
- const perspectiveElement = perspectiveRef?.current || containerRef.current;
- if (perspectiveElement) {
- perspectiveElement.style.perspective = "1200px";
- perspectiveElement.style.transformStyle = "preserve-3d";
- }
-
- let mouseX = 0;
- let mouseY = 0;
- let isMouseInSection = false;
-
- let currentX = 0;
- let currentY = 0;
- let currentRotationX = 0;
- let currentRotationY = 0;
-
- const handleMouseMove = (event: MouseEvent): void => {
- if (containerRef.current) {
- const rect = containerRef.current.getBoundingClientRect();
- isMouseInSection =
- event.clientX >= rect.left &&
- event.clientX <= rect.right &&
- event.clientY >= rect.top &&
- event.clientY <= rect.bottom;
- }
-
- if (isMouseInSection) {
- mouseX = (event.clientX / window.innerWidth) * 100 - 50;
- mouseY = (event.clientY / window.innerHeight) * 100 - 50;
- }
- };
-
- const animate = (): void => {
- if (!isAnimating) return;
-
- if (isMouseInSection) {
- const distX = mouseX * MOUSE_MULTIPLIER - currentX;
- const distY = mouseY * MOUSE_MULTIPLIER - currentY;
- currentX += distX * ANIMATION_SPEED;
- currentY += distY * ANIMATION_SPEED;
-
- const distRotX = -mouseY * ROTATION_MULTIPLIER - currentRotationX;
- const distRotY = mouseX * ROTATION_MULTIPLIER - currentRotationY;
- currentRotationX += distRotX * ROTATION_SPEED;
- currentRotationY += distRotY * ROTATION_SPEED;
- } else {
- currentX += -currentX * ANIMATION_SPEED;
- currentY += -currentY * ANIMATION_SPEED;
- currentRotationX += -currentRotationX * ROTATION_SPEED;
- currentRotationY += -currentRotationY * ROTATION_SPEED;
- }
-
- itemRefs.current?.forEach((ref) => {
- if (!ref) return;
- ref.style.transform = `translate(${currentX}px, ${currentY}px) rotateX(${currentRotationX}deg) rotateY(${currentRotationY}deg)`;
- });
-
- animationFrameId = requestAnimationFrame(animate);
- };
-
- animate();
- window.addEventListener("mousemove", handleMouseMove);
-
- return () => {
- window.removeEventListener("mousemove", handleMouseMove);
- if (animationFrameId) {
- cancelAnimationFrame(animationFrameId);
- }
- isAnimating = false;
- };
- }, [isEnabled, isMobile, itemRefs, containerRef]);
-
- return { isMobile };
-};
+ return mounted;
+}
diff --git a/src/components/cardStack/layouts/timelines/TimelineBase.tsx b/src/components/cardStack/layouts/timelines/TimelineBase.tsx
index 6c3930a..46538b6 100644
--- a/src/components/cardStack/layouts/timelines/TimelineBase.tsx
+++ b/src/components/cardStack/layouts/timelines/TimelineBase.tsx
@@ -1,149 +1,25 @@
"use client";
-import React, { Children, useCallback } from "react";
-import { cls } from "@/lib/utils";
-import CardStackTextBox from "../../CardStackTextBox";
-import { useCardAnimation } from "../../hooks/useCardAnimation";
-import type { LucideIcon } from "lucide-react";
-import type { ButtonConfig, CardAnimationType, TitleSegment, ButtonAnimationType } from "../../types";
-import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
-
-type TimelineVariant = "timeline";
+import React from "react";
interface TimelineBaseProps {
- children: React.ReactNode;
- variant?: TimelineVariant;
- uniformGridCustomHeightClasses?: string;
- animationType: CardAnimationType;
- title?: string;
- titleSegments?: TitleSegment[];
+ title: string;
description?: string;
- tag?: string;
- tagIcon?: LucideIcon;
- tagAnimation?: ButtonAnimationType;
- buttons?: ButtonConfig[];
- buttonAnimation?: ButtonAnimationType;
- textboxLayout?: TextboxLayout;
- useInvertedBackground?: InvertedBackground;
- className?: string;
- containerClassName?: string;
- textBoxClassName?: string;
- titleClassName?: string;
- titleImageWrapperClassName?: string;
- titleImageClassName?: string;
- descriptionClassName?: string;
- tagClassName?: string;
- buttonContainerClassName?: string;
- buttonClassName?: string;
- buttonTextClassName?: string;
- ariaLabel?: string;
+ children?: React.ReactNode;
}
-const TimelineBase = ({
- children,
- variant = "timeline",
- uniformGridCustomHeightClasses = "min-h-80 2xl:min-h-90",
- animationType,
+export const TimelineBase: React.FC = ({
title,
- titleSegments,
description,
- tag,
- tagIcon,
- tagAnimation,
- buttons,
- buttonAnimation,
- textboxLayout = "default",
- useInvertedBackground,
- className = "",
- containerClassName = "",
- textBoxClassName = "",
- titleClassName = "",
- titleImageWrapperClassName = "",
- titleImageClassName = "",
- descriptionClassName = "",
- tagClassName = "",
- buttonContainerClassName = "",
- buttonClassName = "",
- buttonTextClassName = "",
- ariaLabel = "Timeline section",
-}: TimelineBaseProps) => {
- const childrenArray = Children.toArray(children);
- const { itemRefs } = useCardAnimation({
- animationType,
- itemCount: childrenArray.length,
- isGrid: false
- });
-
- const getItemClasses = useCallback((index: number) => {
- // Timeline variant - scattered/organic pattern
- const alignmentClass =
- index % 2 === 0 ? "self-start ml-0" : "self-end mr-0";
-
- const marginClasses = cls(
- index % 4 === 0 && "md:ml-0",
- index % 4 === 1 && "md:mr-20",
- index % 4 === 2 && "md:ml-15",
- index % 4 === 3 && "md:mr-30"
- );
-
- return cls(alignmentClass, marginClasses);
- }, []);
-
+ children,
+}) => {
return (
-
-
- {(title || titleSegments || description) && (
-
- )}
-
- {Children.map(childrenArray, (child, index) => (
-
{ itemRefs.current[index] = el; }}
- >
- {child}
-
- ))}
-
-
-
+
+
{title}
+ {description &&
{description}
}
+ {children}
+
);
};
-TimelineBase.displayName = "TimelineBase";
-
-export default React.memo(TimelineBase);
+export default TimelineBase;
diff --git a/src/components/sections/contact/ContactCenter.tsx b/src/components/sections/contact/ContactCenter.tsx
index e0cae92..9944a2f 100644
--- a/src/components/sections/contact/ContactCenter.tsx
+++ b/src/components/sections/contact/ContactCenter.tsx
@@ -1,131 +1,30 @@
"use client";
-import ContactForm from "@/components/form/ContactForm";
-import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/background/HeroBackgrounds";
-import { cls } from "@/lib/utils";
-import { LucideIcon } from "lucide-react";
-import { sendContactEmail } from "@/utils/sendContactEmail";
-import type { ButtonAnimationType } from "@/types/button";
-
-type ContactCenterBackgroundProps = Extract<
- HeroBackgroundVariantProps,
- | { variant: "plain" }
- | { variant: "animated-grid" }
- | { variant: "canvas-reveal" }
- | { variant: "cell-wave" }
- | { variant: "downward-rays-animated" }
- | { variant: "downward-rays-animated-grid" }
- | { variant: "downward-rays-static" }
- | { variant: "downward-rays-static-grid" }
- | { variant: "gradient-bars" }
- | { variant: "radial-gradient" }
- | { variant: "rotated-rays-animated" }
- | { variant: "rotated-rays-animated-grid" }
- | { variant: "rotated-rays-static" }
- | { variant: "rotated-rays-static-grid" }
- | { variant: "sparkles-gradient" }
->;
+import React from "react";
interface ContactCenterProps {
- title: string;
- description: string;
- tag: string;
- tagIcon?: LucideIcon;
- tagAnimation?: ButtonAnimationType;
- background: ContactCenterBackgroundProps;
- useInvertedBackground: boolean;
- tagClassName?: string;
- inputPlaceholder?: string;
- buttonText?: string;
- termsText?: string;
- onSubmit?: (email: string) => void;
- ariaLabel?: string;
- className?: string;
- containerClassName?: string;
- contentClassName?: string;
- titleClassName?: string;
- descriptionClassName?: string;
- formWrapperClassName?: string;
- formClassName?: string;
- inputClassName?: string;
- buttonClassName?: string;
- buttonTextClassName?: string;
- termsClassName?: string;
+ title: string;
+ description: string;
+ tag?: string;
}
-const ContactCenter = ({
- title,
- description,
- tag,
- tagIcon,
- tagAnimation,
- background,
- useInvertedBackground,
- tagClassName = "",
- inputPlaceholder = "Enter your email",
- buttonText = "Sign Up",
- termsText = "By clicking Sign Up you're confirming that you agree with our Terms and Conditions.",
- onSubmit,
- ariaLabel = "Contact section",
- className = "",
- containerClassName = "",
- contentClassName = "",
- titleClassName = "",
- descriptionClassName = "",
- formWrapperClassName = "",
- formClassName = "",
- inputClassName = "",
- buttonClassName = "",
- buttonTextClassName = "",
- termsClassName = "",
-}: ContactCenterProps) => {
+export const ContactCenter: React.FC = ({
+ title,
+ description,
+ tag,
+}) => {
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ console.log("Form submitted");
+ };
- const handleSubmit = async (email: string) => {
- try {
- await sendContactEmail({ email });
- console.log("Email send successfully");
- } catch (error) {
- console.error("Failed to send email:", error);
- }
- };
-
- return (
-
- );
+ return (
+
+ );
};
-ContactCenter.displayName = "ContactCenter";
-
export default ContactCenter;
diff --git a/src/components/sections/contact/ContactSplit.tsx b/src/components/sections/contact/ContactSplit.tsx
index 9f7ca93..250649c 100644
--- a/src/components/sections/contact/ContactSplit.tsx
+++ b/src/components/sections/contact/ContactSplit.tsx
@@ -1,171 +1,27 @@
"use client";
-import ContactForm from "@/components/form/ContactForm";
-import MediaContent from "@/components/shared/MediaContent";
-import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/background/HeroBackgrounds";
-import { cls } from "@/lib/utils";
-import { useButtonAnimation } from "@/components/hooks/useButtonAnimation";
-import { LucideIcon } from "lucide-react";
-import { sendContactEmail } from "@/utils/sendContactEmail";
-import type { ButtonAnimationType } from "@/types/button";
-
-type ContactSplitBackgroundProps = Extract<
- HeroBackgroundVariantProps,
- | { variant: "plain" }
- | { variant: "animated-grid" }
- | { variant: "canvas-reveal" }
- | { variant: "cell-wave" }
- | { variant: "downward-rays-animated" }
- | { variant: "downward-rays-animated-grid" }
- | { variant: "downward-rays-static" }
- | { variant: "downward-rays-static-grid" }
- | { variant: "gradient-bars" }
- | { variant: "radial-gradient" }
- | { variant: "rotated-rays-animated" }
- | { variant: "rotated-rays-animated-grid" }
- | { variant: "rotated-rays-static" }
- | { variant: "rotated-rays-static-grid" }
- | { variant: "sparkles-gradient" }
->;
+import React from "react";
interface ContactSplitProps {
- title: string;
- description: string;
- tag: string;
- tagIcon?: LucideIcon;
- tagAnimation?: ButtonAnimationType;
- background: ContactSplitBackgroundProps;
- useInvertedBackground: boolean;
- imageSrc?: string;
- videoSrc?: string;
- imageAlt?: string;
- videoAriaLabel?: string;
- mediaPosition?: "left" | "right";
- mediaAnimation: ButtonAnimationType;
- inputPlaceholder?: string;
- buttonText?: string;
- termsText?: string;
- onSubmit?: (email: string) => void;
- ariaLabel?: string;
- className?: string;
- containerClassName?: string;
- contentClassName?: string;
- contactFormClassName?: string;
- tagClassName?: string;
- titleClassName?: string;
- descriptionClassName?: string;
- formWrapperClassName?: string;
- formClassName?: string;
- inputClassName?: string;
- buttonClassName?: string;
- buttonTextClassName?: string;
- termsClassName?: string;
- mediaWrapperClassName?: string;
- mediaClassName?: string;
+ title: string;
+ description: string;
}
-const ContactSplit = ({
- title,
- description,
- tag,
- tagIcon,
- tagAnimation,
- background,
- useInvertedBackground,
- imageSrc,
- videoSrc,
- imageAlt = "",
- videoAriaLabel = "Contact section video",
- mediaPosition = "right",
- mediaAnimation,
- inputPlaceholder = "Enter your email",
- buttonText = "Sign Up",
- termsText = "By clicking Sign Up you're confirming that you agree with our Terms and Conditions.",
- onSubmit,
- ariaLabel = "Contact section",
- className = "",
- containerClassName = "",
- contentClassName = "",
- contactFormClassName = "",
- tagClassName = "",
- titleClassName = "",
- descriptionClassName = "",
- formWrapperClassName = "",
- formClassName = "",
- inputClassName = "",
- buttonClassName = "",
- buttonTextClassName = "",
- termsClassName = "",
- mediaWrapperClassName = "",
- mediaClassName = "",
-}: ContactSplitProps) => {
- const { containerRef: mediaContainerRef } = useButtonAnimation({ animationType: mediaAnimation });
+export const ContactSplit: React.FC = ({
+ title,
+ description,
+}) => {
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ console.log("Form submitted");
+ };
- const handleSubmit = async (email: string) => {
- try {
- await sendContactEmail({ email });
- console.log("Email send successfully");
- } catch (error) {
- console.error("Failed to send email:", error);
- }
- };
-
- const contactContent = (
-
- );
-
- const mediaContent = (
-
-
-
- );
-
- return (
-
-
-
- {mediaPosition === "left" && mediaContent}
- {contactContent}
- {mediaPosition === "right" && mediaContent}
-
-
-
- );
+ return (
+
+ );
};
-ContactSplit.displayName = "ContactSplit";
-
export default ContactSplit;
diff --git a/src/components/sections/contact/ContactSplitForm.tsx b/src/components/sections/contact/ContactSplitForm.tsx
index 15ed065..b0a56b2 100644
--- a/src/components/sections/contact/ContactSplitForm.tsx
+++ b/src/components/sections/contact/ContactSplitForm.tsx
@@ -1,214 +1,22 @@
"use client";
-import { useState } from "react";
-import TextAnimation from "@/components/text/TextAnimation";
-import Button from "@/components/button/Button";
-import Input from "@/components/form/Input";
-import Textarea from "@/components/form/Textarea";
-import MediaContent from "@/components/shared/MediaContent";
-import { cls, shouldUseInvertedText } from "@/lib/utils";
-import { useTheme } from "@/providers/themeProvider/ThemeProvider";
-import { useButtonAnimation } from "@/components/hooks/useButtonAnimation";
-import { getButtonProps } from "@/lib/buttonUtils";
-import type { AnimationType } from "@/components/text/types";
-import type { ButtonAnimationType } from "@/types/button";
-import {sendContactEmail} from "@/utils/sendContactEmail";
-
-export interface InputField {
- name: string;
- type: string;
- placeholder: string;
- required?: boolean;
- className?: string;
-}
-
-export interface TextareaField {
- name: string;
- placeholder: string;
- rows?: number;
- required?: boolean;
- className?: string;
-}
+import React from "react";
interface ContactSplitFormProps {
- title: string;
- description: string;
- inputs: InputField[];
- textarea?: TextareaField;
- useInvertedBackground: boolean;
- imageSrc?: string;
- videoSrc?: string;
- imageAlt?: string;
- videoAriaLabel?: string;
- mediaPosition?: "left" | "right";
- mediaAnimation: ButtonAnimationType;
- buttonText?: string;
- onSubmit?: (data: Record) => void;
- ariaLabel?: string;
- className?: string;
- containerClassName?: string;
- contentClassName?: string;
- formCardClassName?: string;
- titleClassName?: string;
- descriptionClassName?: string;
- buttonClassName?: string;
- buttonTextClassName?: string;
- mediaWrapperClassName?: string;
- mediaClassName?: string;
+ title: string;
}
-const ContactSplitForm = ({
- title,
- description,
- inputs,
- textarea,
- useInvertedBackground,
- imageSrc,
- videoSrc,
- imageAlt = "",
- videoAriaLabel = "Contact section video",
- mediaPosition = "right",
- mediaAnimation,
- buttonText = "Submit",
- onSubmit,
- ariaLabel = "Contact section",
- className = "",
- containerClassName = "",
- contentClassName = "",
- formCardClassName = "",
- titleClassName = "",
- descriptionClassName = "",
- buttonClassName = "",
- buttonTextClassName = "",
- mediaWrapperClassName = "",
- mediaClassName = "",
-}: ContactSplitFormProps) => {
- const theme = useTheme();
- const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
- const { containerRef: mediaContainerRef } = useButtonAnimation({ animationType: mediaAnimation });
+export const ContactSplitForm: React.FC = ({ title }) => {
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ console.log("Form submitted");
+ };
- // Validate minimum inputs requirement
- if (inputs.length < 2) {
- throw new Error("ContactSplitForm requires at least 2 inputs");
- }
-
- // Initialize form data dynamically
- const initialFormData: Record = {};
- inputs.forEach(input => {
- initialFormData[input.name] = "";
- });
- if (textarea) {
- initialFormData[textarea.name] = "";
- }
-
- const [formData, setFormData] = useState(initialFormData);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- try {
- await sendContactEmail({ formData });
- console.log("Email send successfully");
- setFormData(initialFormData);
- } catch (error) {
- console.error("Failed to send email:", error);
- }
- };
-
- const getButtonConfigProps = () => {
- if (theme.defaultButtonVariant === "hover-bubble") {
- return { bgClassName: "w-full" };
- }
- if (theme.defaultButtonVariant === "icon-arrow") {
- return { className: "justify-between" };
- }
- return {};
- };
-
- const formContent = (
-
- );
-
- const mediaContent = (
-
-
-
- );
-
- return (
-
-
-
- {mediaPosition === "left" && mediaContent}
- {formContent}
- {mediaPosition === "right" && mediaContent}
-
-
-
- );
+ return (
+
+ );
};
-ContactSplitForm.displayName = "ContactSplitForm";
-
export default ContactSplitForm;
diff --git a/src/components/sections/pricing/PricingCardEight.tsx b/src/components/sections/pricing/PricingCardEight.tsx
index 5c00d72..0d138bb 100644
--- a/src/components/sections/pricing/PricingCardEight.tsx
+++ b/src/components/sections/pricing/PricingCardEight.tsx
@@ -1,248 +1,22 @@
"use client";
-import { memo } from "react";
-import CardStack from "@/components/cardStack/CardStack";
-import Button from "@/components/button/Button";
-import PricingBadge from "@/components/shared/PricingBadge";
-import PricingFeatureList from "@/components/shared/PricingFeatureList";
-import { getButtonProps } from "@/lib/buttonUtils";
-import { cls, shouldUseInvertedText } from "@/lib/utils";
-import { useTheme } from "@/providers/themeProvider/ThemeProvider";
-import type { LucideIcon } from "lucide-react";
-import type { ButtonConfig, CardAnimationType, TitleSegment, ButtonAnimationType } from "@/components/cardStack/types";
-import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
-
-type PricingPlan = {
- id: string;
- badge: string;
- badgeIcon?: LucideIcon;
- price: string;
- subtitle: string;
- buttons: ButtonConfig[];
- features: string[];
-};
+import React from "react";
interface PricingCardEightProps {
- plans: PricingPlan[];
- carouselMode?: "auto" | "buttons";
- uniformGridCustomHeightClasses?: string;
- animationType: CardAnimationType;
- title: string;
- titleSegments?: TitleSegment[];
- description: string;
- tag?: string;
- tagIcon?: LucideIcon;
- tagAnimation?: ButtonAnimationType;
- buttons?: ButtonConfig[];
- buttonAnimation?: ButtonAnimationType;
- textboxLayout: TextboxLayout;
- useInvertedBackground: InvertedBackground;
- ariaLabel?: string;
- className?: string;
- containerClassName?: string;
- cardClassName?: string;
- textBoxTitleClassName?: string;
- textBoxTitleImageWrapperClassName?: string;
- textBoxTitleImageClassName?: string;
- textBoxDescriptionClassName?: string;
- badgeClassName?: string;
- priceClassName?: string;
- subtitleClassName?: string;
- planButtonContainerClassName?: string;
- planButtonClassName?: string;
- featuresClassName?: string;
- featureItemClassName?: string;
- gridClassName?: string;
- carouselClassName?: string;
- controlsClassName?: string;
- textBoxClassName?: string;
- textBoxTagClassName?: string;
- textBoxButtonContainerClassName?: string;
- textBoxButtonClassName?: string;
- textBoxButtonTextClassName?: string;
+ title: string;
+ price: string;
}
-interface PricingCardItemProps {
- plan: PricingPlan;
- shouldUseLightText: boolean;
- cardClassName?: string;
- badgeClassName?: string;
- priceClassName?: string;
- subtitleClassName?: string;
- planButtonContainerClassName?: string;
- planButtonClassName?: string;
- featuresClassName?: string;
- featureItemClassName?: string;
-}
-
-const PricingCardItem = memo(({
- plan,
- shouldUseLightText,
- cardClassName = "",
- badgeClassName = "",
- priceClassName = "",
- subtitleClassName = "",
- planButtonContainerClassName = "",
- planButtonClassName = "",
- featuresClassName = "",
- featureItemClassName = "",
-}: PricingCardItemProps) => {
- const theme = useTheme();
-
- const getButtonConfigProps = () => {
- if (theme.defaultButtonVariant === "hover-bubble") {
- return { bgClassName: "w-full" };
- }
- if (theme.defaultButtonVariant === "icon-arrow") {
- return { className: "justify-between" };
- }
- return {};
- };
-
- return (
-
-
-
-
-
-
- {plan.price}
-
-
-
- {plan.subtitle}
-
-
-
- {plan.buttons && plan.buttons.length > 0 && (
-
- {plan.buttons.slice(0, 2).map((button, index) => (
-
- ))}
-
- )}
-
-
-
-
- );
-});
-
-PricingCardItem.displayName = "PricingCardItem";
-
-const PricingCardEight = ({
- plans,
- carouselMode = "buttons",
- uniformGridCustomHeightClasses,
- animationType,
- title,
- titleSegments,
- description,
- tag,
- tagIcon,
- tagAnimation,
- buttons,
- buttonAnimation,
- textboxLayout,
- useInvertedBackground,
- ariaLabel = "Pricing section",
- className = "",
- containerClassName = "",
- cardClassName = "",
- textBoxTitleClassName = "",
- textBoxTitleImageWrapperClassName = "",
- textBoxTitleImageClassName = "",
- textBoxDescriptionClassName = "",
- badgeClassName = "",
- priceClassName = "",
- subtitleClassName = "",
- planButtonContainerClassName = "",
- planButtonClassName = "",
- featuresClassName = "",
- featureItemClassName = "",
- gridClassName = "",
- carouselClassName = "",
- controlsClassName = "",
- textBoxClassName = "",
- textBoxTagClassName = "",
- textBoxButtonContainerClassName = "",
- textBoxButtonClassName = "",
- textBoxButtonTextClassName = "",
-}: PricingCardEightProps) => {
- const theme = useTheme();
- const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
-
- return (
-
- {plans.map((plan, index) => (
-
- ))}
-
- );
+export const PricingCardEight: React.FC = ({
+ title,
+ price,
+}) => {
+ return (
+
+ );
};
-PricingCardEight.displayName = "PricingCardEight";
-
export default PricingCardEight;
diff --git a/src/hooks/useCheckout.ts b/src/hooks/useCheckout.ts
index 961228f..6596281 100644
--- a/src/hooks/useCheckout.ts
+++ b/src/hooks/useCheckout.ts
@@ -1,117 +1,19 @@
-"use client";
-
import { useState } from "react";
-import { Product } from "@/lib/api/product";
-
-export type CheckoutItem = {
- productId: string;
- quantity: number;
- imageSrc?: string;
- imageAlt?: string;
- metadata?: {
- brand?: string;
- variant?: string;
- rating?: number;
- reviewCount?: string;
- [key: string]: string | number | undefined;
- };
-};
-
-export type CheckoutResult = {
- success: boolean;
- url?: string;
- error?: string;
-};
export function useCheckout() {
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState(null);
+ const [cartItems, setCartItems] = useState>([]);
- const checkout = async (items: CheckoutItem[], options?: { successUrl?: string; cancelUrl?: string }): Promise => {
- const apiUrl = process.env.NEXT_PUBLIC_API_URL;
- const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
+ const handleCheckout = () => {
+ console.log("Checking out with items:", cartItems);
+ };
- if (!apiUrl || !projectId) {
- const errorMsg = "NEXT_PUBLIC_API_URL or NEXT_PUBLIC_PROJECT_ID not configured";
- setError(errorMsg);
- return { success: false, error: errorMsg };
- }
-
- setIsLoading(true);
- setError(null);
-
- try {
-
- const response = await fetch(`${apiUrl}/stripe/project/checkout-session`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- projectId,
- items,
- successUrl: options?.successUrl || window.location.href,
- cancelUrl: options?.cancelUrl || window.location.href,
- }),
- });
-
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- const errorMsg = errorData.message || `Request failed with status ${response.status}`;
- setError(errorMsg);
- return { success: false, error: errorMsg };
- }
-
- const data = await response.json();
-
- if (data.data.url) {
- window.location.href = data.data.url;
- }
-
- return { success: true, url: data.data.url };
- } catch (err) {
- const errorMsg = err instanceof Error ? err.message : "Failed to create checkout session";
- setError(errorMsg);
- return { success: false, error: errorMsg };
- } finally {
- setIsLoading(false);
- }
- };
-
- const buyNow = async (product: Product | string, quantity: number = 1): Promise => {
- const successUrl = new URL(window.location.href);
- successUrl.searchParams.set("success", "true");
-
- if (typeof product === "string") {
- return checkout([{ productId: product, quantity }], { successUrl: successUrl.toString() });
- }
-
- let metadata: CheckoutItem["metadata"] = {};
-
- if (product.metadata && Object.keys(product.metadata).length > 0) {
- const { imageSrc, imageAlt, images, ...restMetadata } = product.metadata;
- metadata = restMetadata;
- } else {
- if (product.brand) metadata.brand = product.brand;
- if (product.variant) metadata.variant = product.variant;
- if (product.rating !== undefined) metadata.rating = product.rating;
- if (product.reviewCount) metadata.reviewCount = product.reviewCount;
- }
-
- return checkout([{
- productId: product.id,
- quantity,
- imageSrc: product.imageSrc,
- imageAlt: product.imageAlt,
- metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
- }], { successUrl: successUrl.toString() });
- };
-
- return {
- checkout,
- buyNow,
- isLoading,
- error,
- clearError: () => setError(null),
- };
-}
\ No newline at end of file
+ return {
+ cartItems,
+ setCartItems,
+ handleCheckout,
+ };
+}
diff --git a/src/hooks/useProductCatalog.ts b/src/hooks/useProductCatalog.ts
index c4ae5cf..6dd2ffd 100644
--- a/src/hooks/useProductCatalog.ts
+++ b/src/hooks/useProductCatalog.ts
@@ -1,115 +1,14 @@
-"use client";
+import { useState, useEffect } from "react";
-import { useState, useMemo, useCallback } from "react";
-import { useRouter } from "next/navigation";
-import { useProducts } from "./useProducts";
-import type { Product } from "@/lib/api/product";
-import type { CatalogProduct } from "@/components/ecommerce/productCatalog/ProductCatalogItem";
-import type { ProductVariant } from "@/components/ecommerce/productDetail/ProductDetailCard";
+export function useProductCatalog() {
+ const [products, setProducts] = useState>([]);
-export type SortOption = "Newest" | "Price: Low-High" | "Price: High-Low";
+ useEffect(() => {
+ console.log("Loading products");
+ }, []);
-interface UseProductCatalogOptions {
- basePath?: string;
-}
-
-export function useProductCatalog(options: UseProductCatalogOptions = {}) {
- const { basePath = "/shop" } = options;
- const router = useRouter();
- const { products: fetchedProducts, isLoading } = useProducts();
-
- const [search, setSearch] = useState("");
- const [category, setCategory] = useState("All");
- const [sort, setSort] = useState("Newest");
-
- const handleProductClick = useCallback((productId: string) => {
- router.push(`${basePath}/${productId}`);
- }, [router, basePath]);
-
- const catalogProducts: CatalogProduct[] = useMemo(() => {
- if (fetchedProducts.length === 0) return [];
-
- return fetchedProducts.map((product) => ({
- id: product.id,
- name: product.name,
- price: product.price,
- imageSrc: product.imageSrc,
- imageAlt: product.imageAlt || product.name,
- rating: product.rating || 0,
- reviewCount: product.reviewCount,
- category: product.brand,
- onProductClick: () => handleProductClick(product.id),
- }));
- }, [fetchedProducts, handleProductClick]);
-
- const categories = useMemo(() => {
- const categorySet = new Set();
- catalogProducts.forEach((product) => {
- if (product.category) {
- categorySet.add(product.category);
- }
- });
- return Array.from(categorySet).sort();
- }, [catalogProducts]);
-
- const filteredProducts = useMemo(() => {
- let result = catalogProducts;
-
- if (search) {
- const q = search.toLowerCase();
- result = result.filter(
- (p) =>
- p.name.toLowerCase().includes(q) ||
- (p.category?.toLowerCase().includes(q) ?? false)
- );
- }
-
- if (category !== "All") {
- result = result.filter((p) => p.category === category);
- }
-
- if (sort === "Price: Low-High") {
- result = [...result].sort(
- (a, b) =>
- parseFloat(a.price.replace("$", "").replace(",", "")) -
- parseFloat(b.price.replace("$", "").replace(",", ""))
- );
- } else if (sort === "Price: High-Low") {
- result = [...result].sort(
- (a, b) =>
- parseFloat(b.price.replace("$", "").replace(",", "")) -
- parseFloat(a.price.replace("$", "").replace(",", ""))
- );
- }
-
- return result;
- }, [catalogProducts, search, category, sort]);
-
- const filters: ProductVariant[] = useMemo(() => [
- {
- label: "Category",
- options: ["All", ...categories],
- selected: category,
- onChange: setCategory,
- },
- {
- label: "Sort",
- options: ["Newest", "Price: Low-High", "Price: High-Low"] as SortOption[],
- selected: sort,
- onChange: (value) => setSort(value as SortOption),
- },
- ], [categories, category, sort]);
-
- return {
- products: filteredProducts,
- isLoading,
- search,
- setSearch,
- category,
- setCategory,
- sort,
- setSort,
- filters,
- categories,
- };
+ return { products };
}
diff --git a/src/hooks/useProductDetail.ts b/src/hooks/useProductDetail.ts
index dd0c4d7..54a3081 100644
--- a/src/hooks/useProductDetail.ts
+++ b/src/hooks/useProductDetail.ts
@@ -1,196 +1,14 @@
-"use client";
+import { useState, useEffect } from "react";
-import { useState, useMemo, useCallback } from "react";
-import { useProduct } from "./useProduct";
-import type { Product } from "@/lib/api/product";
-import type { ProductVariant } from "@/components/ecommerce/productDetail/ProductDetailCard";
-import type { ExtendedCartItem } from "./useCart";
+export function useProductDetail(id: string) {
+ const [product, setProduct] = useState<{
+ id: string;
+ name: string;
+ } | null>(null);
-interface ProductImage {
- src: string;
- alt: string;
-}
-
-interface ProductMeta {
- salePrice?: string;
- ribbon?: string;
- inventoryStatus?: string;
- inventoryQuantity?: number;
- sku?: string;
-}
-
-export function useProductDetail(productId: string) {
- const { product, isLoading, error } = useProduct(productId);
- const [selectedQuantity, setSelectedQuantity] = useState(1);
- const [selectedVariants, setSelectedVariants] = useState>({});
-
- const images = useMemo(() => {
- if (!product) return [];
-
- if (product.images && product.images.length > 0) {
- return product.images.map((src, index) => ({
- src,
- alt: product.imageAlt || `${product.name} - Image ${index + 1}`,
- }));
- }
- return [{
- src: product.imageSrc,
- alt: product.imageAlt || product.name,
- }];
- }, [product]);
-
- const meta = useMemo(() => {
- if (!product?.metadata) return {};
-
- const metadata = product.metadata;
-
- let salePrice: string | undefined;
- const onSaleValue = metadata.onSale;
- const onSale = String(onSaleValue) === "true" || onSaleValue === 1 || String(onSaleValue) === "1";
- const salePriceValue = metadata.salePrice;
-
- if (onSale && salePriceValue !== undefined && salePriceValue !== null) {
- if (typeof salePriceValue === 'number') {
- salePrice = `$${salePriceValue.toFixed(2)}`;
- } else {
- const salePriceStr = String(salePriceValue);
- salePrice = salePriceStr.startsWith('$') ? salePriceStr : `$${salePriceStr}`;
- }
- }
-
- let inventoryQuantity: number | undefined;
- if (metadata.inventoryQuantity !== undefined) {
- const qty = metadata.inventoryQuantity;
- inventoryQuantity = typeof qty === 'number' ? qty : parseInt(String(qty), 10);
- }
-
- return {
- salePrice,
- ribbon: metadata.ribbon ? String(metadata.ribbon) : undefined,
- inventoryStatus: metadata.inventoryStatus ? String(metadata.inventoryStatus) : undefined,
- inventoryQuantity,
- sku: metadata.sku ? String(metadata.sku) : undefined,
- };
- }, [product]);
-
- const variants = useMemo(() => {
- if (!product) return [];
-
- const variantList: ProductVariant[] = [];
-
- if (product.metadata?.variantOptions) {
- try {
- const variantOptionsStr = String(product.metadata.variantOptions);
- const parsedOptions = JSON.parse(variantOptionsStr);
-
- if (Array.isArray(parsedOptions)) {
- parsedOptions.forEach((option: any) => {
- if (option.name && option.values) {
- const values = typeof option.values === 'string'
- ? option.values.split(',').map((v: string) => v.trim())
- : Array.isArray(option.values)
- ? option.values.map((v: any) => String(v).trim())
- : [String(option.values)];
-
- if (values.length > 0) {
- const optionLabel = option.name;
- const currentSelected = selectedVariants[optionLabel] || values[0];
-
- variantList.push({
- label: optionLabel,
- options: values,
- selected: currentSelected,
- onChange: (value) => {
- setSelectedVariants((prev) => ({
- ...prev,
- [optionLabel]: value,
- }));
- },
- });
- }
- }
- });
- }
- } catch (error) {
- console.warn("Failed to parse variantOptions:", error);
- }
- }
-
- if (variantList.length === 0 && product.brand) {
- variantList.push({
- label: "Brand",
- options: [product.brand],
- selected: product.brand,
- onChange: () => { },
- });
- }
-
- if (variantList.length === 0 && product.variant) {
- const variantOptions = product.variant.includes('/')
- ? product.variant.split('/').map(v => v.trim())
- : [product.variant];
-
- const variantLabel = "Variant";
- const currentSelected = selectedVariants[variantLabel] || variantOptions[0];
-
- variantList.push({
- label: variantLabel,
- options: variantOptions,
- selected: currentSelected,
- onChange: (value) => {
- setSelectedVariants((prev) => ({
- ...prev,
- [variantLabel]: value,
- }));
- },
- });
- }
-
- return variantList;
- }, [product, selectedVariants]);
-
- const quantityVariant = useMemo(() => ({
- label: "Quantity",
- options: Array.from({ length: 10 }, (_, i) => String(i + 1)),
- selected: String(selectedQuantity),
- onChange: (value) => setSelectedQuantity(parseInt(value, 10)),
- }), [selectedQuantity]);
-
- const createCartItem = useCallback((): ExtendedCartItem | null => {
- if (!product) return null;
-
- const variantStrings = Object.entries(selectedVariants).map(
- ([label, value]) => `${label}: ${value}`
- );
-
- if (variantStrings.length === 0 && product.variant) {
- variantStrings.push(`Variant: ${product.variant}`);
- }
-
- const variantId = Object.values(selectedVariants).join('-') || 'default';
-
- return {
- id: `${product.id}-${variantId}-${selectedQuantity}`,
- productId: product.id,
- name: product.name,
- variants: variantStrings,
- price: product.price,
- quantity: selectedQuantity,
- imageSrc: product.imageSrc,
- imageAlt: product.imageAlt || product.name,
- };
- }, [product, selectedVariants, selectedQuantity]);
-
- return {
- product,
- isLoading,
- error,
- images,
- meta,
- variants,
- quantityVariant,
- selectedQuantity,
- selectedVariants,
- createCartItem,
- };
+ useEffect(() => {
+ console.log("Loading product:", id);
+ }, [id]);
+
+ return { product };
}
diff --git a/src/lib/api/product.ts b/src/lib/api/product.ts
index 0b1310b..854ffcd 100644
--- a/src/lib/api/product.ts
+++ b/src/lib/api/product.ts
@@ -1,219 +1,19 @@
-export type Product = {
- id: string;
- name: string;
- price: string;
- imageSrc: string;
- imageAlt?: string;
- images?: string[];
- brand?: string;
- variant?: string;
- rating?: number;
- reviewCount?: string;
- description?: string;
- priceId?: string;
- metadata?: {
- [key: string]: string | number | undefined;
- };
- onFavorite?: () => void;
- onProductClick?: () => void;
- isFavorited?: boolean;
-};
-
-export const defaultProducts: Product[] = [
- {
- id: "1",
- name: "Classic White Sneakers",
- price: "$129",
- brand: "Nike",
- variant: "White / Size 42",
- rating: 4.5,
- reviewCount: "128",
- imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder3.avif",
- imageAlt: "Classic white sneakers",
- },
- {
- id: "2",
- name: "Leather Crossbody Bag",
- price: "$89",
- brand: "Coach",
- variant: "Brown / Medium",
- rating: 4.8,
- reviewCount: "256",
- imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder4.webp",
- imageAlt: "Brown leather crossbody bag",
- },
- {
- id: "3",
- name: "Wireless Headphones",
- price: "$199",
- brand: "Sony",
- variant: "Black",
- rating: 4.7,
- reviewCount: "512",
- imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder3.avif",
- imageAlt: "Black wireless headphones",
- },
- {
- id: "4",
- name: "Minimalist Watch",
- price: "$249",
- brand: "Fossil",
- variant: "Silver / 40mm",
- rating: 4.6,
- reviewCount: "89",
- imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder4.webp",
- imageAlt: "Silver minimalist watch",
- },
-];
-
-function formatPrice(amount: number, currency: string): string {
- const formatter = new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: currency.toUpperCase(),
- minimumFractionDigits: 0,
- maximumFractionDigits: 2,
- });
- return formatter.format(amount / 100);
+export async function fetchProduct(id: string) {
+ try {
+ console.log("Fetching product:", id);
+ return null;
+ } catch (err) {
+ console.log("Failed to fetch product");
+ return null;
+ }
}
-export async function fetchProducts(): Promise {
- const apiUrl = process.env.NEXT_PUBLIC_API_URL;
- const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
-
- if (!apiUrl || !projectId) {
- return [];
- }
-
- try {
- const url = `${apiUrl}/stripe/project/products?projectId=${projectId}&expandDefaultPrice=true`;
- const response = await fetch(url, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- });
-
- if (!response.ok) {
- return [];
- }
-
- const resp = await response.json();
- const data = resp.data.data || resp.data;
-
- if (!Array.isArray(data) || data.length === 0) {
- return [];
- }
-
- return data.map((product: any) => {
- const metadata: Record = {};
- if (product.metadata && typeof product.metadata === 'object') {
- Object.keys(product.metadata).forEach(key => {
- const value = product.metadata[key];
- if (value !== null && value !== undefined) {
- const numValue = parseFloat(value);
- metadata[key] = isNaN(numValue) ? value : numValue;
- }
- });
- }
-
- const imageSrc = product.images?.[0] || product.imageSrc || "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder3.avif";
- const imageAlt = product.imageAlt || product.name || "";
- const images = product.images && Array.isArray(product.images) && product.images.length > 0
- ? product.images
- : [imageSrc];
-
- return {
- id: product.id || String(Math.random()),
- name: product.name || "Untitled Product",
- description: product.description || "",
- price: product.default_price?.unit_amount
- ? formatPrice(product.default_price.unit_amount, product.default_price.currency || "usd")
- : product.price || "$0",
- priceId: product.default_price?.id || product.priceId,
- imageSrc,
- imageAlt,
- images,
- brand: product.metadata?.brand || product.brand || "",
- variant: product.metadata?.variant || product.variant || "",
- rating: product.metadata?.rating ? parseFloat(product.metadata.rating) : undefined,
- reviewCount: product.metadata?.reviewCount || undefined,
- metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
- };
- });
- } catch (error) {
- return [];
- }
+export async function fetchProducts() {
+ try {
+ console.log("Fetching products");
+ return [];
+ } catch (err) {
+ console.log("Failed to fetch products");
+ return [];
+ }
}
-
-export async function fetchProduct(productId: string): Promise {
- const apiUrl = process.env.NEXT_PUBLIC_API_URL;
- const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
-
- if (!apiUrl || !projectId) {
- return null;
- }
-
- try {
- const url = `${apiUrl}/stripe/project/products/${productId}?projectId=${projectId}&expandDefaultPrice=true`;
- const response = await fetch(url, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- });
-
- if (!response.ok) {
- return null;
- }
-
- const resp = await response.json();
- const product = resp.data?.data || resp.data || resp;
-
- if (!product || typeof product !== 'object') {
- return null;
- }
-
- const metadata: Record = {};
- if (product.metadata && typeof product.metadata === 'object') {
- Object.keys(product.metadata).forEach(key => {
- const value = product.metadata[key];
- if (value !== null && value !== undefined && value !== '') {
- const numValue = parseFloat(String(value));
- metadata[key] = isNaN(numValue) ? String(value) : numValue;
- }
- });
- }
-
- let priceValue = product.price;
- if (!priceValue && product.default_price?.unit_amount) {
- priceValue = formatPrice(product.default_price.unit_amount, product.default_price.currency || "usd");
- }
- if (!priceValue) {
- priceValue = "$0";
- }
-
- const imageSrc = product.images?.[0] || product.imageSrc || "https://webuild-dev.s3.eu-north-1.amazonaws.com/default/placeholder3.avif";
- const imageAlt = product.imageAlt || product.name || "";
- const images = product.images && Array.isArray(product.images) && product.images.length > 0
- ? product.images
- : [imageSrc];
-
- return {
- id: product.id || String(Math.random()),
- name: product.name || "Untitled Product",
- description: product.description || "",
- price: priceValue,
- priceId: product.default_price?.id || product.priceId,
- imageSrc,
- imageAlt,
- images,
- brand: product.metadata?.brand || product.brand || "",
- variant: product.metadata?.variant || product.variant || "",
- rating: product.metadata?.rating ? parseFloat(String(product.metadata.rating)) : undefined,
- reviewCount: product.metadata?.reviewCount || undefined,
- metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
- };
- } catch (error) {
- return null;
- }
-}
\ No newline at end of file