- useAuditLog hook + AuditLogRow type.
- new /admin/audit route: action/target filters, search, and a compact
before→after diff of the touched fields per entry.
- sidebar "گزارش فعالیت" nav link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approve (complete) / reject buttons on pending payouts, hitting the new admin
action endpoint (which debits the balance + journals on approve). Toasts + list
and overview invalidation on success.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ledger section with sub-tabs: overview (money held/owed, payouts by status, and
a reconciliation warning card), payouts + transactions lists, per-seller balances
list, and a per-seller detail showing recorded vs derived balance + discrepancy,
bank accounts and payout history. Enabled the Ledger nav item.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /admin opted out of shopper chrome in MyLayout; /admin added to server
protectedRoutes; useRequireAdmin() gates to superusers (UserProfile.isSuperuser).
- Admin shell (admin.tsx) with sidebar nav (Overview live; other sections stubbed)
and a mobile top bar; Overview dashboard (KPI cards + 14-day orders chart) via
use-admin-hooks.ts hitting /api/adminpanel/v1/overview/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- app/components/store/ColorSelector.tsx: renders as a 9-column grid sorted
by info.sortOrder. Compact aspect-square swatches; Persian name lives in
title/aria-label so 81 swatches breathe on mobile. Checkmark ink flips by
swatch luminance so it stays visible on both سفید and مشکی.
- app/components/store/SizeSelector.tsx + ProductVariantDrawer.tsx: dropped
the "بدون مشخص کردن سایز" toggle now that "FreeSize" exists in the
palette. Every product must pick a size (or FreeSize) on save.
- app/routes/store.add.manual.$storeId.tsx:
- two-step category picker (parent → sub) driven by category.parentId,
with edit-mode pre-fill deriving the parent from the sub's parentId.
- getFinalImageUrls now uploads in parallel with concurrency 3, retries
each image once, persists successful uploads to state as they land so
a resubmit only retries the still-failing ones. If every upload fails
we refuse to submit rather than sending an empty gallery.
- only ship discountPrice when it's a REAL discount (finalNum > 0 and
< priceNum) — otherwise pass null so the backend clears any stale
value. Closes the "phantom discount" hole where PricingForm's
"no discount" radio still shipped the previous typed number.
- isStep1Valid requires every variant to have a real (non-"unselected")
size, matching the new mandatory-size rule.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- app/hooks/useStoreOwnership.ts: the old redirect logic sent owners to /
the moment useMyStores briefly resolved to []. That empty array came from
useMyStores's dead `if (!token) return []` inside the queryFn, so any
transient auth blip left the store page thinking the user has no stores.
Now compares usernames case-insensitively, only redirects while the query
is settled (!isLoading && !isFetching && !isError), and never bounces to
home on empty — worst case the seller sees an empty dashboard rather than
a silent kick-out.
- app/requestHandler/use-seller-hooks.ts: removed the unreachable
`if (!token) return []` in useMyStores and added the token to the
queryKey so a login switch stops serving the previous user's cached list.
- app/routes/store.$storeId._index.tsx: mount StorePushPromptCard at the
top of the store dashboard.
- app/routes/store.$storeId.tsx: sidebar "پیامها" badge reads
badges.sellerChat[storeId] (per-store) instead of the buyer-side
badges.chat, matching the badge-serializer split.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- app/components/MyLayout.tsx / app/components/home/HomePageTopBar.tsx /
app/routes/profile._index.tsx: the BadgeCounts type was declared with
snake_case fields (order_updates, seller_orders) while the API's global
camelCase renderer sends orderUpdates / sellerOrders on the wire — every
read went to undefined, so the "buyer activity" dot on the profile icon
and the order-badge on the store dashboard were silently stuck at 0.
Fixed by renaming the interface to match the wire and adding sellerChat
(per-store roll-up) so the store dashboard sidebar reads
badges.sellerChat[storeId] instead of the buyer-side chat count.
- app/hooks/useBadgeCounts.ts: interface + EMPTY constant updated to
camelCase + new sellerChat map.
- app/utils/api-error-handler.ts: 403 no longer force-redirects to /logout.
It used to force-log-out on ANY 403/401, so an approved-but-unbadged
owner (Ehsan / Noelabel) whose store dashboard fired the Instagram-tab
query got a 403 → forced logout → the store page vanished mid-load
and the seller landed on the home screen convinced they'd been kicked
off their own store. Only 401 signs the session out now.
- app/components/store/StorePushPromptCard.tsx: new dashboard-level card
that nudges the store owner/staff to enable browser push. Unlike the
global buyer banner it's not dismissible-forever; "بعداً یادآوری کن"
hides for 24h and it comes back until the user actually subscribes.
Handles the permission="denied" path with an in-browser-settings hint.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two more iOS-notch overlaps:
- Seller محصولات page: top container now carries pt-[env(safe-area-inset-top)]
on mobile (lg:pt-0) so the heading drops below the notch.
- Owner single-product view: the carousel's ⋮/back buttons sat at top-3 (under
the notch); offset them by calc(env(safe-area-inset-top,0px)+12px), matching
the buyer back button. Only the owner buttons (product detail, top of screen)
use this — grid tiles don't pass isOwner.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MyLayout only pads the bottom inset globally; each top bar must add its own
top inset. Several inline page headers omitted it, so their content slid
under the notch in the iOS standalone PWA (home was fine — its bar has it).
Wrap the back-button headers the same way ProfilePagesHeader already does
(outer sticky element carries pt-[env(safe-area-inset-top)]; inner relative
bar holds the content) so the absolute back arrow stays out of the notch.
Fixed: /profile hub, /discounts, /collections, /collection/:id, /sellers,
seller store index, and financial transactions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sellers fill a size grid (rows = sizes, columns = measurements) in the
product create/edit wizard; buyers see it via a راهنمای سایز dialog.
- SizeChartSection editor: rows seeded from the product's sizes, columns
seeded from category-aware defaults, category-specific quick-add chips
(tops→دور سینه/سرشانه/آستین, pants→دور کمر/دور باسن/فاق, shoes→سایز اروپایی,
bags→طول/عرض/ارتفاع, …), custom columns, cm/inch toggle, scrollable grid
- required-if-the-product-has-sizes gate on publish (with per-size hint);
optional otherwise so accessories/one-size and imports aren't blocked
- buyer SizeGuideDialog + راهنمای سایز link in the size selector header
- SizeChart types on ProductDetail / FullProductCreate / FullProductUpdate
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pencil hint was hover-only, so on phones the price read as plain text.
Style the display as an editable field (subtle border + grey fill) with an
always-visible pencil, and drop the negative margins that fought the border.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sellers can tap a price on the products page to edit it inline (Enter to
save / Escape to cancel), mirroring the existing stock quick-edit.
- new reusable InlinePriceEditor (optimistic display, Persian/Latin digit
input, thousands grouping, loading + toast feedback, a11y labels)
- base price editable on each product row (mobile + desktop column)
- per-variant price editable beside stock in the expanded panel; empty
clears the override and inherits the base price ("پیشفرض: <base>")
- useUpdateProductBasePrice + useUpdateVariantPrice hooks; extend generated
API client + types for the two new endpoints
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- HeaderWithSupport is now lg:hidden (all its callers are desktop-full-width
store/profile pages), removing the stray mobile back+support bar on desktop.
- Constrain + title the previously mobile-only pages so their content doesn't
stretch edge-to-edge on desktop: store edit/create form (StoreForm), add
product, shipping method, financial sub-pages (cash-funds/reports/
transactions), order detail, instagram auth; and profile add/edit address,
set detail, and payment result. Forms use a narrow centered column, lists/
details a wider one. Transactions keeps its filter button (header restyled,
not hidden).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These pages are desktop-enabled (they get the DesktopHeader via the /profile
allowlist) but had no desktop layout, so their content stretched edge-to-edge on
wide screens. Add a centered max-width container + a desktop title to the main
profile sub-pages (bookmarks, following, orders, settings, wallet, addresses,
sets). Grids use a wider max-width; single-column lists a narrower one.
ProfilePagesHeader is already lg:hidden, so the mobile header still disappears.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render a فروشگاهها row (store cards with logo via SellerLogo, linking to the
store) and a کالکشنها row (reusing HorizontalCollectionsList) above the product
grid on the search page. Sellers/collections read off the first page (they don't
paginate); products paginate as before. Types extended (SellerSearchResult.logo,
SearchResponse.collections). Degrades gracefully on older backends.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a complete /discounts page (MondrianProductList grid, infinite scroll via
useGetHomeDiscounts) and link the home page "تخفیفها" section to it with a
«مشاهده همه» link, mirroring the برترین فروشگاهها → /sellers pattern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review fixes:
- useMyStores throws on a failed response instead of resolving to [] — a
transient 401/blip no longer looks like "no stores" and bounces an owner off
their own dashboard (query stays in error/retry, keeps prior data).
- Add useRequireOwner() guard and apply it to the owner-only routes
(financial-dashboard + its cash-funds/reports/transactions sub-routes and
shipping-method) so staff who hit those URLs directly are redirected to the
dashboard. (Financial data was already backend-protected; this is the matching
client-side gate.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
- Banner editor in store edit: color swatches + custom color picker, or
image/GIF upload with a 1600x500 dimension hint. Renders the chosen banner
(image cover / solid color) on the public seller page, falling back to the
default gradient.
- Sellers list: drop the desktop name overlay (match mobile, image-only tiles).
- Seller page mobile header: chat moved top-left under the banner, search+filter
beside the follow button, followers/product counts removed (rating kept),
compacted so products fill more of the screen, circular logo, thinner action row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bottom-nav profile badge sums unread chat + order updates, but the
profile page gave no breakdown. Wire useBadgeCounts into the mobile
account hub: the پیامها (messages) menu item shows the chat unread count,
and the سفارشها quick action shows the order-updates count. Adds a badge
slot to the menu item content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework the mobile profile to match the design: an avatar + name + phone
header with an edit button, a wallet gradient card (live balance from
useServiceGetWallet + افزایش موجودی → /profile/wallet), a 4-up quick-action
grid (orders / bookmarks / following[badge] / addresses), a «حساب من»
section label, the theme toggle, and a trimmed menu list (items surfaced
above are dropped from the list). Desktop profile (sidebar + welcome
panel) is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CartItem: compact row — image + title + رنگ/سایز attribute chips
(circular color dot) + a bottom row with the stepper and the effective
price plus struck original. Drops the verbose label:value rows and the
product-code line. Shared with the desktop detail column (verified).
- cart list: add a green «ارسال جداگانه» badge per seller-cart, matching
the mockup's per-seller grouping.
Keeps the existing list -> detail checkout flow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bring the seller page mobile header in line with the design / desktop:
replace the plain top bar + centered logo with a cover banner, a floating
circular back button (safe-area aware), an overlapping rounded logo, the
store name + verified badge + @handle, a clean borderless stats row
(محصول / دنبالکننده / امتیاز), the description, and follow + message-icon
actions. Search bar and product masonry unchanged. Desktop untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bring the home product tile to search and explore: seller brand chip
(SellerChip) plus price/discount overlay, instead of a bare image.
- search: map discountPercent/discountPrice/basePrice + sellerName from
ProductSearchResult (sellerName is already returned, so the chip shows
now with the initial-letter fallback).
- explore: map sellerName/sellerLogo/sellerUsername (and add them to the
Product type); these populate once the backend ProductSerializer change
ships.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show the seller's brand on the home product cards, like the design:
a small logo circle + name overlaid above the title.
- new SellerChip component: renders nothing without a name; falls back to
the seller's initial when no logo is present (e.g. before the backend
ships seller_logo). seller_name is already returned today, so the name
shows immediately.
- thread sellerName/sellerLogo/sellerUsername through ProductHomepage type,
the home discounts + for-you mappings, and the MondrianProductList /
HorizontalProductList card interfaces + overlays.
Only renders where seller data is mapped (home), so seller-page and
store-dashboard tiles are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Most browsers (everything but Safari/WebKit) can't decode HEIC in <img> or
canvas, so picking an iPhone HEIC photo broke the preview and the crop editor,
and could reach the backend in a format it couldn't process. Convert HEIC/HEIF
to JPEG right after selection via heic2any (dynamically imported, browser-only).
- new util app/utils/convert-heic.ts: isHeic() detection (MIME or extension
when iOS reports no type) + convertHeicToJpeg()/convertHeicFiles(); never
throws — falls back to the original file on failure.
- wired into the product image picker (store.add.manual) and the store
logo picker (StoreForm).
- adds heic2any dependency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Content was drawing under the iOS status bar and home indicator because
the app uses viewport-fit=cover + black-translucent but the fixed/sticky
chrome didn't reserve the safe areas.
- bottom nav (MyLayout): move the 50px row into an inner box and let the
bar extend by env(safe-area-inset-bottom) so icons sit above the home
indicator (previously the fixed height clipped the inset padding).
- top headers gain env(safe-area-inset-top): HomePageTopBar, ExploreTopBar,
SearchTopBar, CartHeader, ChatHeader, and wrapper-padded HeaderWithSupport
/ ProfilePagesHeader (so their absolutely-positioned buttons shift too).
- chat input pads bottom by the inset; chat scroll area height accounts for
the taller header/input.
- _index.tsx no longer overrides the viewport meta (it was dropping
viewport-fit=cover on the home page); the global one in root.tsx wins.
env(safe-area-inset-*) is 0 on non-notch devices, so desktop/Android render
identically — only iOS gains the padding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surfaces three events without a full notification center:
- service worker (public/sw.js) gains push + notificationclick handlers
(RTL, focuses an existing tab or opens the target URL).
- usePushNotifications hook: permission + VAPID subscribe/unsubscribe
against the backend push endpoints.
- opt-in UI both ways: a one-time dismissible post-login banner
(PushPermissionBanner, rendered in MyLayout) and a permanent toggle
(PushSettingToggle) on buyer profile settings and seller store settings.
- useBadgeCounts polls /api/notif/v1/badges/; small red badges on the
bottom-nav profile icon (chat + order updates), the profile sidebar
(پیامها / سفارشها) and the seller dashboard sidebar. Visiting orders
clears its badge.
Degrades gracefully: if the backend endpoints are absent the counts fall
back to zero and nothing throws.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Redesign mobile-style seller dashboard pages for desktop (lg:) while
leaving the mobile view unchanged:
- products: table-style header row (product/price/variant/actions) with a
bordered card wrapper and an "add product" button; accordion rows align
to the columns, inline price hidden on desktop.
- orders: pill-style status tabs + search on one row, orders shown as a
two-column card grid instead of stretched full-width rows.
- financial-dashboard: three-column card grid with icon chips.
- setting: status/theme as side-by-side cards, menu items as a
three-column card grid (logout styled in red).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- new store.$storeId.tsx layout: sticky admin sidebar (store/products/orders/
financial/shipping/threads/settings) beside content at lg; mobile unchanged
- MyLayout: store/* gets a full-width desktop canvas (own sidebar), no shopper
header/footer, mobile store-nav hidden at lg
- store home: hide the redundant mobile store header at lg
- ProductImageCarousel: prev/next arrows now opt-in (showArrows) so they only
appear on the product gallery, not every masonry card
The account sub-pages were already protected via validateTokens; add a loader
on the profile index so the hub itself bounces to /login when unauthenticated.
- header shows "ورود / ثبت نام" when logged out (no profile/cart/bookmark icons,
so the profile tab isn't reachable) and the avatar + cart + bookmarks when
logged in
- add the light/dark SunMoonToggle to the desktop header
- about/faq: hide their mobile HeaderWithSupport at lg (content already
width-constrained)
profile/location + profile/wallet "add" buttons were fixed to the viewport
bottom; make them static/inline at lg so they sit at the end of the content.
- new profile.tsx layout: sticky desktop sidebar (user card + menu) wrapping all
/profile/* pages; mobile renders page content only (unchanged)
- ProfilePagesHeader hidden at lg (one change covers all account sub-pages +
cart); profile index shows a welcome on desktop (menu lives in the sidebar)
- sellers: 5-col brand grid with name overlay, contained, mobile header hidden
- MyLayout: desktop gate now covers /profile/* and /sellers
- cart checkout (/cart/$cartId): steps on the left, sticky order summary on the
right at lg (1320px container); CartSummary becomes an inline card on desktop
(fixed bottom bar on mobile)
- profile (/profile): contained account layout; menu becomes a 2-col card grid
on desktop with a heading
- MyLayout: desktop gate now includes /cart/* and /profile
- explore: hide mobile search bar at lg (global header has search); contain
content to 1320px; category grid 2-col mobile -> 4-col desktop with taller tiles
- MyLayout: desktop gate now includes /explore
- product: show seller (logo + name) in the desktop info column (was missing
after hiding the mobile header); action icons (save/share/collection/AI) are
now circular bordered buttons on desktop instead of a bare icon row
- collection detail: cover constrained to a 360px portrait column with
object-cover (fixes over-long image + side margins); tighter spacing
- masonry: only render the loading skeleton while actively fetching (removes
the persistent grey block before the footer); sentinel moved above it
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- seller: only the logo overlaps the cover now; name/handle/stats sit below the
cover on white (fixes low-contrast name on the gradient)
- collections (home rail, /collections grid, /collection cover): portrait 3/4
images so full-body figures aren't cropped; detail cover uses object-contain
- MondrianProductList: add IntersectionObserver sentinel for reliable
infinite scroll on desktop (window-scroll math missed under the footer)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- seller: desktop brand header matching the design — cover, overlapping rounded
logo, name + verified badge + @handle, horizontal stats, Follow/Message
actions, description; mobile blocks hidden at lg; feed = responsive masonry
- /collections: 4-column card grid on desktop (was a stretched single column)
- /collection/<id>: two-column hero (cover + info) on desktop, contained width,
mobile header hidden at lg; products use the responsive masonry
- MyLayout: desktop gate now includes /collections and /collection/*
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- OTP input now 6 digits (matches backend 6-digit codes): maxLength 6, six
slots with responsive widths, verify enabled at length === 6
- login page: centered desktop card on a neutral canvas with brand mark
- MyLayout: auth pages (/login, /logout) get full-width desktop canvas and no
mobile bottom nav at lg (no shopping header/footer)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- HorizontalCollectionsList: 4-column grid at lg (overrides mobile fixed-width
cards), hide scroll arrows on desktop
- seller page: contained desktop layout, hide mobile back-header at lg, show
store name; feed uses the responsive masonry
- MyLayout: desktop chrome gate now includes /seller/*
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>