Vitron-Front/app/components/store/StoreForm.tsx
Arda Samadi a642fc0fae feat(seller-team): multi-user dashboard — store switcher, role gating, team UI
- useMyStores + team hooks (invite by phone / list / remove) in use-seller-hooks.
- useStoreOwnership rewritten to allow staff (not just the owner) via my-stores;
  exposes `role` so callers can gate owner-only UI, and no longer redirects
  members away from a store they help manage.
- Store settings: owner-only items (financial, shipping, edit store, instagram
  sync, team) hidden from staff; new "مدیریت اعضای تیم" entry (owner-only).
- New /store/:storeId/team page: invite by phone + members list with pending/
  active status + remove (owner-only, redirects staff). MyLayout keeps store mode
  on the team route.
- StoreForm edit is owner-only (redirects staff). Profile "enter my store" now
  routes owners to their store and staff to the store they manage.
- Chat already split earlier: seller inbox vs personal buyer inbox.

Depends on the seller-team backend; ships on this branch, not main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:43:53 +03:30

1053 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"
: "flex flex-col gap-6 p-4 bg-WHITE";
return (
<div className={containerClass}>
{renderHeader()}
<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>
);
}