Vitron-Front/app/components/store/StoreForm.tsx
Arda Samadi 16389fdd85 fix(desktop): lay out dashboard sub-pages + form pages for wide screens
- HeaderWithSupport is now lg:hidden (all its callers are desktop-full-width
  store/profile pages), removing the stray mobile back+support bar on desktop.
- Constrain + title the previously mobile-only pages so their content doesn't
  stretch edge-to-edge on desktop: store edit/create form (StoreForm), add
  product, shipping method, financial sub-pages (cash-funds/reports/
  transactions), order detail, instagram auth; and profile add/edit address,
  set detail, and payment result. Forms use a narrow centered column, lists/
  details a wider one. Transactions keeps its filter button (header restyled,
  not hidden).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 12:06:13 +03:30

1056 lines
33 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useRef, useEffect } from "react";
import { useNavigate } from "@remix-run/react";
import { Loader2, Save, Store, Upload } from "lucide-react";
import ProfilePagesHeader from "../ProfilePagesHeader";
import HeaderWithSupport from "../HeaderWithSupport";
import AppInput from "../AppInput";
import AppTextArea from "../AppTextArea";
import AppSelectBox from "../AppSelectBox";
import { Button } from "../ui/button";
import { FormField } from "../ui/FormField";
import { ImageUpload } from "./ImageUpload";
import { convertHeicToJpeg } from "~/utils/convert-heic";
import { ImageUploadDrawer } from "./ImageUploadDrawer";
import { Textarea } from "../ui/textarea";
import { useToast } from "../../hooks/use-toast";
import {
useCreateSellerApplication,
useSellerCategories,
useStateCityData,
useSellerUpdate,
useSellerData,
} from "../../requestHandler/use-seller-hooks";
import { useImageUpload } from "../../requestHandler/use-media-hooks";
import { SellerApplication } from "../../../src/api/types/models";
import {
validateUsername,
validateInstagramId,
validateStoreName,
validateStoreDescription,
} from "../../utils/validation";
import { useServiceGetProfile } from "../../requestHandler/use-profile-hooks";
import { LoadingModal } from "../../routes/store.add.manual.$storeId";
import { useStoreOwnership } from "../../hooks/useStoreOwnership";
// Types
interface CategoryData {
id: string;
name: string;
}
interface CityData {
id: number;
name: string;
}
interface StateData {
id: number;
name: string;
cities: CityData[];
}
interface CategoryOption {
value: string;
label: string;
}
interface StateOption {
value: string;
label: string;
}
interface CityOption {
value: string;
label: string;
}
interface ValidationErrors {
username: string;
storeName: string;
storeDescription: string;
instagramId: string;
}
interface FormData {
username: string;
storeName: string;
storeDescription: string;
selectedCategory: string;
selectedState: string;
selectedCity: string;
instagramId: string;
storeLogo: string;
bannerType: string;
bannerColor: string;
bannerImage: string;
}
// Preset banner colors offered alongside the custom color picker.
const BANNER_PRESETS = [
"#1d2b4d",
"#0f172a",
"#111827",
"#374151",
"#7c3aed",
"#be123c",
"#0e7490",
"#15803d",
"#b45309",
"#db2777",
];
const DEFAULT_BANNER_COLOR = "#1d2b4d";
interface StoreFormProps {
mode: "create" | "edit";
storeId?: string;
}
export default function StoreForm({ mode, storeId }: StoreFormProps) {
const navigate = useNavigate();
const { toast } = useToast();
const fileInputRef = useRef<HTMLInputElement>(null);
// Conditional data fetching for edit mode
const { data: sellerData, isLoading: isSellerLoading } = useSellerData(
mode === "edit" ? storeId : undefined
);
// Check store ownership for edit mode
const { isLoading: isOwnershipLoading, role } = useStoreOwnership(
mode === "edit" ? storeId : undefined,
mode
);
// Editing store info is owner-only; send staff back to the dashboard.
useEffect(() => {
if (mode === "edit" && !isOwnershipLoading && role && role !== "owner") {
navigate(`/store/${storeId}`);
}
}, [mode, isOwnershipLoading, role, storeId, navigate]);
// Form state
const [selectedImage, setSelectedImage] = useState<File | null>(null);
const [imagePreviewUrl, setImagePreviewUrl] = useState<string | null>(null);
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [hasChanges, setHasChanges] = useState(false);
// Banner state (edit mode)
const bannerFileInputRef = useRef<HTMLInputElement>(null);
const [selectedBannerImage, setSelectedBannerImage] = useState<File | null>(
null
);
const [bannerPreviewUrl, setBannerPreviewUrl] = useState<string | null>(null);
// Loading modal states
const [createSellerOpen, setCreateSellerOpen] = useState(false);
const [imageUploadOpen, setImageUploadOpen] = useState(false);
const [formData, setFormData] = useState<FormData>({
username: "",
storeName: "",
storeDescription: "",
selectedCategory: "",
selectedState: "",
selectedCity: "",
instagramId: "",
storeLogo: "",
bannerType: "color",
bannerColor: "",
bannerImage: "",
});
// Validation errors state
const [validationErrors, setValidationErrors] = useState<ValidationErrors>({
username: "",
storeName: "",
storeDescription: "",
instagramId: "",
});
// Mutations and queries
const createSellerMutation = useCreateSellerApplication();
const updateSellerMutation = useSellerUpdate();
const imageUploadMutation = useImageUpload();
// Fetch categories and state/city data
const { data: categoriesData, isLoading: categoriesLoading } =
useSellerCategories();
const { data: stateCityData, isLoading: stateCityLoading } =
useStateCityData();
const { data: profileData } = useServiceGetProfile();
// Initialize form with seller data for edit mode
useEffect(() => {
if (mode === "edit" && sellerData) {
const initialData = {
username: sellerData.username || "",
storeName: sellerData.storeName || "",
storeDescription: sellerData.storeDescription || "",
selectedCategory: "", // Not available in SellerUpdate
selectedState: "", // Not available in SellerUpdate
selectedCity: "", // Not available in SellerUpdate
instagramId: sellerData.instagramId || "",
storeLogo: sellerData.storeLogo || "",
bannerType: sellerData.bannerType || "color",
bannerColor: sellerData.bannerColor || "",
bannerImage: sellerData.bannerImage || "",
};
setFormData(initialData);
}
}, [mode, sellerData]);
// Track changes for edit mode
useEffect(() => {
if (mode === "edit" && sellerData) {
const hasFormChanges =
formData.storeName !== (sellerData.storeName || "") ||
formData.storeDescription !== (sellerData.storeDescription || "") ||
formData.instagramId !== (sellerData.instagramId || "") ||
formData.storeLogo !== (sellerData.storeLogo || "") ||
formData.bannerType !== (sellerData.bannerType || "color") ||
formData.bannerColor !== (sellerData.bannerColor || "") ||
formData.bannerImage !== (sellerData.bannerImage || "") ||
selectedImage !== null || // Include image changes
selectedBannerImage !== null;
setHasChanges(hasFormChanges);
}
}, [mode, formData, sellerData, selectedImage, selectedBannerImage]);
// Redirect if user already has a store (create mode only)
useEffect(() => {
if (
mode === "create" &&
profileData?.sellerUsername &&
profileData.sellerUsername !== ""
) {
navigate(`/store/${profileData.sellerUsername}`);
}
}, [mode, profileData, navigate]);
// Clean up image preview URL on component unmount
useEffect(() => {
return () => {
if (imagePreviewUrl) {
URL.revokeObjectURL(imagePreviewUrl);
}
};
}, [imagePreviewUrl]);
// Clean up banner preview URL
useEffect(() => {
return () => {
if (bannerPreviewUrl) {
URL.revokeObjectURL(bannerPreviewUrl);
}
};
}, [bannerPreviewUrl]);
// Show loading while checking ownership or loading seller data (edit mode)
if (mode === "edit" && (isOwnershipLoading || isSellerLoading)) {
return (
<div className="flex items-center justify-center min-h-[500px]">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
);
}
// Transform data for AppSelectBox
const categoryOptions: CategoryOption[] =
(categoriesData as CategoryData[])?.map((cat) => ({
value: cat.id || "",
label: cat.name,
})) || [];
const stateOptions: StateOption[] =
(stateCityData as StateData[])?.map((state) => ({
value: state.id?.toString() || "",
label: state.name,
})) || [];
const selectedStateData = (stateCityData as StateData[])?.find(
(state) => state.id?.toString() === formData.selectedState
);
const cityOptions: CityOption[] =
selectedStateData?.cities?.map((city) => ({
value: city.id?.toString() || "",
label: city.name,
})) || [];
// Validate individual field
const validateField = (
name: keyof ValidationErrors,
value: string
): boolean => {
let error = "";
switch (name) {
case "username":
error = validateUsername(value) || "";
break;
case "storeName":
error = validateStoreName(value) || "";
break;
case "storeDescription":
error = validateStoreDescription(value) || "";
break;
case "instagramId":
error = validateInstagramId(value) || "";
break;
}
setValidationErrors((prev) => ({
...prev,
[name]: error,
}));
return !error;
};
// Handle form input changes
const handleInputChange = (name: keyof FormData, value: string) => {
// Sanitize Instagram ID input - only allow valid characters
if (name === "instagramId") {
// Remove @ symbol if present and only allow alphanumeric, dots, and underscores
value = value.replace(/^@/, "").replace(/[^a-zA-Z0-9._]/g, "");
}
setFormData((prev) => ({
...prev,
[name]: value,
}));
// Validate field on change if it's a validatable field
if (name in validationErrors) {
validateField(name as keyof ValidationErrors, value);
}
};
// Form validation
const isValidForm =
mode === "create"
? formData.username.trim() !== "" &&
formData.storeName.trim() !== "" &&
formData.storeDescription.trim() !== "" &&
formData.selectedCategory !== "" &&
formData.selectedState !== "" &&
formData.selectedCity !== "" &&
formData.instagramId.trim() !== "" &&
!validationErrors.username &&
!validationErrors.storeName &&
!validationErrors.storeDescription &&
!validationErrors.instagramId
: formData.storeName.trim() !== "" &&
!validationErrors.storeName &&
!validationErrors.storeDescription &&
!validationErrors.instagramId;
// Handle image selection
const handleImageSelect = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
const picked = event.target.files?.[0];
if (picked) {
// iPhone logos are often HEIC, which most browsers can't preview; convert.
const file = await convertHeicToJpeg(picked);
if (imagePreviewUrl) {
URL.revokeObjectURL(imagePreviewUrl);
}
const newImageUrl = URL.createObjectURL(file);
setSelectedImage(file);
setImagePreviewUrl(newImageUrl);
}
};
// Handle banner image selection (from gallery; GIFs preserved)
const handleBannerSelect = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
const picked = event.target.files?.[0];
if (picked) {
// convertHeicToJpeg only touches HEIC; GIF/PNG/JPG pass through untouched.
const file = await convertHeicToJpeg(picked);
if (bannerPreviewUrl) {
URL.revokeObjectURL(bannerPreviewUrl);
}
const newUrl = URL.createObjectURL(file);
setSelectedBannerImage(file);
setBannerPreviewUrl(newUrl);
setFormData((prev) => ({ ...prev, bannerType: "image" }));
}
};
// Handle drawer actions
const handleUploadClick = () => {
setIsDrawerOpen(true);
};
const handleCameraSelect = () => {
setIsDrawerOpen(false);
if (fileInputRef.current) {
fileInputRef.current.setAttribute("capture", "environment");
fileInputRef.current.click();
}
};
const handleGallerySelect = () => {
setIsDrawerOpen(false);
if (fileInputRef.current) {
fileInputRef.current.removeAttribute("capture");
fileInputRef.current.click();
}
};
// Handle state change - reset city when state changes
const handleStateChange = (value: string) => {
setFormData((prev) => ({
...prev,
selectedState: value,
selectedCity: "",
}));
};
const handleBack = () => {
if (mode === "create") {
navigate(-1);
} else {
navigate(`/store/${storeId}/setting`);
}
};
// Handle form submission
const handleSubmit = async () => {
if (mode === "edit") {
// Edit mode submission
if (!hasChanges) return;
let logoUrl = formData.storeLogo; // Keep existing logo by default
// If user selected a new image, upload it first
if (selectedImage) {
try {
setImageUploadOpen(true);
const imageResponse = await imageUploadMutation.mutateAsync({
file: selectedImage,
usageType: "logo",
});
logoUrl = imageResponse.originalUrl || formData.storeLogo; // Use new image or fallback to existing
setImageUploadOpen(false);
} catch (error) {
console.error("Error uploading image:", error);
setImageUploadOpen(false);
toast({
title: "خطا در آپلود تصویر",
description:
"متاسفانه خطایی در آپلود تصویر رخ داد. لطفا مجددا تلاش کنید.",
variant: "destructive",
});
return; // Stop submission if image upload fails
}
}
// Upload a newly selected banner image first (GIFs preserved by the API).
let bannerImageUrl = formData.bannerImage;
if (selectedBannerImage) {
try {
setImageUploadOpen(true);
const bannerResponse = await imageUploadMutation.mutateAsync({
file: selectedBannerImage,
usageType: "banner",
});
bannerImageUrl = bannerResponse.originalUrl || formData.bannerImage;
setImageUploadOpen(false);
} catch (error) {
console.error("Error uploading banner:", error);
setImageUploadOpen(false);
toast({
title: "خطا در آپلود بنر",
description:
"متاسفانه خطایی در آپلود بنر رخ داد. لطفا مجددا تلاش کنید.",
variant: "destructive",
});
return;
}
}
const updateData = {
storeName: formData.storeName,
storeDescription: formData.storeDescription || null,
instagramId: formData.instagramId || null,
storeLogo: logoUrl || null,
bannerType: formData.bannerType || "color",
bannerColor: formData.bannerColor || null,
bannerImage: bannerImageUrl || null,
};
updateSellerMutation.mutate({
username: storeId!,
data: updateData,
});
return;
}
// Create mode submission
const usernameValid = validateField("username", formData.username);
const storeNameValid = validateField("storeName", formData.storeName);
const storeDescriptionValid = validateField(
"storeDescription",
formData.storeDescription
);
const instagramIdValid = validateField("instagramId", formData.instagramId);
if (
!isValidForm ||
!usernameValid ||
!storeNameValid ||
!storeDescriptionValid ||
!instagramIdValid
) {
toast({
description: "لطفا خطاهای فرم را تصحیح کنید.",
variant: "destructive",
});
return;
}
let imageUrl: string | null = null;
// If user selected an image, upload it first
if (selectedImage) {
try {
setImageUploadOpen(true);
const imageResponse = await imageUploadMutation.mutateAsync({
file: selectedImage,
usageType: "logo",
});
imageUrl = imageResponse.originalUrl || null;
setImageUploadOpen(false);
} catch (error) {
console.error("Error uploading image:", error);
setImageUploadOpen(false);
toast({
title: "خطا در آپلود تصویر",
description:
"متاسفانه خطایی در آپلود تصویر رخ داد. لطفا مجددا تلاش کنید.",
variant: "destructive",
});
return;
}
}
const applicationData: SellerApplication = {
username: formData.username,
storeName: formData.storeName,
storeDescription: formData.storeDescription,
storeLogo: imageUrl,
instagramId: formData.instagramId,
category: formData.selectedCategory,
state: parseInt(formData.selectedState),
city: parseInt(formData.selectedCity),
};
setCreateSellerOpen(true);
try {
await createSellerMutation.mutateAsync(applicationData);
setCreateSellerOpen(false);
toast({
title: "فروشگاه ایجاد شد",
description: "فروشگاه شما با موفقیت ایجاد شد.",
});
// Small delay to ensure profile is updated before navigation
setTimeout(() => {
navigate(`/store/${applicationData.username}`);
}, 500);
} catch (error) {
console.error("Error creating seller application:", error);
setCreateSellerOpen(false);
toast({
title: "خطا در ثبت درخواست",
description:
"متاسفانه خطایی در ثبت درخواست رخ داد. لطفا مجددا تلاش کنید.",
variant: "destructive",
});
}
};
// Render header based on mode
const renderHeader = () => {
if (mode === "create") {
return <ProfilePagesHeader title="ایجاد فروشگاه" />;
} else {
return (
<HeaderWithSupport title="ویرایش اطلاعات فروشگاه" onBack={handleBack} />
);
}
};
// Render logo section based on mode
const renderLogoSection = () => {
// Show the current image or selected image preview
const currentImageUrl = imagePreviewUrl || formData.storeLogo;
return (
<>
<p className="text-R12 text-center text-BLACK2">
{mode === "create"
? "عکس پروفایل فروشگاه خود را انتخاب کنید:"
: "لوگو فروشگاه:"}
</p>
<ImageUpload
imagePreviewUrl={currentImageUrl}
onUploadClick={handleUploadClick}
onImageSelect={handleImageSelect}
/>
</>
);
};
// Render banner editor (edit mode only): color swatches/picker or image/GIF
const renderBannerSection = () => {
const previewImage = bannerPreviewUrl || formData.bannerImage;
const previewColor = formData.bannerColor || DEFAULT_BANNER_COLOR;
return (
<div className="flex flex-col gap-3">
<p className="text-R14">بنر فروشگاه</p>
{/* Live preview */}
<div className="relative h-28 w-full rounded-[14px] overflow-hidden border border-WHITE2 bg-WHITE3">
{formData.bannerType === "image" && previewImage ? (
<img
src={previewImage}
alt="بنر فروشگاه"
className="w-full h-full object-cover"
/>
) : (
<div
className="w-full h-full"
style={{ backgroundColor: previewColor }}
/>
)}
</div>
{/* Type toggle */}
<div className="flex gap-2">
<button
type="button"
onClick={() => handleInputChange("bannerType", "color")}
className={`flex-1 h-10 rounded-[10px] border text-R12 ${
formData.bannerType === "color"
? "bg-BLACK text-WHITE border-BLACK"
: "bg-WHITE2 text-BLACK border-WHITE3"
}`}
>
رنگ
</button>
<button
type="button"
onClick={() => handleInputChange("bannerType", "image")}
className={`flex-1 h-10 rounded-[10px] border text-R12 ${
formData.bannerType === "image"
? "bg-BLACK text-WHITE border-BLACK"
: "bg-WHITE2 text-BLACK border-WHITE3"
}`}
>
تصویر
</button>
</div>
{formData.bannerType === "color" ? (
<div className="flex flex-wrap items-center gap-2">
{BANNER_PRESETS.map((c) => (
<button
key={c}
type="button"
onClick={() => handleInputChange("bannerColor", c)}
aria-label={c}
className={`w-8 h-8 rounded-full border-2 transition-all ${
(formData.bannerColor || "").toLowerCase() === c.toLowerCase()
? "border-BLACK scale-110"
: "border-WHITE2"
}`}
style={{ backgroundColor: c }}
/>
))}
{/* Custom color */}
<label
className="w-8 h-8 rounded-full border-2 border-dashed border-GRAY3 overflow-hidden flex items-center justify-center cursor-pointer"
aria-label="رنگ دلخواه"
>
<input
type="color"
value={previewColor}
onChange={(e) =>
handleInputChange("bannerColor", e.target.value)
}
className="w-10 h-10 cursor-pointer border-0 bg-transparent p-0"
/>
</label>
</div>
) : (
<div className="flex flex-col gap-2">
<button
type="button"
onClick={() => bannerFileInputRef.current?.click()}
className="w-full h-11 rounded-[10px] border-2 border-dashed border-GRAY3 flex items-center justify-center gap-2 text-GRAY text-R12 hover:border-primary hover:text-primary transition-colors"
>
<Upload size={18} />
{previewImage ? "تغییر تصویر بنر" : "آپلود تصویر بنر"}
</button>
<p className="text-R10 text-BLACK2 leading-5">
اندازه پیشنهادی: ۱۶۰۰×۵۰۰ پیکسل. بنر تمامعرض نمایش داده میشود و
فقط ارتفاع آن برش میخورد؛ بخشهای مهم (متن/لوگو) را وسط قرار دهید.
فرمت JPG/PNG/WebP یا GIF متحرک.
</p>
</div>
)}
<input
ref={bannerFileInputRef}
type="file"
accept="image/*,image/gif"
onChange={handleBannerSelect}
className="hidden"
/>
</div>
);
};
// Render description field based on mode
const renderDescriptionField = () => {
if (mode === "create") {
return (
<FormField
label="توضیحات فروشگاه"
required
error={validationErrors.storeDescription}
>
<AppTextArea
inputTitle=""
inputValue={formData.storeDescription}
inputPlaceholder="در مورد فروشگاه خود توضیح دهید..."
inputOnChange={(e) =>
handleInputChange("storeDescription", e.target.value)
}
/>
</FormField>
);
} else {
return (
<div className="flex flex-col gap-2">
<p className="text-R14">توضیحات فروشگاه</p>
<Textarea
placeholder="توضیحات کوتاهی از فروشگاه خود بنویسید..."
value={formData.storeDescription}
onChange={(e) =>
handleInputChange("storeDescription", e.target.value)
}
className="min-h-[100px] resize-none"
dir="rtl"
/>
</div>
);
}
};
// Render submit button
const renderSubmitButton = () => {
if (mode === "create") {
return (
<Button
disabled={
!isValidForm ||
createSellerMutation.isPending ||
imageUploadMutation.isPending
}
className="w-full"
size="xl"
variant="dark"
onClick={handleSubmit}
>
{imageUploadMutation.isPending ? (
<>
<Loader2 className="animate-spin" />
در حال آپلود تصویر...
</>
) : createSellerMutation.isPending ? (
<>
<Loader2 className="animate-spin" />
در حال ثبت...
</>
) : (
"ایجاد فروشگاه"
)}
</Button>
);
} else {
return (
<div className="sticky bottom-0 bg-WHITE border-t border-WHITE2 p-4">
<Button
onClick={handleSubmit}
disabled={
!hasChanges ||
updateSellerMutation.isPending ||
imageUploadMutation.isPending ||
!formData.storeName.trim()
}
className="w-full"
size="lg"
variant={hasChanges ? "dark" : "outline"}
>
{imageUploadMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 animate-spin mr-2" />
در حال آپلود تصویر...
</>
) : updateSellerMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 animate-spin mr-2" />
در حال ذخیره...
</>
) : (
<>
<Save className="w-4 h-4 mr-2" />
{hasChanges ? "ذخیره تغییرات" : "بدون تغییر"}
</>
)}
</Button>
</div>
);
}
};
const containerClass =
mode === "create"
? "flex flex-col gap-0 w-full pb-20"
: "flex flex-col bg-WHITE";
const contentClass =
mode === "create"
? "flex flex-col gap-6 w-full px-4 pt-6 lg:max-w-[640px] lg:mx-auto lg:pt-8"
: "flex flex-col gap-6 p-4 bg-WHITE lg:max-w-[640px] lg:mx-auto lg:pt-8";
return (
<div className={containerClass}>
{renderHeader()}
<h1 className="hidden lg:block text-[28px] font-extrabold px-4 lg:max-w-[640px] lg:mx-auto lg:w-full lg:px-0 lg:pt-8">
{mode === "create" ? "ایجاد فروشگاه" : "ویرایش اطلاعات فروشگاه"}
</h1>
<div className={contentClass}>
{renderLogoSection()}
{/* Banner editor (edit mode) */}
{mode === "edit" && renderBannerSection()}
{/* Username Input - Only for create mode */}
{mode === "create" && (
<FormField
label="نام کاربری"
required
error={validationErrors.username}
>
<AppInput
inputIcon={Store}
inputTitle=""
inputValue={formData.username}
inputPlaceholder="username"
inputDir="ltr"
inputOnChange={(e) =>
handleInputChange("username", e.target.value)
}
/>
</FormField>
)}
{/* Store Name Input */}
{mode === "create" ? (
<FormField
label="نام فروشگاه"
required
error={validationErrors.storeName}
>
<AppInput
inputIcon={Store}
inputTitle=""
inputValue={formData.storeName}
inputPlaceholder="نام فروشگاه خود را وارد کنید"
inputDir="rtl"
inputOnChange={(e) =>
handleInputChange("storeName", e.target.value)
}
/>
</FormField>
) : (
<AppInput
inputTitle="نام فروشگاه"
inputValue={formData.storeName}
inputPlaceholder="نام فروشگاه خود را وارد کنید"
inputOnChange={(e) =>
handleInputChange("storeName", e.target.value)
}
inputDir="rtl"
/>
)}
{/* Store Description */}
{renderDescriptionField()}
{/* Instagram ID */}
{mode === "create" ? (
<FormField
label="آیدی اینستاگرام"
required
error={validationErrors.instagramId}
>
<AppInput
inputTitle=""
inputValue={formData.instagramId}
inputPlaceholder="instagram id"
inputDir="ltr"
inputOnChange={(e) =>
handleInputChange("instagramId", e.target.value)
}
/>
</FormField>
) : (
<AppInput
inputTitle="آیدی اینستاگرام"
inputValue={formData.instagramId}
inputPlaceholder="@username"
inputOnChange={(e) =>
handleInputChange("instagramId", e.target.value)
}
inputDir="ltr"
/>
)}
{/* Category Selection - Only for create mode */}
{mode === "create" && (
<FormField label="دسته‌بندی" required>
<AppSelectBox
items={categoryOptions}
value={formData.selectedCategory}
setValue={(value) => {
const newValue =
typeof value === "function"
? value(formData.selectedCategory)
: value;
handleInputChange("selectedCategory", newValue);
}}
placeholder="انتخاب دسته‌بندی"
loading={categoriesLoading}
haveSearchbar={true}
width="w-full"
/>
</FormField>
)}
{/* State and City Selection - Only for create mode */}
{mode === "create" && (
<div className="flex gap-2 w-full">
<FormField label="استان" required>
<AppSelectBox
items={stateOptions}
value={formData.selectedState}
setValue={(value) => {
const newValue =
typeof value === "function"
? value(formData.selectedState)
: value;
handleStateChange(newValue);
}}
placeholder="انتخاب استان"
loading={stateCityLoading}
haveSearchbar={true}
width="w-full"
/>
</FormField>
<FormField label="شهر" required>
<AppSelectBox
items={cityOptions}
value={formData.selectedCity}
setValue={(value) => {
const newValue =
typeof value === "function"
? value(formData.selectedCity)
: value;
handleInputChange("selectedCity", newValue);
}}
placeholder={
formData.selectedState
? "انتخاب شهر"
: "ابتدا استان را انتخاب کنید"
}
loading={false}
haveSearchbar={true}
width="w-full"
disabled={!formData.selectedState}
/>
</FormField>
</div>
)}
{/* Store Status Display - Only for edit mode */}
{mode === "edit" && sellerData && (
<div className="p-4 bg-WHITE rounded-lg border">
<div className="flex w-full items-center justify-between">
<div className="flex flex-col gap-1 w-full">
<div className="flex w-full gap-1 items-center">
<p className="text-R12">وضعیت فروشگاه</p>
<span
className={`px-2 py-1 rounded-full text-xs ${
sellerData?.isVerified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}`}
>
{sellerData?.isVerified ? "فعال" : "در انتظار تایید"}
</span>
</div>
<p className="text-R10 text-BLACK2">
{sellerData?.isVerified
? "فروشگاه شما فعال است و برای مخاطبان نمایش داده می‌شود"
: "فروشگاه شما در انتظار تایید است"}
</p>
</div>
</div>
</div>
)}
{/* Submit Button */}
{renderSubmitButton()}
</div>
{/* Image Upload Drawer - Available for both modes */}
<ImageUploadDrawer
isOpen={isDrawerOpen}
onOpenChange={setIsDrawerOpen}
onCameraSelect={handleCameraSelect}
onGallerySelect={handleGallerySelect}
/>
{/* Loading Modals */}
<LoadingModal
open={createSellerOpen}
setOpen={setCreateSellerOpen}
title="در حال بررسی..."
/>
<LoadingModal
open={imageUploadOpen}
setOpen={setImageUploadOpen}
title="در حال آپلود تصویر..."
/>
{/* Hidden file input - Available for both modes */}
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleImageSelect}
className="hidden"
/>
</div>
);
}