Although i transferred props properly, i keep getting “is not function” error. I’ve worked a lot on that but i couldn’t find a solution. When I try to move(I do necessary implementings) handleComplete function to ProductAddTemplate.tsx, this time i get setIsSubmitting() is not function error.
AddProduct.tsx:
"use client";
import React, { useState } from "react";
import PageBreadcrumb from "@/components/common/PageBreadCrumb";
import { ProductAddTemplate } from "../templates/ProductAddTemplate";
import { Product } from "../types/types";
import { Step } from "../types/types";
import { motion } from "framer-motion";
import { FileSpreadsheet, Edit, CheckCircle, ArrowRight } from "lucide-react";
import { ProductService } from "@/utils/api/services/productService";
import {toast} from "sonner";
export default function AddProduct(): React.ReactElement {
const [currentStep, setCurrentStep] = useState<number>(0);
const [selectedMethod, setSelectedMethod] = useState<"manual" | "excel" | null>(null);
const [productName, setProductName] = useState<string>("");
const [selectedProducts, setSelectedProducts] = useState<Product[]>([]);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
// Adımları tanımlayalım - renkli ve ikonlu
const steps: Step[] = [
{
id: 0,
title: "Yükleme Yöntemi",
icon: <ArrowRight className="h-5 w-5" />,
color: "bg-blue-500"
},
{
id: 1,
title: "Ürün Bilgileri",
icon: <Edit className="h-5 w-5" />,
color: "bg-purple-500"
},
{
id: 2,
title: "Önizleme",
icon: <FileSpreadsheet className="h-5 w-5" />,
color: "bg-amber-500"
},
{
id: 3,
title: "Tamamlandı",
icon: <CheckCircle className="h-5 w-5" />,
color: "bg-green-500"
}
];
// Yükleme yöntemini seçme işlevi
const handleSelectMethod = (method: "manual" | "excel"): void => {
setSelectedMethod(method);
// Animasyon için timeout
setTimeout(() => {
setCurrentStep(1); // Bir sonraki adıma geç
}, 300);
};
// Ürün ekleme
const handleAddProduct = (): void => {
if (productName.trim() === "") return;
// Basit bir ürün oluştur (gerçek uygulamada API'den gelecek)
const newProduct: Product = {
id: Date.now(), // Geçici ID
name: productName
};
// Animasyonlu ekleme
setSelectedProducts(prev => [...prev, newProduct]);
setProductName(""); // Input'u temizle
};
// Ürün kaldırma
const handleRemoveProduct = (productId: number): void => {
setSelectedProducts(selectedProducts.filter(p => p.id !== productId));
};
// Sıfırlama işlevi
const handleReset = (): void => {
setCurrentStep(0);
setSelectedProducts([]);
setProductName("");
setSelectedMethod(null);
};
// Son adıma geçildiğinde ürünleri API'ye gönder ve konfeti efekti
// Ürün ekleme işlemini tamamla
// Ürün ekleme işlemini tamamla
const handleComplete = async (): Promise<void> => {
// Ürün kontrolü
if (selectedMethod === "manual" && selectedProducts.length === 0) {
toast.warning('Lütfen en az bir ürün ekleyin.');
return;
}
// Yükleme durumunu başlat
setIsSubmitting(true);
// Ürün verilerini hazırla
interface ProductData {
productNames: string[];
method: "manual" | "excel" | null;
}
const productData: ProductData = {
productNames: selectedProducts.map((product: Product) => product.name),
method: selectedMethod
};
try {
// API çağrısını yap
let success: boolean = false;
if (selectedMethod === "manual") {
// Manuel ekleme için API çağrısı
interface ApiResponse {
isSuccessful: boolean;
errorMessages?: string[];
data?: any;
}
const response: ApiResponse | undefined = await ProductService.create(productData);
success = Boolean(response && response.isSuccessful);
} else {
// Excel yükleme için farklı bir API çağrısı (örnek)
// const response = await ProductService.uploadExcel(excelFile);
// success = Boolean(response && response.isSuccessful);
// Şimdilik Excel yüklemesini başarılı sayalım
success = true;
}
// Sonuca göre işlem yap
if (success) {
// Başarılı olursa son adıma geç
setCurrentStep(3);
// Konfeti efekti
// Başarı mesajı
const productCount: number | string = selectedMethod === "manual"
? selectedProducts.length
: "Excel'den yüklenen";
toast.success(`${productCount} ürün başarıyla eklendi.`);
} else {
// Hata mesajı
toast.error("Ürünler eklenirken bir hata oluştu. Lütfen tekrar deneyin.");
}
} catch (error: unknown) {
// Hata durumunda
console.error("Ürün ekleme işlemi başarısız:", error);
// Hata mesajını daha detaylı göster
if (error instanceof Error) {
toast.error(`Hata: ${error.message}`);
} else {
toast.error("Bir hata oluştu. Lütfen daha sonra tekrar deneyin.");
}
} finally {
// Her durumda yükleme durumunu sonlandır
setIsSubmitting(false);
}
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="min-h-screen bg-gradient-to-br from-white to-blue-50"
>
<PageBreadcrumb pageTitle="Ürün Ekle" />
<motion.div
className="space-y-4 max-w-5xl mx-auto p-6"
initial={{ scale: 0.95 }}
animate={{ scale: 1 }}
transition={{ duration: 0.3 }}
>
<ProductAddTemplate
currentStep={currentStep}
steps={steps}
selectedMethod={selectedMethod}
productName={productName}
setProductName={setProductName}
selectedProducts={selectedProducts}
handleSelectMethod={handleSelectMethod}
handleAddProduct={handleAddProduct}
handleRemoveProduct={handleRemoveProduct}
setCurrentStep={setCurrentStep}
handleReset={handleReset}
isSubmitting={isSubmitting}
handleComplete={handleComplete}
/>
</motion.div>
</motion.div>
);
}
ProductAddTemplate.tsx:
import React from "react";
import { StepIndicator } from "../molecules/StepIndicator";
import { MethodSelection } from "../organisms/MethodSelection";
import { StepHeader } from "../organisms/StepHeader";
import { ManualProductEntry } from "../organisms/ManualProductEntry";
import { ExcelUpload } from "../organisms/ExcelUpload";
import { ProductPreview } from "../organisms/ProductPreview";
import { CompletionMessage } from "../organisms/CompletionMessage";
import ComponentCard from "@/components/common/ComponentCard";
import { Step } from "../types/types";
import { Product } from "../types/types";
import { useRouter } from 'next/navigation';
import { motion, AnimatePresence } from "framer-motion";
import { ArrowRight, ArrowLeft, CheckCircle } from "lucide-react";
import {toast} from "sonner";
import ProductService from "@/utils/api/services/productService";
interface ProductAddTemplateProps {
currentStep: number;
steps: Step[];
selectedMethod: "manual" | "excel" | null;
productName: string;
setProductName: (name: string) => void;
selectedProducts: Product[];
handleSelectMethod: (method: "manual" | "excel") => void;
handleAddProduct: () => void;
handleRemoveProduct: (productId: number) => void;
setCurrentStep: (step: number) => void;
handleReset: () => void;
isSubmitting?: boolean;
handleComplete: () => Promise<void>;
}
export const ProductAddTemplate: React.FC<ProductAddTemplateProps> = ({
currentStep,
steps,
selectedMethod,
productName,
setProductName,
selectedProducts,
handleSelectMethod,
handleAddProduct,
handleRemoveProduct,
setCurrentStep,
handleReset,
isSubmitting = false,
handleComplete
}) => {
const router = useRouter();
// Adım geçişleri için animasyon varyantları
const variants = {
hidden: { opacity: 0, x: 50 },
visible: { opacity: 1, x: 0 },
exit: { opacity: 0, x: -50 }
};
// Adım başlıklarını ve renklerini belirle
const getStepColor = (stepId: number) => {
const colors = ["bg-blue-500", "bg-purple-500", "bg-amber-500", "bg-green-500"];
return colors[stepId] || colors[0];
};
// Adım 3'e geçiş için işlev - async olarak işaretlendi
const goToFinalStep = async (): Promise<void> => {
console.log("handleComplete type:", typeof handleComplete);
await handleComplete();
};
return (
<ComponentCard
title="Ürün Ekle"
className="border shadow-lg rounded-xl overflow-hidden"
>
{/* Adım göstergesi - renkli ve animasyonlu */}
<div className="mb-8 px-4">
<StepIndicator
steps={steps}
currentStep={currentStep}
activeColor={getStepColor(currentStep)}
/>
</div>
<AnimatePresence mode="wait">
{/* Adım 0: Yükleme Yöntemi Seçimi */}
{currentStep === 0 && (
<motion.div
key="step0"
initial="hidden"
animate="visible"
exit="exit"
variants={variants}
transition={{ duration: 0.3 }}
className="py-4"
>
<MethodSelection
onSelectMethod={handleSelectMethod}
/>
</motion.div>
)}
{/* Adım 1: Ürün Bilgileri */}
{currentStep === 1 && (
<motion.div
key="step1"
initial="hidden"
animate="visible"
exit="exit"
variants={variants}
transition={{ duration: 0.3 }}
className="flex flex-col gap-6 w-full py-4"
>
<StepHeader
title={selectedMethod === "manual" ? "Ürün Bilgilerini Girin" : "Excel Dosyasını Yükleyin"}
subtitle={selectedMethod === "manual"
? "Eklemek istediğiniz ürünlerin bilgilerini girin"
: "Ürün bilgilerini içeren Excel dosyasını yükleyin"}
icon={selectedMethod === "manual" ? "edit" : "file-spreadsheet"}
color={getStepColor(1)}
onBack={() => setCurrentStep(0)}
/>
{selectedMethod === "manual" ? (
<ManualProductEntry
productName={productName}
setProductName={setProductName}
selectedProducts={selectedProducts}
handleAddProduct={handleAddProduct}
handleRemoveProduct={handleRemoveProduct}
onContinue={() => setCurrentStep(2)}
buttonColor={getStepColor(1)}
/>
) : (
<ExcelUpload
onContinue={() => setCurrentStep(2)}
buttonColor={getStepColor(1)}
/>
)}
</motion.div>
)}
{/* Adım 2: Önizleme */}
{currentStep === 2 && (
<motion.div
key="step2"
initial="hidden"
animate="visible"
exit="exit"
variants={variants}
transition={{ duration: 0.3 }}
className="flex flex-col gap-6 w-full py-4"
>
<StepHeader
title="Ürün Bilgilerini Önizleyin"
subtitle="Eklediğiniz ürünleri kontrol edin ve onaylayın"
icon="eye"
color={getStepColor(2)}
onBack={() => setCurrentStep(1)}
/>
<ProductPreview
selectedMethod={selectedMethod}
selectedProducts={selectedProducts}
onEdit={() => setCurrentStep(1)}
onConfirm={goToFinalStep}
buttonColor={getStepColor(2)}
isSubmitting={isSubmitting}
/>
</motion.div>
)}
{/* Adım 3: Tamamlandı */}
{currentStep === 3 && (
<motion.div
key="step3"
initial="hidden"
animate="visible"
variants={variants}
transition={{ duration: 0.3 }}
className="py-4"
>
<CompletionMessage
selectedMethod={selectedMethod}
productCount={selectedProducts.length}
onAddNew={handleReset}
onGoToList={() => router.push('/products')}
color={getStepColor(3)}
isProcessing={isSubmitting}
/>
</motion.div>
)}
</AnimatePresence>
{/* İlerleme göstergesi */}
<div className="mt-8 flex justify-between items-center text-sm text-gray-500 border-t pt-4">
<div>
{currentStep > 0 && currentStep < 3 && (
<button
onClick={() => setCurrentStep(currentStep - 1)}
className="flex items-center text-gray-600 hover:text-gray-900 transition-colors"
disabled={isSubmitting}
>
<ArrowLeft className="h-4 w-4 mr-1" />
Geri
</button>
)}
</div>
<div className="flex items-center">
<span className={`font-medium ${getStepColor(currentStep).replace('bg-', 'text-')}`}>
Adım {currentStep + 1}/{steps.length}
</span>
{currentStep === 3 && (
<CheckCircle className="h-4 w-4 ml-2 text-green-500" />
)}
</div>
<div>
{currentStep < 2 && currentStep > 0 && (
<button
onClick={() => setCurrentStep(currentStep + 1)}
className="flex items-center text-gray-600 hover:text-gray-900 transition-colors"
disabled={isSubmitting}
>
İleri
<ArrowRight className="h-4 w-4 ml-1" />
</button>
)}
</div>
</div>
</ComponentCard>
);
};