- 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/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>
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>
- 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>
- 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>
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>
The crop canvas was sized to cropArea.width/height, which are the editor's
on-screen (CSS) pixels — a few hundred px. Phone photos were therefore
downscaled to that size before upload, so cropped product images came out
blurry (most visibly on iPhone, whose source photos are large).
Size the canvas to the crop region in the source image's native pixels
(adjustedCropWidth/Height, already computed) and draw 1:1. Bumped JPEG
quality 0.9 -> 0.92. The backend still caps the final image to 1600px webp,
so output size stays bounded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>