Flatten transparent product images to white and allow cropper unzoom.
PNG/WebP uploads no longer bake to black on JPEG; product croppers can zoom out with white padding, and SEO tools move into the product overflow menu. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b743c09e4d
commit
06e2442aaf
@@ -751,6 +751,7 @@ export function AddCustomerProductPage() {
|
||||
}
|
||||
}}
|
||||
aspect={1}
|
||||
minZoom={0.4}
|
||||
hint={t('customerProducts.form.images.thumbnailHint')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ImageCropper } from '../components/ImageCropper'
|
||||
import { ImageUploader } from '../components/ImageUploader'
|
||||
import { TagInput } from '../components/TagInput'
|
||||
import { RichTextEditor } from '../components/RichTextEditor'
|
||||
import { SeoMetaFields } from '../components/SeoMetaFields'
|
||||
import { Breadcrumbs } from '../components/Breadcrumbs'
|
||||
import { ProductFieldAiPromptModal } from '../components/ProductFieldAiPromptModal'
|
||||
import { Tooltip } from '../components/Tooltip'
|
||||
@@ -30,7 +29,6 @@ import {
|
||||
} from '../services/productAiService'
|
||||
import type { Category, FlatCategory } from '../types/category'
|
||||
import { flattenCategories } from '../utils/categories'
|
||||
import { seoMetaPayload } from '../utils/seoMeta'
|
||||
import pageStyles from '../components/PageContent.module.css'
|
||||
import aiStyles from '../styles/ai.module.css'
|
||||
import styles from './AddNewProductPage.module.css'
|
||||
@@ -54,8 +52,6 @@ export function AddNewProductPage() {
|
||||
const [thumbnail, setThumbnail] = useState<string | null>(null)
|
||||
const [images, setImages] = useState<string[]>([])
|
||||
const [tags, setTags] = useState<string[]>([])
|
||||
const [seoMetaTitle, setSeoMetaTitle] = useState('')
|
||||
const [seoMetaDescription, setSeoMetaDescription] = useState('')
|
||||
const [thumbnailMediaId, setThumbnailMediaId] = useState<string | null>(null)
|
||||
const [galleryMediaIds, setGalleryMediaIds] = useState<string[]>([])
|
||||
const [loaded, setLoaded] = useState(!isEdit)
|
||||
@@ -175,8 +171,6 @@ export function AddNewProductPage() {
|
||||
setThumbnail(form.thumbnail)
|
||||
setImages(form.images)
|
||||
setTags(form.tags)
|
||||
setSeoMetaTitle(form.seoMetaTitle)
|
||||
setSeoMetaDescription(form.seoMetaDescription)
|
||||
setThumbnailMediaId(form.thumbnailMediaId)
|
||||
setGalleryMediaIds(form.galleryMediaIds)
|
||||
setLoaded(true)
|
||||
@@ -225,7 +219,6 @@ export function AddNewProductPage() {
|
||||
featuredMediaId: featuredMediaId ?? undefined,
|
||||
galleryMediaIds: nextGalleryMediaIds,
|
||||
tags,
|
||||
...seoMetaPayload(seoMetaTitle, seoMetaDescription),
|
||||
status: 'published' as const,
|
||||
}
|
||||
|
||||
@@ -293,7 +286,7 @@ export function AddNewProductPage() {
|
||||
<div className={styles.formGrid}>
|
||||
<div className={`${styles.field} ${styles.col2} ${styles.thumbnailField}`}>
|
||||
<label>{t('products.form.thumbnail')}</label>
|
||||
<ImageCropper value={thumbnail} onChange={setThumbnail} />
|
||||
<ImageCropper value={thumbnail} onChange={setThumbnail} minZoom={0.4} />
|
||||
</div>
|
||||
|
||||
<div className={styles.col10}>
|
||||
@@ -445,19 +438,6 @@ export function AddNewProductPage() {
|
||||
placeholder={t('products.form.tagsPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SeoMetaFields
|
||||
seoMetaTitle={seoMetaTitle}
|
||||
seoMetaDescription={seoMetaDescription}
|
||||
onTitleChange={setSeoMetaTitle}
|
||||
onDescriptionChange={setSeoMetaDescription}
|
||||
disabled={isSubmitting}
|
||||
hint={t('seo.meta.hint')}
|
||||
titleLabel={t('seo.meta.title')}
|
||||
descriptionLabel={t('seo.meta.description')}
|
||||
titlePlaceholder={t('seo.meta.titlePlaceholder')}
|
||||
descriptionPlaceholder={t('seo.meta.descriptionPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
|
||||
@@ -8,6 +8,8 @@ import { Pagination } from '../components/Pagination'
|
||||
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
|
||||
import { ProductVariantsModal } from '../components/ProductVariantsModal'
|
||||
import { ProductTechnicalInfoModal } from '../components/ProductTechnicalInfoModal'
|
||||
import { ProductCommentsModal } from '../components/ProductCommentsModal'
|
||||
import { ProductSeoModal } from '../components/ProductSeoModal'
|
||||
import { TranslationModal } from '../components/TranslationModal'
|
||||
import { TranslateAllLanguageModal } from '../components/TranslateAllLanguageModal'
|
||||
import { Breadcrumbs } from '../components/Breadcrumbs'
|
||||
@@ -52,12 +54,15 @@ export function MyProductsPage() {
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [variantCounts, setVariantCounts] = useState<Record<string, number>>({})
|
||||
const [commentCounts, setCommentCounts] = useState<Record<string, number>>({})
|
||||
const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string } | null>(null)
|
||||
const [variantsTarget, setVariantsTarget] = useState<{
|
||||
id: string
|
||||
name: string
|
||||
categoryId: string
|
||||
} | null>(null)
|
||||
const [commentsTarget, setCommentsTarget] = useState<{ id: string; name: string } | null>(null)
|
||||
const [seoTarget, setSeoTarget] = useState<{ id: string; name: string } | null>(null)
|
||||
const [technicalTarget, setTechnicalTarget] = useState<{
|
||||
id: string
|
||||
name: string
|
||||
@@ -205,6 +210,24 @@ export function MyProductsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleComments(id: string) {
|
||||
const product = products.find((p) => p.id === id)
|
||||
if (product) {
|
||||
setCommentsTarget({ id, name: product.nameFa || product.nameEn })
|
||||
}
|
||||
}
|
||||
|
||||
function handleSeo(id: string) {
|
||||
const product = products.find((p) => p.id === id)
|
||||
if (product) {
|
||||
setSeoTarget({ id, name: product.nameFa || product.nameEn })
|
||||
}
|
||||
}
|
||||
|
||||
function handleQuickInfo(id: string) {
|
||||
navigate(`/products/detail/${id}`)
|
||||
}
|
||||
|
||||
function handleVariations(id: string) {
|
||||
const product = products.find((p) => p.id === id)
|
||||
if (product) {
|
||||
@@ -273,6 +296,14 @@ export function MyProductsPage() {
|
||||
writeListQuery('', 1)
|
||||
}
|
||||
|
||||
function handleCommentCountChange(count: number) {
|
||||
if (!commentsTarget) return
|
||||
setCommentCounts((prev) => ({
|
||||
...prev,
|
||||
[commentsTarget.id]: count,
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<main className={pageStyles.content}>
|
||||
<Breadcrumbs
|
||||
@@ -356,9 +387,13 @@ export function MyProductsPage() {
|
||||
<ProductCard
|
||||
key={product.id}
|
||||
product={product}
|
||||
commentCount={commentCounts[product.id] ?? product.commentCount}
|
||||
variantCount={variantCounts[product.id] ?? product.variantCount ?? 0}
|
||||
onEdit={handleEdit}
|
||||
onTranslate={handleTranslate}
|
||||
onQuickInfo={handleQuickInfo}
|
||||
onComments={handleComments}
|
||||
onSeo={handleSeo}
|
||||
onTechnicalInfo={handleTechnicalInfo}
|
||||
onVariations={handleVariations}
|
||||
onRemove={handleRemoveRequest}
|
||||
@@ -414,6 +449,25 @@ export function MyProductsPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{commentsTarget && (
|
||||
<ProductCommentsModal
|
||||
open={!!commentsTarget}
|
||||
productId={commentsTarget.id}
|
||||
productName={commentsTarget.name}
|
||||
onClose={() => setCommentsTarget(null)}
|
||||
onCountChange={handleCommentCountChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{seoTarget && (
|
||||
<ProductSeoModal
|
||||
open={!!seoTarget}
|
||||
productId={seoTarget.id}
|
||||
productName={seoTarget.name}
|
||||
onClose={() => setSeoTarget(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{technicalTarget && (
|
||||
<ProductTechnicalInfoModal
|
||||
open={!!technicalTarget}
|
||||
|
||||
Reference in New Issue
Block a user