Add used-product flag for store listings.
Merchants can mark items as used on create/edit, see a used badge on cards, and filter the store list for used products. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
32f0dbab63
commit
61d145d92e
@@ -155,6 +155,31 @@
|
|||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usedCheckLabel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 4px 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usedCheck {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: var(--primary);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usedCheck:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
.compactInput:disabled {
|
.compactInput:disabled {
|
||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export function CreateStoreItemsModal({
|
|||||||
const [closing, setClosing] = useState(false)
|
const [closing, setClosing] = useState(false)
|
||||||
const [products, setProducts] = useState<{ id: string; title: string; nameFa: string }[]>([])
|
const [products, setProducts] = useState<{ id: string; title: string; nameFa: string }[]>([])
|
||||||
const [productId, setProductId] = useState('')
|
const [productId, setProductId] = useState('')
|
||||||
|
const [isUsed, setIsUsed] = useState(false)
|
||||||
const [variations, setVariations] = useState<ProductVariationSelection[]>([])
|
const [variations, setVariations] = useState<ProductVariationSelection[]>([])
|
||||||
const [rows, setRows] = useState<StoreItemDraftRow[]>([])
|
const [rows, setRows] = useState<StoreItemDraftRow[]>([])
|
||||||
const [isLoadingProducts, setIsLoadingProducts] = useState(false)
|
const [isLoadingProducts, setIsLoadingProducts] = useState(false)
|
||||||
@@ -118,6 +119,7 @@ export function CreateStoreItemsModal({
|
|||||||
setMounted(false)
|
setMounted(false)
|
||||||
setClosing(false)
|
setClosing(false)
|
||||||
setProductId('')
|
setProductId('')
|
||||||
|
setIsUsed(false)
|
||||||
setVariations([])
|
setVariations([])
|
||||||
setRows([])
|
setRows([])
|
||||||
setError('')
|
setError('')
|
||||||
@@ -281,6 +283,7 @@ export function CreateStoreItemsModal({
|
|||||||
try {
|
try {
|
||||||
await batchCreateStoreItems({
|
await batchCreateStoreItems({
|
||||||
productId,
|
productId,
|
||||||
|
isUsed,
|
||||||
items: buildPayload(),
|
items: buildPayload(),
|
||||||
})
|
})
|
||||||
onCreated?.()
|
onCreated?.()
|
||||||
@@ -421,6 +424,17 @@ export function CreateStoreItemsModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label className={rowStyles.usedCheckLabel}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className={rowStyles.usedCheck}
|
||||||
|
checked={isUsed}
|
||||||
|
onChange={(e) => setIsUsed(e.target.checked)}
|
||||||
|
disabled={busy}
|
||||||
|
/>
|
||||||
|
<span>{t('storeItems.create.isUsed')}</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
{productId && isLoadingVariations && (
|
{productId && isLoadingVariations && (
|
||||||
<p className={modalStyles.emptyText}>{t('storeItems.create.loadingVariations')}</p>
|
<p className={modalStyles.emptyText}>{t('storeItems.create.loadingVariations')}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export function EditStoreItemsModal({
|
|||||||
const [variations, setVariations] = useState<ProductVariationSelection[]>([])
|
const [variations, setVariations] = useState<ProductVariationSelection[]>([])
|
||||||
const [rows, setRows] = useState<StoreItemDraftRow[]>([])
|
const [rows, setRows] = useState<StoreItemDraftRow[]>([])
|
||||||
const [removedIds, setRemovedIds] = useState<string[]>([])
|
const [removedIds, setRemovedIds] = useState<string[]>([])
|
||||||
|
const [isUsed, setIsUsed] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
@@ -123,6 +124,7 @@ export function EditStoreItemsModal({
|
|||||||
setClosing(false)
|
setClosing(false)
|
||||||
setRemovedIds([])
|
setRemovedIds([])
|
||||||
setRows(itemsToRows(items))
|
setRows(itemsToRows(items))
|
||||||
|
setIsUsed(items.some((item) => item.isUsed))
|
||||||
setError('')
|
setError('')
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
void loadVariations(productId, controller.signal)
|
void loadVariations(productId, controller.signal)
|
||||||
@@ -223,6 +225,7 @@ export function EditStoreItemsModal({
|
|||||||
try {
|
try {
|
||||||
const data = await syncProductStoreItems({
|
const data = await syncProductStoreItems({
|
||||||
productId,
|
productId,
|
||||||
|
isUsed,
|
||||||
items: rows.map((row) => ({
|
items: rows.map((row) => ({
|
||||||
id: row.storeItemId,
|
id: row.storeItemId,
|
||||||
selections: formVariations.flatMap((variation) => {
|
selections: formVariations.flatMap((variation) => {
|
||||||
@@ -294,6 +297,17 @@ export function EditStoreItemsModal({
|
|||||||
<p className={modalStyles.emptyText}>{t('storeItems.create.loadingVariations')}</p>
|
<p className={modalStyles.emptyText}>{t('storeItems.create.loadingVariations')}</p>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
<label className={rowStyles.usedCheckLabel}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className={rowStyles.usedCheck}
|
||||||
|
checked={isUsed}
|
||||||
|
onChange={(e) => setIsUsed(e.target.checked)}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
<span>{t('storeItems.create.isUsed')}</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={rowStyles.itemRowHeader}
|
className={rowStyles.itemRowHeader}
|
||||||
style={{ gridTemplateColumns: gridTemplate }}
|
style={{ gridTemplateColumns: gridTemplate }}
|
||||||
|
|||||||
@@ -64,7 +64,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.festivalBadge,
|
.festivalBadge,
|
||||||
.stockBadge {
|
.stockBadge,
|
||||||
|
.usedBadge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
@@ -86,6 +87,14 @@
|
|||||||
color: #7c3aed;
|
color: #7c3aed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usedBadge {
|
||||||
|
inset-inline-start: 8px;
|
||||||
|
top: auto;
|
||||||
|
bottom: 8px;
|
||||||
|
color: #b45309;
|
||||||
|
background: rgba(255, 251, 235, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
.stockBadge {
|
.stockBadge {
|
||||||
inset-inline-end: 8px;
|
inset-inline-end: 8px;
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ export function StoreItemCard({
|
|||||||
{listing.showFestival && (
|
{listing.showFestival && (
|
||||||
<span className={styles.festivalBadge}>{t('storeItems.card.festival')}</span>
|
<span className={styles.festivalBadge}>{t('storeItems.card.festival')}</span>
|
||||||
)}
|
)}
|
||||||
|
{listing.isUsed && (
|
||||||
|
<span className={styles.usedBadge}>{t('storeItems.card.used')}</span>
|
||||||
|
)}
|
||||||
{listing.productTotalStock > 0 && (
|
{listing.productTotalStock > 0 && (
|
||||||
<span className={styles.stockBadge}>
|
<span className={styles.stockBadge}>
|
||||||
{t('storeItems.card.inStock', { count: listing.productTotalStock })}
|
{t('storeItems.card.inStock', { count: listing.productTotalStock })}
|
||||||
|
|||||||
@@ -895,6 +895,7 @@ const en = {
|
|||||||
'storeItems.minPrice': 'Min price (IRT)',
|
'storeItems.minPrice': 'Min price (IRT)',
|
||||||
'storeItems.maxPrice': 'Max price (IRT)',
|
'storeItems.maxPrice': 'Max price (IRT)',
|
||||||
'storeItems.onlyDiscounted': 'Only discounted',
|
'storeItems.onlyDiscounted': 'Only discounted',
|
||||||
|
'storeItems.onlyUsed': 'Only used products',
|
||||||
'storeItems.search': 'Search',
|
'storeItems.search': 'Search',
|
||||||
'storeItems.clearFilters': 'Clear filters',
|
'storeItems.clearFilters': 'Clear filters',
|
||||||
'storeItems.excel.export': 'Export Excel',
|
'storeItems.excel.export': 'Export Excel',
|
||||||
@@ -923,6 +924,7 @@ const en = {
|
|||||||
'Remove "{name}" and all {variants} from your store? This cannot be undone.',
|
'Remove "{name}" and all {variants} from your store? This cannot be undone.',
|
||||||
'storeItems.card.inStock': '{count} in stock',
|
'storeItems.card.inStock': '{count} in stock',
|
||||||
'storeItems.card.festival': 'Festival',
|
'storeItems.card.festival': 'Festival',
|
||||||
|
'storeItems.card.used': 'Used',
|
||||||
'storeItems.card.variantOne': '1 variant',
|
'storeItems.card.variantOne': '1 variant',
|
||||||
'storeItems.card.variantMany': '{count} variants',
|
'storeItems.card.variantMany': '{count} variants',
|
||||||
'storeItems.card.edit': 'Edit store items',
|
'storeItems.card.edit': 'Edit store items',
|
||||||
@@ -935,6 +937,7 @@ const en = {
|
|||||||
'storeItems.create.title': 'Add Store Items',
|
'storeItems.create.title': 'Add Store Items',
|
||||||
'storeItems.create.subtitle': "Create sellable variants from a product's variations.",
|
'storeItems.create.subtitle': "Create sellable variants from a product's variations.",
|
||||||
'storeItems.create.productPlaceholder': 'Product — type 3+ characters to search',
|
'storeItems.create.productPlaceholder': 'Product — type 3+ characters to search',
|
||||||
|
'storeItems.create.isUsed': 'This is a used product',
|
||||||
'storeItems.create.loadingVariations': 'Loading product variations...',
|
'storeItems.create.loadingVariations': 'Loading product variations...',
|
||||||
'storeItems.create.price': 'Price (IRT)',
|
'storeItems.create.price': 'Price (IRT)',
|
||||||
'storeItems.create.stock': 'Stock',
|
'storeItems.create.stock': 'Stock',
|
||||||
@@ -3482,6 +3485,7 @@ const fa: Record<MessageKey, string> = {
|
|||||||
'storeItems.minPrice': 'حداقل قیمت (IRT)',
|
'storeItems.minPrice': 'حداقل قیمت (IRT)',
|
||||||
'storeItems.maxPrice': 'حداکثر قیمت (IRT)',
|
'storeItems.maxPrice': 'حداکثر قیمت (IRT)',
|
||||||
'storeItems.onlyDiscounted': 'فقط تخفیفدار',
|
'storeItems.onlyDiscounted': 'فقط تخفیفدار',
|
||||||
|
'storeItems.onlyUsed': 'فقط کالای دستدوم',
|
||||||
'storeItems.search': 'جستجو',
|
'storeItems.search': 'جستجو',
|
||||||
'storeItems.clearFilters': 'پاک کردن فیلترها',
|
'storeItems.clearFilters': 'پاک کردن فیلترها',
|
||||||
'storeItems.excel.export': 'خروجی اکسل',
|
'storeItems.excel.export': 'خروجی اکسل',
|
||||||
@@ -3510,6 +3514,7 @@ const fa: Record<MessageKey, string> = {
|
|||||||
'«{name}» و همه {variants} از فروشگاه حذف شوند؟ این عمل قابل بازگشت نیست.',
|
'«{name}» و همه {variants} از فروشگاه حذف شوند؟ این عمل قابل بازگشت نیست.',
|
||||||
'storeItems.card.inStock': '{count} موجود',
|
'storeItems.card.inStock': '{count} موجود',
|
||||||
'storeItems.card.festival': 'جشنواره',
|
'storeItems.card.festival': 'جشنواره',
|
||||||
|
'storeItems.card.used': 'دستدوم',
|
||||||
'storeItems.card.variantOne': '۱ تنوع',
|
'storeItems.card.variantOne': '۱ تنوع',
|
||||||
'storeItems.card.variantMany': '{count} تنوع',
|
'storeItems.card.variantMany': '{count} تنوع',
|
||||||
'storeItems.card.edit': 'ویرایش محصول فروشگاه',
|
'storeItems.card.edit': 'ویرایش محصول فروشگاه',
|
||||||
@@ -3522,6 +3527,7 @@ const fa: Record<MessageKey, string> = {
|
|||||||
'storeItems.create.title': 'افزودن محصول به فروشگاه',
|
'storeItems.create.title': 'افزودن محصول به فروشگاه',
|
||||||
'storeItems.create.subtitle': 'تنوعهای قابل فروش را از تنوعهای یک محصول بسازید.',
|
'storeItems.create.subtitle': 'تنوعهای قابل فروش را از تنوعهای یک محصول بسازید.',
|
||||||
'storeItems.create.productPlaceholder': 'محصول — حداقل ۳ کاراکتر برای جستجو',
|
'storeItems.create.productPlaceholder': 'محصول — حداقل ۳ کاراکتر برای جستجو',
|
||||||
|
'storeItems.create.isUsed': 'این محصول دستدوم است',
|
||||||
'storeItems.create.loadingVariations': 'در حال بارگذاری تنوعهای محصول...',
|
'storeItems.create.loadingVariations': 'در حال بارگذاری تنوعهای محصول...',
|
||||||
'storeItems.create.price': 'قیمت (IRT)',
|
'storeItems.create.price': 'قیمت (IRT)',
|
||||||
'storeItems.create.stock': 'موجودی',
|
'storeItems.create.stock': 'موجودی',
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ function StoreItemsPageContent() {
|
|||||||
const [draftMinPrice, setDraftMinPrice] = useState('')
|
const [draftMinPrice, setDraftMinPrice] = useState('')
|
||||||
const [draftMaxPrice, setDraftMaxPrice] = useState('')
|
const [draftMaxPrice, setDraftMaxPrice] = useState('')
|
||||||
const [draftOnlyDiscounted, setDraftOnlyDiscounted] = useState(false)
|
const [draftOnlyDiscounted, setDraftOnlyDiscounted] = useState(false)
|
||||||
|
const [draftOnlyUsed, setDraftOnlyUsed] = useState(false)
|
||||||
const [appliedFilters, setAppliedFilters] = useState<StoreListingFilters>(
|
const [appliedFilters, setAppliedFilters] = useState<StoreListingFilters>(
|
||||||
EMPTY_STORE_LISTING_FILTERS,
|
EMPTY_STORE_LISTING_FILTERS,
|
||||||
)
|
)
|
||||||
@@ -156,6 +157,7 @@ function StoreItemsPageContent() {
|
|||||||
minPrice: parseIrtInput(draftMinPrice),
|
minPrice: parseIrtInput(draftMinPrice),
|
||||||
maxPrice: parseIrtInput(draftMaxPrice),
|
maxPrice: parseIrtInput(draftMaxPrice),
|
||||||
onlyDiscounted: draftOnlyDiscounted,
|
onlyDiscounted: draftOnlyDiscounted,
|
||||||
|
onlyUsed: draftOnlyUsed,
|
||||||
})
|
})
|
||||||
writePage(1)
|
writePage(1)
|
||||||
}
|
}
|
||||||
@@ -165,6 +167,7 @@ function StoreItemsPageContent() {
|
|||||||
setDraftMinPrice('')
|
setDraftMinPrice('')
|
||||||
setDraftMaxPrice('')
|
setDraftMaxPrice('')
|
||||||
setDraftOnlyDiscounted(false)
|
setDraftOnlyDiscounted(false)
|
||||||
|
setDraftOnlyUsed(false)
|
||||||
setAppliedFilters(EMPTY_STORE_LISTING_FILTERS)
|
setAppliedFilters(EMPTY_STORE_LISTING_FILTERS)
|
||||||
writePage(1)
|
writePage(1)
|
||||||
}
|
}
|
||||||
@@ -415,6 +418,22 @@ function StoreItemsPageContent() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={`${filterStyles.switchField} ${filterStyles.fieldCol3}`}>
|
||||||
|
<div className={filterStyles.switchInline}>
|
||||||
|
<ToggleSwitch
|
||||||
|
checked={draftOnlyUsed}
|
||||||
|
onChange={setDraftOnlyUsed}
|
||||||
|
disabled={isLoading}
|
||||||
|
ariaLabel={t('storeItems.onlyUsed')}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={filterStyles.switchLabel}
|
||||||
|
onClick={() => !isLoading && setDraftOnlyUsed(!draftOnlyUsed)}
|
||||||
|
>
|
||||||
|
{t('storeItems.onlyUsed')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={filterStyles.filterActions}>
|
<div className={filterStyles.filterActions}>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface StoreItem {
|
|||||||
stockQuantity: number | null
|
stockQuantity: number | null
|
||||||
rewardPoints: number | null
|
rewardPoints: number | null
|
||||||
isFestival: boolean
|
isFestival: boolean
|
||||||
|
isUsed: boolean
|
||||||
sortOrder: number
|
sortOrder: number
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
@@ -48,6 +49,7 @@ export interface CreateStoreItemPayload {
|
|||||||
|
|
||||||
export interface BatchCreateStoreItemsPayload {
|
export interface BatchCreateStoreItemsPayload {
|
||||||
productId: string
|
productId: string
|
||||||
|
isUsed?: boolean
|
||||||
items: CreateStoreItemPayload[]
|
items: CreateStoreItemPayload[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ export interface SyncStoreItemRowPayload {
|
|||||||
|
|
||||||
export interface SyncProductStoreItemsPayload {
|
export interface SyncProductStoreItemsPayload {
|
||||||
productId: string
|
productId: string
|
||||||
|
isUsed?: boolean
|
||||||
items: SyncStoreItemRowPayload[]
|
items: SyncStoreItemRowPayload[]
|
||||||
removedIds: string[]
|
removedIds: string[]
|
||||||
}
|
}
|
||||||
@@ -95,7 +98,7 @@ export async function listStoreItems(
|
|||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
filters?: { name?: string },
|
filters?: { name?: string; isUsed?: boolean },
|
||||||
) {
|
) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
page: String(page),
|
page: String(page),
|
||||||
@@ -104,6 +107,9 @@ export async function listStoreItems(
|
|||||||
if (filters?.name?.trim()) {
|
if (filters?.name?.trim()) {
|
||||||
params.set('name', filters.name.trim())
|
params.set('name', filters.name.trim())
|
||||||
}
|
}
|
||||||
|
if (filters?.isUsed) {
|
||||||
|
params.set('isUsed', 'true')
|
||||||
|
}
|
||||||
|
|
||||||
return apiRequest<StoreItemsListResponse>(
|
return apiRequest<StoreItemsListResponse>(
|
||||||
`${businessPath()}?${params.toString()}`,
|
`${businessPath()}?${params.toString()}`,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface StoreSpecialStoreItem {
|
|||||||
productNameFa: string
|
productNameFa: string
|
||||||
productImage: string | null
|
productImage: string | null
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
isUsed?: boolean
|
||||||
sortOrder: number
|
sortOrder: number
|
||||||
variants: StoreSpecialVariant[]
|
variants: StoreSpecialVariant[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export interface StoreListingFilters {
|
|||||||
minPrice: number | null
|
minPrice: number | null
|
||||||
maxPrice: number | null
|
maxPrice: number | null
|
||||||
onlyDiscounted: boolean
|
onlyDiscounted: boolean
|
||||||
|
onlyUsed: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EMPTY_STORE_LISTING_FILTERS: StoreListingFilters = {
|
export const EMPTY_STORE_LISTING_FILTERS: StoreListingFilters = {
|
||||||
@@ -13,6 +14,7 @@ export const EMPTY_STORE_LISTING_FILTERS: StoreListingFilters = {
|
|||||||
minPrice: null,
|
minPrice: null,
|
||||||
maxPrice: null,
|
maxPrice: null,
|
||||||
onlyDiscounted: false,
|
onlyDiscounted: false,
|
||||||
|
onlyUsed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
function listingEffectivePrice(listing: StoreProductListing): number | null {
|
function listingEffectivePrice(listing: StoreProductListing): number | null {
|
||||||
@@ -50,6 +52,10 @@ export function filterStoreListings(
|
|||||||
if (!hasDiscount) return false
|
if (!hasDiscount) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filters.onlyUsed && !listing.isUsed) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface StoreProductListing {
|
|||||||
displayPrice: number | null
|
displayPrice: number | null
|
||||||
displayDiscountedPrice: number | null
|
displayDiscountedPrice: number | null
|
||||||
showFestival: boolean
|
showFestival: boolean
|
||||||
|
isUsed: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function effectivePrice(item: StoreItem): number | null {
|
function effectivePrice(item: StoreItem): number | null {
|
||||||
@@ -103,6 +104,7 @@ export function groupStoreItemsByProduct(
|
|||||||
showFestival: sorted.some(
|
showFestival: sorted.some(
|
||||||
(variant) => variant.isFestival || (variant.rewardPoints ?? 0) > 0,
|
(variant) => variant.isFestival || (variant.rewardPoints ?? 0) > 0,
|
||||||
),
|
),
|
||||||
|
isUsed: sorted.some((variant) => variant.isUsed),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ function specialStoreItemToVariants(item: StoreSpecialStoreItem): StoreItem[] {
|
|||||||
stockQuantity: variant.stockQuantity,
|
stockQuantity: variant.stockQuantity,
|
||||||
rewardPoints: variant.rewardPoints,
|
rewardPoints: variant.rewardPoints,
|
||||||
isFestival: variant.isFestival,
|
isFestival: variant.isFestival,
|
||||||
|
isUsed: item.isUsed ?? false,
|
||||||
sortOrder: variant.sortOrder,
|
sortOrder: variant.sortOrder,
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
updatedAt: '',
|
updatedAt: '',
|
||||||
@@ -55,6 +56,7 @@ function listingFromStoreItemWithoutVariants(
|
|||||||
stockQuantity: null,
|
stockQuantity: null,
|
||||||
rewardPoints: null,
|
rewardPoints: null,
|
||||||
isFestival: false,
|
isFestival: false,
|
||||||
|
isUsed: item.isUsed ?? false,
|
||||||
sortOrder: item.sortOrder,
|
sortOrder: item.sortOrder,
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
updatedAt: '',
|
updatedAt: '',
|
||||||
@@ -72,6 +74,7 @@ function listingFromStoreItemWithoutVariants(
|
|||||||
displayPrice: null,
|
displayPrice: null,
|
||||||
displayDiscountedPrice: null,
|
displayDiscountedPrice: null,
|
||||||
showFestival: false,
|
showFestival: false,
|
||||||
|
isUsed: item.isUsed ?? false,
|
||||||
storeItemId: item.id,
|
storeItemId: item.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user