Add customer and business user-product flows across dashboards.

Ship my-products / customer-products UI with gallery uploads, status controls, module gating, and related shared UI polish.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-10 00:23:03 +03:30
co-authored by Cursor
parent c5bdaad16b
commit 1271d96539
96 changed files with 10532 additions and 345 deletions
+264
View File
@@ -12,6 +12,7 @@ const en = {
'nav.addresses': 'My Addresses',
'nav.orders': 'My Orders',
'nav.favorites': 'My Favorites',
'nav.myProducts': 'My Products',
'nav.help': 'Help Center',
'nav.logout': 'Logout',
@@ -30,12 +31,19 @@ const en = {
'home.card.addresses.title': 'My Addresses',
'home.card.addresses.desc': 'Manage your shipping addresses for checkout and deliveries.',
'home.card.addresses.link': 'View addresses',
'home.card.addresses.count': 'addresses',
'home.card.orders.title': 'My Orders',
'home.card.orders.desc': 'Track your orders, view order history and order details.',
'home.card.orders.link': 'View orders',
'home.card.orders.count': 'orders',
'home.card.favorites.title': 'My Favorites',
'home.card.favorites.desc': 'Browse and manage your saved favorite products.',
'home.card.favorites.link': 'View favorites',
'home.card.favorites.count': 'favorites',
'home.card.myProducts.title': 'My Products',
'home.card.myProducts.desc': 'List and manage products you have submitted.',
'home.card.myProducts.link': 'View my products',
'home.card.myProducts.count': 'products',
'profile.title': 'My Profile',
'profile.subtitle': 'Update your personal information and contact details.',
@@ -154,6 +162,128 @@ const en = {
'favorites.inStock': '{count} in stock',
'favorites.variantOne': '1 variant',
'favorites.variantMany': '{count} variants',
'myProducts.title': 'My Products',
'myProducts.subtitle':
'The products below are yours and will be sold on our website after supervisor approval.',
'myProducts.empty': 'You have not added any products yet.',
'myProducts.add': 'Add product',
'myProducts.addTitle': 'Add product',
'myProducts.addSubtitle': 'Submit a new product for review.',
'myProducts.addComingSoon': 'The add form will be designed next.',
'myProducts.backToList': 'Back to my products',
'myProducts.editTitle': 'Edit product',
'myProducts.editSubtitle': 'Update your listing details, then save.',
'myProducts.detailsTitle': 'Product details',
'myProducts.detailsSubtitle': 'Review your submitted listing.',
'myProducts.edit': 'Edit product',
'myProducts.yes': 'Yes',
'myProducts.editSoon': 'Edit product will be available soon.',
'myProducts.remove': 'Remove product',
'myProducts.removeConfirm': 'Remove this product from your stock?',
'myProducts.removeSuccess': 'Product removed.',
'myProducts.promote': 'Promote product',
'myProducts.promoted': 'Promoted',
'myProducts.promoteSuccess': 'Product promoted.',
'myProducts.priceUnavailable': 'Price on request',
'myProducts.status.pending': 'Pending',
'myProducts.status.published': 'Approved',
'myProducts.status.rejected': 'Rejected',
'myProducts.status.archived': 'Archived',
'myProducts.stepperLabel': 'Add product steps',
'myProducts.step.basics': 'Basics',
'myProducts.step.basicsHint': 'Category, names, description, price, and location.',
'myProducts.step.basicsPlaceholder': 'Basic fields will go here.',
'myProducts.step.images': 'Images',
'myProducts.step.imagesHint': 'Add a cropped thumbnail and gallery photos.',
'myProducts.step.details': 'Details',
'myProducts.step.detailsHint': 'Description and technical information for the selected category.',
'myProducts.step.detailsPlaceholder': 'Details and technical fields will go here.',
'myProducts.step.technical': 'Technical data',
'myProducts.step.technicalHint':
'Choose condition, add optional notes, then fill the category technical form.',
'myProducts.optional': '(optional)',
'myProducts.loading': 'Loading products…',
'myProducts.fields.category': 'Category',
'myProducts.fields.selectCategory': 'Select category',
'myProducts.fields.searchCategory': 'Search category…',
'myProducts.fields.clearCategory': 'Clear category',
'myProducts.fields.noCategories': 'No categories found',
'myProducts.fields.titleFa': 'Name (FA)',
'myProducts.fields.titleFaPlaceholder': 'Product name in Farsi',
'myProducts.fields.titleEn': 'Name (EN)',
'myProducts.fields.titleEnPlaceholder': 'Product name in English',
'myProducts.fields.description': 'Description',
'myProducts.fields.descriptionPlaceholder': 'Short description of your product',
'myProducts.fields.price': 'Desired price',
'myProducts.fields.priceSuggested': 'Your suggested price',
'myProducts.fields.priceByExpert': 'I want an expert to set the price',
'myProducts.fields.pricePlaceholder': 'e.g. 1,500,000',
'myProducts.fields.priceUnit': 'Unit',
'myProducts.fields.priceUnit.IRT': 'IRT',
'myProducts.fields.priceUnit.USD': 'Dollar',
'myProducts.fields.priceUnit.EUR': 'EURO',
'myProducts.fields.priceUnit.AED': 'AED',
'myProducts.fields.location': 'Location',
'myProducts.fields.country': 'Country',
'myProducts.fields.selectCountry': 'Select country',
'myProducts.fields.province': 'Province',
'myProducts.fields.selectProvince': 'Select province',
'myProducts.fields.city': 'City',
'myProducts.fields.selectCity': 'Select city',
'myProducts.fields.searchCity': 'Search city…',
'myProducts.fields.clearCity': 'Clear city',
'myProducts.fields.noCities': 'No cities found',
'myProducts.fields.deliveryNote': 'Pickup / delivery note',
'myProducts.fields.deliveryNotePlaceholder': 'e.g. pickup only, evening delivery…',
'myProducts.fields.district': 'District',
'myProducts.fields.selectDistrict': 'Select district',
'myProducts.fields.condition': 'Condition',
'myProducts.fields.technicalNotes': 'Technical notes',
'myProducts.fields.technicalNotesPlaceholder': 'Optional extra technical details…',
'myProducts.condition.new': 'New',
'myProducts.condition.stock': 'Stock',
'myProducts.condition.needs_repair': 'Needs repair',
'myProducts.condition.scrap': 'Scrap',
'myProducts.images.thumbnail': 'Thumbnail',
'myProducts.images.thumbnailUpload': 'Upload thumbnail',
'myProducts.images.thumbnailHint': '3:2 landscape works best',
'myProducts.images.thumbnailChange': 'Change thumbnail',
'myProducts.images.thumbnailRemove': 'Remove thumbnail',
'myProducts.images.zoom': 'Zoom',
'myProducts.images.applyCrop': 'Apply crop',
'myProducts.images.gallery': 'Gallery',
'myProducts.images.galleryAdd': 'Add photos',
'myProducts.images.galleryHint': 'You can select multiple images.',
'myProducts.images.galleryRemove': 'Remove image {index}',
'myProducts.technical.select': 'Select…',
'myProducts.technical.needCategory': 'Choose a category in step 1 to load technical fields.',
'myProducts.technical.empty': 'This category has no technical fields yet.',
'myProducts.technical.categoryForm': 'Category technical form',
'myProducts.technical.loading': 'Loading technical fields…',
'myProducts.cancel': 'Cancel',
'myProducts.next': 'Next',
'myProducts.back': 'Back',
'myProducts.submit': 'Submit',
'myProducts.save': 'Save changes',
'myProducts.submitting': 'Submitting…',
'myProducts.submitSuccess': 'Product submitted for review.',
'myProducts.updateSuccess': 'Product updated.',
'myProducts.error.load': 'Unable to load your products.',
'myProducts.error.loadDetail': 'Unable to load this product.',
'myProducts.error.loadLocations': 'Unable to load locations.',
'myProducts.error.loadCategories': 'Unable to load categories.',
'myProducts.error.loadTechnicalForm': 'Unable to load the category technical form.',
'myProducts.error.categoryRequired': 'Please select a category.',
'myProducts.error.titleFaRequired': 'Please enter the Farsi name.',
'myProducts.error.locationRequired': 'Please select country and city.',
'myProducts.error.priceInvalid': 'Please enter a valid price.',
'myProducts.error.conditionRequired': 'Please select a condition.',
'myProducts.error.technicalRequired': 'Please fill required technical fields.',
'myProducts.error.submit': 'Unable to submit the product.',
'myProducts.error.update': 'Unable to update the product.',
'myProducts.error.remove': 'Unable to remove the product.',
'myProducts.error.promote': 'Unable to promote the product.',
'storeItems.price.contact': 'Contact for price',
'title.signIn': 'Sign in',
@@ -261,6 +391,7 @@ const fa: Record<MessageKey, string> = {
'nav.addresses': 'آدرس‌های من',
'nav.orders': 'سفارش‌های من',
'nav.favorites': 'علاقه‌مندی‌ها',
'nav.myProducts': 'محصولات من',
'nav.help': 'مرکز راهنما',
'nav.logout': 'خروج',
@@ -279,12 +410,19 @@ const fa: Record<MessageKey, string> = {
'home.card.addresses.title': 'آدرس‌های من',
'home.card.addresses.desc': 'آدرس‌های ارسال برای تسویه‌حساب و تحویل را مدیریت کنید.',
'home.card.addresses.link': 'مشاهده آدرس‌ها',
'home.card.addresses.count': 'آدرس',
'home.card.orders.title': 'سفارش‌های من',
'home.card.orders.desc': 'سفارش‌ها را پیگیری کنید و تاریخچه و جزئیات را ببینید.',
'home.card.orders.link': 'مشاهده سفارش‌ها',
'home.card.orders.count': 'سفارش',
'home.card.favorites.title': 'علاقه‌مندی‌ها',
'home.card.favorites.desc': 'محصولات ذخیره‌شده مورد علاقه‌تان را ببینید و مدیریت کنید.',
'home.card.favorites.link': 'مشاهده علاقه‌مندی‌ها',
'home.card.favorites.count': 'علاقه‌مندی',
'home.card.myProducts.title': 'محصولات من',
'home.card.myProducts.desc': 'محصولاتی که ثبت کرده‌اید را ببینید و مدیریت کنید.',
'home.card.myProducts.link': 'مشاهده محصولات من',
'home.card.myProducts.count': 'محصول',
'profile.title': 'پروفایل من',
'profile.subtitle': 'اطلاعات شخصی و راه‌های ارتباطی خود را به‌روزرسانی کنید.',
@@ -403,6 +541,128 @@ const fa: Record<MessageKey, string> = {
'favorites.inStock': '{count} موجود',
'favorites.variantOne': '۱ تنوع',
'favorites.variantMany': '{count} تنوع',
'myProducts.title': 'محصولات من',
'myProducts.subtitle':
'محصولات زیر، محصولات شما هستند که توسط وبسایت ما بعد از تایید ناظر به فروش خواهد رسید.',
'myProducts.empty': 'هنوز محصولی ثبت نکرده‌اید.',
'myProducts.add': 'افزودن محصول',
'myProducts.addTitle': 'افزودن محصول',
'myProducts.addSubtitle': 'محصول جدید را برای بررسی ارسال کنید.',
'myProducts.addComingSoon': 'فرم افزودن در مرحله بعد طراحی می‌شود.',
'myProducts.backToList': 'بازگشت به محصولات من',
'myProducts.editTitle': 'ویرایش محصول',
'myProducts.editSubtitle': 'جزئیات آگهی را به‌روز کنید و ذخیره کنید.',
'myProducts.detailsTitle': 'جزئیات محصول',
'myProducts.detailsSubtitle': 'جزئیات آگهی ثبت‌شده را ببینید.',
'myProducts.edit': 'ویرایش محصول',
'myProducts.yes': 'بله',
'myProducts.editSoon': 'ویرایش محصول به‌زودی در دسترس خواهد بود.',
'myProducts.remove': 'حذف محصول',
'myProducts.removeConfirm': 'این محصول از موجودی شما حذف شود؟',
'myProducts.removeSuccess': 'محصول حذف شد.',
'myProducts.promote': 'پروموت محصول',
'myProducts.promoted': 'پروموت شده',
'myProducts.promoteSuccess': 'محصول پروموت شد.',
'myProducts.priceUnavailable': 'قیمت اعلام نشده',
'myProducts.status.pending': 'در انتظار تأیید',
'myProducts.status.published': 'تأیید شده',
'myProducts.status.rejected': 'رد شده',
'myProducts.status.archived': 'بایگانی',
'myProducts.stepperLabel': 'مراحل افزودن محصول',
'myProducts.step.basics': 'اطلاعات پایه',
'myProducts.step.basicsHint': 'دسته‌بندی، نام، توضیحات، قیمت و موقعیت.',
'myProducts.step.basicsPlaceholder': 'فیلدهای پایه اینجا قرار می‌گیرند.',
'myProducts.step.images': 'تصاویر',
'myProducts.step.imagesHint': 'تصویر شاخص با برش و گالری تصاویر را اضافه کنید.',
'myProducts.step.details': 'جزئیات',
'myProducts.step.detailsHint': 'توضیحات و اطلاعات فنی بر اساس دسته‌بندی انتخاب‌شده.',
'myProducts.step.detailsPlaceholder': 'جزئیات و فیلدهای فنی اینجا قرار می‌گیرند.',
'myProducts.step.technical': 'اطلاعات فنی',
'myProducts.step.technicalHint':
'وضعیت را انتخاب کنید، یادداشت اختیاری بنویسید و فرم فنی دسته‌بندی را تکمیل کنید.',
'myProducts.optional': '(اختیاری)',
'myProducts.loading': 'در حال بارگذاری محصولات…',
'myProducts.fields.category': 'دسته‌بندی',
'myProducts.fields.selectCategory': 'انتخاب دسته‌بندی',
'myProducts.fields.searchCategory': 'جستجوی دسته‌بندی…',
'myProducts.fields.clearCategory': 'پاک کردن دسته‌بندی',
'myProducts.fields.noCategories': 'دسته‌بندی‌ای پیدا نشد',
'myProducts.fields.titleFa': 'نام (فارسی)',
'myProducts.fields.titleFaPlaceholder': 'نام محصول به فارسی',
'myProducts.fields.titleEn': 'نام (انگلیسی)',
'myProducts.fields.titleEnPlaceholder': 'نام محصول به انگلیسی',
'myProducts.fields.description': 'توضیحات',
'myProducts.fields.descriptionPlaceholder': 'توضیح کوتاه درباره محصول',
'myProducts.fields.price': 'قیمت پیشنهادی',
'myProducts.fields.priceSuggested': 'قیمت پیشنهادی شما',
'myProducts.fields.priceByExpert': 'می‌خواهم قیمت توسط کارشناس مشخص شود',
'myProducts.fields.pricePlaceholder': 'مثلاً ۱٬۵۰۰٬۰۰۰',
'myProducts.fields.priceUnit': 'واحد',
'myProducts.fields.priceUnit.IRT': 'IRT',
'myProducts.fields.priceUnit.USD': 'Dollar',
'myProducts.fields.priceUnit.EUR': 'EURO',
'myProducts.fields.priceUnit.AED': 'AED',
'myProducts.fields.location': 'موقعیت',
'myProducts.fields.country': 'کشور',
'myProducts.fields.selectCountry': 'انتخاب کشور',
'myProducts.fields.province': 'استان',
'myProducts.fields.selectProvince': 'انتخاب استان',
'myProducts.fields.city': 'شهر',
'myProducts.fields.selectCity': 'انتخاب شهر',
'myProducts.fields.searchCity': 'جستجوی شهر…',
'myProducts.fields.clearCity': 'پاک کردن شهر',
'myProducts.fields.noCities': 'شهری پیدا نشد',
'myProducts.fields.deliveryNote': 'یادداشت تحویل / دریافت',
'myProducts.fields.deliveryNotePlaceholder': 'مثلاً فقط حضوری، تحویل عصر…',
'myProducts.fields.district': 'منطقه',
'myProducts.fields.selectDistrict': 'انتخاب منطقه',
'myProducts.fields.condition': 'وضعیت',
'myProducts.fields.technicalNotes': 'توضیحات فنی',
'myProducts.fields.technicalNotesPlaceholder': 'جزئیات فنی اختیاری…',
'myProducts.condition.new': 'نو',
'myProducts.condition.stock': 'استوک',
'myProducts.condition.needs_repair': 'نیاز به تعمیر',
'myProducts.condition.scrap': 'اوراق',
'myProducts.images.thumbnail': 'تصویر شاخص',
'myProducts.images.thumbnailUpload': 'آپلود تصویر شاخص',
'myProducts.images.thumbnailHint': 'نسبت ۳:۲ افقی بهتر است',
'myProducts.images.thumbnailChange': 'تغییر تصویر شاخص',
'myProducts.images.thumbnailRemove': 'حذف تصویر شاخص',
'myProducts.images.zoom': 'بزرگنمایی',
'myProducts.images.applyCrop': 'اعمال برش',
'myProducts.images.gallery': 'گالری',
'myProducts.images.galleryAdd': 'افزودن عکس',
'myProducts.images.galleryHint': 'می‌توانید چند تصویر انتخاب کنید.',
'myProducts.images.galleryRemove': 'حذف تصویر {index}',
'myProducts.technical.select': 'انتخاب کنید…',
'myProducts.technical.needCategory': 'برای نمایش فیلدهای فنی، در مرحله ۱ دسته‌بندی را انتخاب کنید.',
'myProducts.technical.empty': 'برای این دسته‌بندی هنوز فیلد فنی تعریف نشده است.',
'myProducts.technical.categoryForm': 'فرم فنی دسته‌بندی',
'myProducts.technical.loading': 'در حال بارگذاری فیلدهای فنی…',
'myProducts.cancel': 'انصراف',
'myProducts.next': 'بعدی',
'myProducts.back': 'قبلی',
'myProducts.submit': 'ارسال',
'myProducts.save': 'ذخیره تغییرات',
'myProducts.submitting': 'در حال ارسال…',
'myProducts.submitSuccess': 'محصول برای بررسی ارسال شد.',
'myProducts.updateSuccess': 'محصول به‌روز شد.',
'myProducts.error.load': 'بارگذاری محصولات ممکن نشد.',
'myProducts.error.loadDetail': 'بارگذاری این محصول ممکن نشد.',
'myProducts.error.loadLocations': 'بارگذاری موقعیت‌ها ممکن نشد.',
'myProducts.error.loadCategories': 'بارگذاری دسته‌بندی‌ها ممکن نشد.',
'myProducts.error.loadTechnicalForm': 'بارگذاری فرم فنی دسته‌بندی ممکن نشد.',
'myProducts.error.categoryRequired': 'لطفاً دسته‌بندی را انتخاب کنید.',
'myProducts.error.titleFaRequired': 'لطفاً نام فارسی را وارد کنید.',
'myProducts.error.locationRequired': 'لطفاً کشور و شهر را انتخاب کنید.',
'myProducts.error.priceInvalid': 'لطفاً قیمت معتبر وارد کنید.',
'myProducts.error.conditionRequired': 'لطفاً وضعیت را انتخاب کنید.',
'myProducts.error.technicalRequired': 'لطفاً فیلدهای فنی الزامی را تکمیل کنید.',
'myProducts.error.submit': 'ارسال محصول ممکن نشد.',
'myProducts.error.update': 'به‌روزرسانی محصول ممکن نشد.',
'myProducts.error.remove': 'حذف محصول ممکن نشد.',
'myProducts.error.promote': 'پروموت محصول ممکن نشد.',
'storeItems.price.contact': 'برای قیمت، تماس بگیرید',
'title.signIn': 'ورود',
@@ -533,6 +793,10 @@ export function getCustomerRouteTitleRules(locale: DashboardLocale) {
{ match: '/addresses', labels: [t('nav.addresses')] },
{ match: '/orders', labels: [t('nav.orders')] },
{ match: '/favorites', labels: [t('nav.favorites')] },
{ match: '/my-products/new', labels: [t('nav.myProducts'), t('myProducts.addTitle')] },
{ match: /^\/my-products\/[^/]+\/edit$/, labels: [t('nav.myProducts'), t('myProducts.editTitle')] },
{ match: /^\/my-products\/[^/]+$/, labels: [t('nav.myProducts'), t('myProducts.detailsTitle')] },
{ match: '/my-products', labels: [t('nav.myProducts')] },
{ match: '/', labels: [t('nav.home')] },
]
}