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>
/discounts was missing from MyLayout's isDesktopReady allowlist, so on desktop it
stayed in the narrow mobile column with no desktop header/footer. Add it so it
gets the full-width desktop canvas; the page's lg:max-w container + Mondrian
4-column masonry then render like /sellers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On iOS, apple-mobile-web-app-status-bar-style: black-translucent has no effect
unless apple-mobile-web-app-capable=yes is also set. Without it the installed
PWA never went edge-to-edge, so env(safe-area-inset-top) resolved to 0 and the
header padding did nothing — content ran under the notch. Android was unaffected
(it honors mobile-web-app-capable, which was already present).
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>
Mobile profile page already has this action, but the desktop chrome had
no way for a seller or staff member to jump into a store they manage —
they'd have to type the URL. Add a compact "فروشگاه من" pill in the
header action row, gated on useMyStores() so it only renders for users
who actually manage a store. Selection mirrors the mobile flow: owned
first, then the first store they staff.
my-stores, members list/invite/remove all mount under /api/seller/v1/ in the
Django backend, so my-stores and every team-management fetch was 404'ing. That
made useMyStores throw silently, useStoreOwnership fall back to role=null, and
the setting page's `!ownerOnly || role === "owner"` filter drop every
owner-only tile (edit-store, team, financial-dashboard, shipping-method,
instagram-sync). Owners could log into their own store and see none of the
owner-gated menu items.
Co-Authored-By: Claude Opus 4.7 <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>
Seller dashboard threads (type="store") now fetch the store's inbox via
useGetStoreThreads(storeId) -> /threads/list/?store_id=; the profile threads page
(type="user") keeps the personal buyer inbox. Depends on the seller-team backend
(store-aware threads + store_id-by-username), so it ships on this branch, not main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The homepage seller tiles now show just the shop image on all breakpoints.
The desktop-only bottom gradient + name label was making some brand images
harder to read; the shop click target is unchanged and username is still
available on hover-tap navigation to /seller/<username>.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
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