- 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>
Fire a fire-and-forget view event when an authenticated, non-owner user
opens a product page (deduped per product), POSTing to the new
/api/engagement/v1/views/ endpoint. Silent on failure so tracking never
disrupts browsing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Click-to-select instead of geocoding on every move: pan the map freely
(drag), then tap to drop the pin — reverse geocodes that point ONCE.
Saves Map.ir reverse tokens. Shows a "tap to select" hint until a point
is chosen; a draggable-style red marker marks the selection (and the
existing point in edit mode, without re-geocoding).
- "موقعیت فعلی من" button: uses the device geolocation to fly to and
select the current position (with an error toast if denied/unsupported).
- removed the old center-pin + moveend auto-geocode and the unused
user-location-found event/pulse effect.
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>
- register MapLibre's RTL text plugin (@mapbox/mapbox-gl-rtl-text) so
Persian/Arabic map labels render correctly instead of reversed/broken.
Guarded via getRTLTextPluginStatus so it's set only once.
- reverse geocoding now prefers Map.ir's address_compact (shorter, cleaner
address) over the full address.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the broken-looking Leaflet/OSM address picker with a MapLibre GL
map using Map.ir vector tiles, plus Map.ir reverse geocoding for fast
coordinates -> Persian address.
- InteractiveMap: drop react-leaflet/leaflet; init a MapLibre map (Map.ir
style via transformRequest x-api-key), a NavigationControl, and a fixed
center pin — the map moves under the pin and the centre is reverse-
geocoded on moveend (debounced). Wrapped dir=ltr so the map renders
correctly inside the RTL page (fixes the scattered-tiles bug). Selected
address shown in an overlay chip; city select / geolocation fly the map.
- reverse geocoding: Nominatim -> Map.ir (https://map.ir/reverse), which
returns proper Persian addresses (Nominatim was weak for Iran and its
TOS forbids production use).
- default map centre fixed to Tehran (matched the "تهران" selector).
- key via VITE_MAPIR_KEY (documented in .env.example; real key in env).
- deps: add maplibre-gl, remove leaflet/react-leaflet/@types/leaflet.
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>
Per design feedback, restructure the mobile product page (desktop layout
unchanged except color swatches):
- remove the top seller bar; the seller/brand (logo + name + ارسال پیام)
now sits in a row below the gallery.
- add a floating circular back button over the image (safe-area aware).
- move the rating out of the action row into a prominent body row, shown
only when the average rating is > 0.
- color swatches are now circles with a selection ring (shared with
desktop for consistency) instead of squares.
- make the افزودن به سبد خرید button full-width/taller with a compact
price beside it.
- the action row keeps save / share / add-to-collection / try-on.
- the red discount icon is kept as-is.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restore the original red discount-icon badge on product cards per
preference; revert the ٪N pill restyle.
This reverts commit eefad53.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restyle the discount badge to the redesign's pill: ٪ + rounded Persian
digits on a translucent white rounded-full chip, no icon. Drops the raw
decimal percent (e.g. "12.7" -> "٪۱۳") and the discount-red icon import.
Applied in both the discounts rail and the for-you masonry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the mobile home scroll-tab bar with the redesign's search-first
chrome: logo + ویترون wordmark, notification (bell) and cart (bag) icon
buttons with unread dot / count badges, and a prominent tap-to-search
field that opens /explore. Mobile only (stays inside lg:hidden), so the
desktop DesktopHeader is unaffected. Keeps the existing pinar font and
color tokens; preserves the safe-area-inset top padding.
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>
The seller store page (useGetSellerProducts) inherited the global 30s
staleTime with refetchOnWindowFocus disabled, so after a seller edited a
price the store tiles could keep showing the pre-edit price while the
(SSR) single-product page already showed the new one — a list-vs-detail
mismatch. The backend serves both from live DB and is consistent.
Set staleTime: 0 + refetchOnMount/refetchOnWindowFocus on the storefront
query so it revalidates on every visit and tab refocus, keeping the list
prices current.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Single-product page showed basePrice with no discount while list tiles
showed the cheapest variant's discounted price. Cause: the detail view
defaulted selectedColor/selectedSize to the first in-stock color and the
first in-stock size independently — a combo that often isn't a real
variant, so no variant matched and it fell back to basePrice / no sale.
- ProductDetailView: derive a `defaultVariant` (cheapest in-stock variant
by final price, == the tile's min/discount price) and use it for the
initial selection, the initial displayed price, and the fallback when no
exact color/size combo is matched. A shared `pricingOf` helper applies
the same discount rule everywhere. Detail price now matches the tile.
- use-home-hooks: drop staleTime 5m -> 60s on the For You / discounts /
top-sellers feeds so price/discount edits show up promptly in lists.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The desktop header search only navigated on submit — the autocomplete
suggestions dropdown existed on mobile (explore SearchSection) but was
never wired into the desktop chrome. Reuse the same useSearchAutocomplete
hook (debounced) and render a suggestions dropdown under the search box:
product titles navigate to /search, sellers navigate to /seller/<id>.
Closes on outside-click / Escape / selection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The login-required popup still rendered a 4-slot OTP (maxLength=4, gated on
length===4) while the main login page already used 6. Users who triggered
login via the dialog saw the old 4-digit code. Match login.tsx: 6 slots,
length===6, same slot sizing.
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>
The backend view returns {"public_key": ...} but the global
CamelCaseJSONRenderer rewrites it to {"publicKey": ...} on the wire,
so destructuring `public_key` always resolved to undefined and the
subscribe path bailed silently. Browsers never registered with the
backend, no PushSubscription rows were ever created, and no push
notifications fired.
Co-Authored-By: Claude Opus 4.7 (1M context) <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.
- MyLayout: make the desktop shell a flex column with the content area
flex-1, so the footer sits flush and pins to the bottom on short pages
- DesktopFooter: drop the mt-24 that exposed the wrapper bg as a gray band
- also fixes the light-gray patches in dark mode (wrapper bg no longer shows)
- 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
Gallery (desktop):
- portrait 3/4 box, object-contain so the photo isn't cropped
- prev/next arrows on the carousel
- save (heart) overlay top-left; AI try-on button "آنلاین پرو کن" at image bottom
Info column (desktop): hide the mobile action icon row (incl. share); clean
layout — title, star rating + review count, prominent price with old/strike,
color + size, buy row (chat + add-to-cart + qty stepper), assurance cards.
Mobile keeps its existing components (ProductActions / ProductInfo / BottomBar /
ContactButton are lg:hidden).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
- ProductDetailView: gallery | info two-column grid on desktop with breadcrumb;
gallery sticky; reviews + similar products full-width below. Mobile stack
unchanged (only lg: utilities + a moved BottomBar, which is position:fixed so
its DOM location doesn't affect mobile)
- BottomBar: static inline buy card on desktop (fixed bottom bar on mobile)
- ProductInfo: larger title + tightened top spacing at lg
- MyLayout: desktop gate now includes /product/*
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>
Single responsive tree (Tailwind lg:) — mobile experience unchanged; desktop
chrome is gated to the home route for now.
- DesktopHeader / DesktopFooter components (visible only at lg+), wired to
real routes with live cart badge and working search
- MyLayout: render desktop header/footer on home, expand container and hide
mobile bottom nav at lg
- Home: hide mobile top bar at lg, 1320px centered container, desktop hero
grid (carousel + 2 promos), larger section headers
- SellerTilesGrid: 2-col mobile -> 5-col desktop with name overlay
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- sessions: require REMIX_SECRET in prod (no "secret" fallback); base Secure
cookie on NODE_ENV; add maxAge
- login: safeRedirect() blocks open-redirect via redirectTo (loader + action)
- SSR loaders: encodeURIComponent on user path params before internal API
fetch (product, store product, seller)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>