mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Add store items pagination and fix modal backdrop blur across dashboards.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
4d480799b0
commit
e6670ed06f
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.25);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,6 +10,19 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: var(--modal-overlay-bg);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
@@ -135,14 +147,6 @@
|
||||
box-shadow: 0 6px 16px rgba(var(--primary-rgb) / 0.35);
|
||||
}
|
||||
|
||||
.overlayIn {
|
||||
animation: overlayFadeIn 0.22s ease forwards;
|
||||
}
|
||||
|
||||
.overlayOut {
|
||||
animation: overlayFadeOut 0.22s ease forwards;
|
||||
}
|
||||
|
||||
.modalIn {
|
||||
animation: modalFadeIn 0.25s ease forwards;
|
||||
}
|
||||
@@ -150,17 +154,6 @@
|
||||
.modalOut {
|
||||
animation: modalFadeOut 0.22s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes overlayFadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes overlayFadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes modalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.25);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,6 +10,19 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: var(--modal-overlay-bg);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
@@ -147,14 +159,6 @@
|
||||
box-shadow: 0 6px 16px rgba(var(--primary-rgb) / 0.35);
|
||||
}
|
||||
|
||||
.overlayIn {
|
||||
animation: overlayFadeIn 0.22s ease forwards;
|
||||
}
|
||||
|
||||
.overlayOut {
|
||||
animation: overlayFadeOut 0.22s ease forwards;
|
||||
}
|
||||
|
||||
.modalIn {
|
||||
animation: modalFadeIn 0.25s ease forwards;
|
||||
}
|
||||
@@ -162,17 +166,6 @@
|
||||
.modalOut {
|
||||
animation: modalFadeOut 0.22s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes overlayFadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes overlayFadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes modalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.28);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,6 +10,19 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: var(--modal-overlay-bg);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -174,13 +186,8 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.overlayIn { animation: overlayFadeIn 0.22s ease forwards; }
|
||||
.overlayOut { animation: overlayFadeOut 0.22s ease forwards; }
|
||||
.modalIn { animation: modalFadeIn 0.25s ease forwards; }
|
||||
.modalOut { animation: modalFadeOut 0.22s ease forwards; }
|
||||
|
||||
@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(127, 29, 29, 0.2);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,6 +10,19 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: rgba(127, 29, 29, 0.28);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -105,13 +117,8 @@
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.overlayIn { animation: overlayFadeIn 0.22s ease forwards; }
|
||||
.overlayOut { animation: overlayFadeOut 0.22s ease forwards; }
|
||||
.modalIn { animation: modalFadeIn 0.25s ease forwards; }
|
||||
.modalOut { animation: modalFadeOut 0.22s ease forwards; }
|
||||
|
||||
@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
isolation: isolate;
|
||||
z-index: 300;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -8,18 +9,20 @@
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 24px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: rgba(15, 23, 42, 0.82);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
.overlayIn {
|
||||
animation: fadeIn 0.2s ease forwards;
|
||||
}
|
||||
|
||||
.overlayOut {
|
||||
animation: fadeOut 0.2s ease forwards;
|
||||
}
|
||||
|
||||
.closeBtn {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.25);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,8 +10,18 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.overlayIn { animation: overlayFadeIn 0.22s ease forwards; }
|
||||
.overlayOut { animation: overlayFadeOut 0.22s ease forwards; }
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: var(--modal-overlay-bg);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
@@ -206,9 +215,6 @@
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.35);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,13 +10,23 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: var(--modal-overlay-bg);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.overlayNested {
|
||||
z-index: 1010;
|
||||
}
|
||||
|
||||
.overlayIn { animation: overlayFadeIn 0.22s ease forwards; }
|
||||
.overlayOut { animation: overlayFadeOut 0.22s ease forwards; }
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
@@ -420,9 +429,6 @@
|
||||
color: #ef4444;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
|
||||
@@ -393,6 +393,8 @@ const en = {
|
||||
'storeItems.loading': 'Loading store items...',
|
||||
'storeItems.empty': 'No store items yet. Use the + button to add products from your catalog.',
|
||||
'storeItems.emptyFiltered': 'No store items match your filters.',
|
||||
'storeItems.pagination':
|
||||
'Page {page} / {totalPages} · {perPage} per page · {total} total',
|
||||
'storeItems.errorLoad': 'Unable to load store items.',
|
||||
'storeItems.errorRemove': 'Unable to remove store item.',
|
||||
'storeItems.add': 'Add store items',
|
||||
@@ -1490,6 +1492,8 @@ const fa: Record<MessageKey, string> = {
|
||||
'storeItems.loading': 'در حال بارگذاری اقلام فروشگاه...',
|
||||
'storeItems.empty': 'هنوز قلمی در فروشگاه نیست. با دکمه + از کاتالوگ اضافه کنید.',
|
||||
'storeItems.emptyFiltered': 'هیچ قلمی با فیلترهای شما مطابقت ندارد.',
|
||||
'storeItems.pagination':
|
||||
'صفحه {page} / {totalPages} · {perPage} در هر صفحه · {total} مجموع',
|
||||
'storeItems.errorLoad': 'بارگذاری اقلام فروشگاه ممکن نشد.',
|
||||
'storeItems.errorRemove': 'حذف قلم از فروشگاه ممکن نشد.',
|
||||
'storeItems.add': 'افزودن اقلام فروشگاه',
|
||||
@@ -1504,7 +1508,7 @@ const fa: Record<MessageKey, string> = {
|
||||
'storeItems.card.festival': 'جشنواره',
|
||||
'storeItems.card.variantOne': '۱ تنوع',
|
||||
'storeItems.card.variantMany': '{count} تنوع',
|
||||
'storeItems.card.edit': 'ویرایش اقلام فروشگاه',
|
||||
'storeItems.card.edit': 'ویرایش محصول فروشگاه',
|
||||
'storeItems.card.discount': 'تنظیم تخفیف',
|
||||
'storeItems.card.festivalPoints': 'امتیاز جشنواره',
|
||||
'storeItems.card.remove': 'حذف از فروشگاه',
|
||||
@@ -1543,7 +1547,7 @@ const fa: Record<MessageKey, string> = {
|
||||
'storeItems.create.errorVariations': 'بارگذاری تنوعهای محصول ممکن نشد.',
|
||||
'storeItems.create.errorCreate': 'ایجاد اقلام فروشگاه ممکن نشد.',
|
||||
'storeItems.price.contact': 'برای قیمت، تماس بگیرید',
|
||||
'storeItems.edit.title': 'ویرایش اقلام فروشگاه',
|
||||
'storeItems.edit.title': 'ویرایش محصول فروشگاه',
|
||||
'storeItems.edit.save': 'ذخیره تغییرات',
|
||||
'storeItems.edit.saving': 'در حال ذخیره…',
|
||||
'storeItems.edit.errorSave': 'ذخیره اقلام فروشگاه ممکن نشد.',
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
--glass-border: rgba(255, 255, 255, 0.7);
|
||||
--glass-shadow: 0 8px 32px rgba(31, 38, 135, 0.08);
|
||||
--blur-glass: 28px;
|
||||
--modal-overlay-bg: rgba(15, 23, 42, 0.4);
|
||||
--modal-overlay-blur: 12px;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #64748b;
|
||||
--text-muted: #94a3b8;
|
||||
|
||||
@@ -24,6 +24,39 @@
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
padding: 0 4px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
min-height: 38px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.paginationMeta {
|
||||
grid-column: 1;
|
||||
justify-self: start;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 38px;
|
||||
font-family: var(--font-ui);
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
.pagination > nav {
|
||||
grid-column: 2;
|
||||
justify-self: center;
|
||||
height: 38px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fabDock {
|
||||
position: fixed;
|
||||
inset-inline-end: 32px;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { Plus, RotateCcw, Search } from 'lucide-react'
|
||||
import { Breadcrumbs } from '../components/Breadcrumbs'
|
||||
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
|
||||
import { CreateStoreItemsModal } from '../components/CreateStoreItemsModal'
|
||||
import { EditStoreItemsModal } from '../components/EditStoreItemsModal'
|
||||
import { Pagination } from '../components/Pagination'
|
||||
import { PickStoreVariantModal } from '../components/PickStoreVariantModal'
|
||||
import { ShoppingCartModal } from '../components/ShoppingCartModal'
|
||||
import { StoreItemCard } from '../components/StoreItemCard'
|
||||
@@ -15,7 +17,8 @@ import { useToast } from '../context/ToastContext'
|
||||
import { ApiError } from '../lib/api'
|
||||
import {
|
||||
deleteStoreItemsByProduct,
|
||||
listStoreItems,
|
||||
listAllStoreItems,
|
||||
STORE_ITEMS_PER_PAGE,
|
||||
type StoreItem,
|
||||
} from '../services/storeItemService'
|
||||
import type { ShoppingCard } from '../services/shoppingCardService'
|
||||
@@ -37,6 +40,11 @@ import filterStyles from '../components/ListFiltersPanel.module.css'
|
||||
import pageStyles from '../components/PageContent.module.css'
|
||||
import styles from './StoreItemsPage.module.css'
|
||||
|
||||
function readPage(searchParams: URLSearchParams) {
|
||||
const pageRaw = Number(searchParams.get('page'))
|
||||
return Number.isFinite(pageRaw) && pageRaw >= 1 ? Math.floor(pageRaw) : 1
|
||||
}
|
||||
|
||||
export function StoreItemsPage() {
|
||||
return <StoreItemsPageContent />
|
||||
}
|
||||
@@ -44,6 +52,8 @@ export function StoreItemsPage() {
|
||||
function StoreItemsPageContent() {
|
||||
const t = useT()
|
||||
const { locale } = useLocale()
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const currentPage = readPage(searchParams)
|
||||
const { itemCount, hasItems, addVariant, loadShoppingCard } = useDraftCart()
|
||||
const { showToast } = useToast()
|
||||
const [items, setItems] = useState<StoreItem[]>([])
|
||||
@@ -97,6 +107,20 @@ function StoreItemsPageContent() {
|
||||
[listings, appliedFilters],
|
||||
)
|
||||
|
||||
const totalListings = filteredListings.length
|
||||
const totalPages = Math.max(1, Math.ceil(totalListings / STORE_ITEMS_PER_PAGE))
|
||||
const safePage = Math.min(currentPage, totalPages)
|
||||
const pagedListings = useMemo(() => {
|
||||
const start = (safePage - 1) * STORE_ITEMS_PER_PAGE
|
||||
return filteredListings.slice(start, start + STORE_ITEMS_PER_PAGE)
|
||||
}, [filteredListings, safePage])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && currentPage > totalPages) {
|
||||
writePage(totalPages)
|
||||
}
|
||||
}, [isLoading, currentPage, totalPages])
|
||||
|
||||
const productItems = useMemo(() => {
|
||||
const target = editTarget ?? discountTarget ?? festivalTarget
|
||||
if (!target) return []
|
||||
@@ -108,6 +132,13 @@ function StoreItemsPageContent() {
|
||||
[listings],
|
||||
)
|
||||
|
||||
function writePage(page: number) {
|
||||
const next = new URLSearchParams(searchParams)
|
||||
if (page > 1) next.set('page', String(page))
|
||||
else next.delete('page')
|
||||
setSearchParams(next, { replace: true })
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
setAppliedFilters({
|
||||
name: draftName.trim(),
|
||||
@@ -115,6 +146,7 @@ function StoreItemsPageContent() {
|
||||
maxPrice: parseIrtInput(draftMaxPrice),
|
||||
onlyDiscounted: draftOnlyDiscounted,
|
||||
})
|
||||
writePage(1)
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
@@ -123,6 +155,12 @@ function StoreItemsPageContent() {
|
||||
setDraftMaxPrice('')
|
||||
setDraftOnlyDiscounted(false)
|
||||
setAppliedFilters(EMPTY_STORE_LISTING_FILTERS)
|
||||
writePage(1)
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
writePage(page)
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -136,8 +174,8 @@ function StoreItemsPageContent() {
|
||||
setError('')
|
||||
|
||||
try {
|
||||
const data = await listStoreItems(1, 50, signal)
|
||||
setItems(data.items)
|
||||
const data = await listAllStoreItems(signal)
|
||||
setItems(data)
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException && err.name === 'AbortError') return
|
||||
if (err instanceof ApiError) {
|
||||
@@ -215,6 +253,12 @@ function StoreItemsPageContent() {
|
||||
await deleteStoreItemsByProduct(deleteTarget.productId)
|
||||
setItems((prev) => prev.filter((item) => item.productId !== deleteTarget.productId))
|
||||
setDeleteTarget(null)
|
||||
|
||||
const nextTotal = Math.max(0, totalListings - 1)
|
||||
const nextTotalPages = Math.max(1, Math.ceil(nextTotal / STORE_ITEMS_PER_PAGE))
|
||||
if (safePage > nextTotalPages) {
|
||||
writePage(nextTotalPages)
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError) {
|
||||
setError(err.message)
|
||||
@@ -329,8 +373,9 @@ function StoreItemsPageContent() {
|
||||
) : filteredListings.length === 0 ? (
|
||||
<p className={styles.empty}>{t('storeItems.emptyFiltered')}</p>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.grid}>
|
||||
{filteredListings.map((listing) => (
|
||||
{pagedListings.map((listing) => (
|
||||
<StoreItemCard
|
||||
key={listing.productId}
|
||||
listing={listing}
|
||||
@@ -343,6 +388,25 @@ function StoreItemsPageContent() {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.pagination}>
|
||||
<div className={styles.paginationMeta}>
|
||||
{t('storeItems.pagination', {
|
||||
page: safePage,
|
||||
totalPages,
|
||||
perPage: STORE_ITEMS_PER_PAGE,
|
||||
total: totalListings,
|
||||
})}
|
||||
</div>
|
||||
<Pagination
|
||||
currentPage={safePage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={handlePageChange}
|
||||
disabled={isLoading}
|
||||
variant="inline"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={styles.fabDock}>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { apiRequest } from '../lib/api'
|
||||
import { getActiveBusinessId } from '../lib/businessContext'
|
||||
|
||||
/** Product cards per page on My Store Items (grid). */
|
||||
export const STORE_ITEMS_PER_PAGE = 24
|
||||
|
||||
export interface StoreItemSelection {
|
||||
variationId: string
|
||||
variationName: string
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.25);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,13 +10,23 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: var(--modal-overlay-bg);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.overlayNested {
|
||||
z-index: 210;
|
||||
}
|
||||
|
||||
.overlayIn { animation: overlayFadeIn 0.22s ease forwards; }
|
||||
.overlayOut { animation: overlayFadeOut 0.22s ease forwards; }
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
@@ -412,9 +421,6 @@
|
||||
color: #ef4444;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(127, 29, 29, 0.28);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 300;
|
||||
z-index: 200;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: rgba(127, 29, 29, 0.28);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -105,13 +117,8 @@
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.overlayIn { animation: overlayFadeIn 0.22s ease forwards; }
|
||||
.overlayOut { animation: overlayFadeOut 0.22s ease forwards; }
|
||||
.modalIn { animation: modalFadeIn 0.25s ease forwards; }
|
||||
.modalOut { animation: modalFadeOut 0.22s ease forwards; }
|
||||
|
||||
@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.32);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,6 +10,19 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: var(--modal-overlay-bg);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
--radius: 16px;
|
||||
--radius-sm: 12px;
|
||||
--blur-glass: 28px;
|
||||
--modal-overlay-bg: rgba(15, 23, 42, 0.4);
|
||||
--modal-overlay-blur: 12px;
|
||||
--select-arrow-size: 16px;
|
||||
--select-arrow-offset: 12px;
|
||||
--select-padding-end: 2.5rem;
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
--glass-border: rgba(255, 255, 255, 0.7);
|
||||
--glass-shadow: 0 8px 32px rgba(31, 38, 135, 0.08);
|
||||
--blur-glass: 28px;
|
||||
--modal-overlay-bg: rgba(15, 23, 42, 0.4);
|
||||
--modal-overlay-blur: 12px;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #64748b;
|
||||
--text-muted: #94a3b8;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.25);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
isolation: isolate;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -11,6 +10,19 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */
|
||||
.overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
background: var(--modal-overlay-bg);
|
||||
backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
-webkit-backdrop-filter: blur(var(--modal-overlay-blur));
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -245,13 +257,8 @@
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.overlayIn { animation: overlayFadeIn 0.22s ease forwards; }
|
||||
.overlayOut { animation: overlayFadeOut 0.22s ease forwards; }
|
||||
.modalIn { animation: modalFadeIn 0.25s ease forwards; }
|
||||
.modalOut { animation: modalFadeOut 0.22s ease forwards; }
|
||||
|
||||
@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
|
||||
Reference in New Issue
Block a user