Vitron-Front/app/hooks/useStoreOwnership.ts
fazli 6a300b8760 fix(store): don't bounce owners home, stop cross-user myStores cache
- 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>
2026-07-18 13:18:23 +00:00

72 lines
2.2 KiB
TypeScript

import { useEffect } from "react";
import { useNavigate } from "@remix-run/react";
import { useMyStores } from "../requestHandler/use-seller-hooks";
/**
* Guards a store dashboard route. A user may enter a store they OWN or are an
* active STAFF member of. Non-members are redirected away. Returns the user's
* role for this store so callers can gate owner-only UI.
*/
export function useStoreOwnership(storeId: string | undefined, mode?: string) {
const navigate = useNavigate();
const { data: stores, isLoading, isFetching, isError } = useMyStores();
const current = stores?.find(
(s) => s.username?.toLowerCase() === storeId?.toLowerCase()
);
useEffect(() => {
if (isLoading || isFetching || isError || mode === "create") return;
if (!storeId) {
navigate("/");
return;
}
if (!stores || stores.length === 0) return; // nothing to compare against yet
if (!current) {
// The user is a member of at least one OTHER store — send them there.
// If they're not a member of any store, do NOT bounce to home: staying
// put lets an owner see their (empty) dashboard rather than being
// silently kicked out on a transient/mismatched my-stores response.
navigate(`/store/${stores[0].username}`);
}
}, [
stores,
current,
storeId,
isLoading,
isFetching,
isError,
navigate,
mode,
]);
const ownedOrFirst = stores?.find((s) => s.role === "owner") ?? stores?.[0];
return {
isLoading,
isMember: !!current,
role: current?.role ?? null,
isOwner: current?.role === "owner",
userStoreId: ownedOrFirst?.username,
stores: stores ?? [],
};
}
/**
* Guard for OWNER-ONLY store pages (financial, shipping, edit, team). Redirects
* non-members (via useStoreOwnership) and staff members back to the dashboard.
* Returns { isLoading, isOwner } so callers can render a spinner meanwhile.
*/
export function useRequireOwner(storeId: string | undefined) {
const navigate = useNavigate();
const { isLoading, role } = useStoreOwnership(storeId);
useEffect(() => {
if (!isLoading && role && role !== "owner") {
navigate(`/store/${storeId}`);
}
}, [isLoading, role, storeId, navigate]);
return { isLoading, isOwner: role === "owner" };
}