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>
- 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>
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>