Vitron-Front/app/routes/store.$storeId.financial-dashboard._index.tsx
Arda Samadi bb44ebf0e5 fix(seller-team): guard owner-only routes + robust useMyStores
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>
2026-07-01 17:04:06 +03:30

113 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { MetaFunction } from "@remix-run/node";
import { useNavigate, useParams } from "@remix-run/react";
import { safeGoBack } from "~/utils/helpers";
import {
ArrowLeftRight,
ChartNoAxesCombined,
ChevronLeft,
HandCoins,
} from "lucide-react";
import HeaderWithSupport from "../components/HeaderWithSupport";
import { useRequireOwner } from "../hooks/useStoreOwnership";
export const meta: MetaFunction = () => {
return [
{ title: "داشبورد مالی - ویترون" },
{
name: "description",
content:
"مدیریت امور مالی فروشگاه شما در ویترون. برداشت وجه، گزارش مالی و پیگیری تراکنش‌ها.",
},
{
name: "keywords",
content: "داشبورد مالی، برداشت وجه، گزارش مالی، تراکنش، فروشگاه، ویترون",
},
{ name: "robots", content: "noindex, follow" },
{ property: "og:title", content: "داشبورد مالی - ویترون" },
{
property: "og:description",
content:
"مدیریت امور مالی فروشگاه شما در ویترون. برداشت وجه، گزارش مالی و پیگیری تراکنش‌ها.",
},
{ property: "og:type", content: "website" },
{ name: "twitter:card", content: "summary" },
{ name: "twitter:title", content: "داشبورد مالی - ویترون" },
{
name: "twitter:description",
content:
"مدیریت امور مالی فروشگاه شما در ویترون. برداشت وجه، گزارش مالی و پیگیری تراکنش‌ها.",
},
];
};
export default function FinancialDashboard() {
const { storeId } = useParams();
useRequireOwner(storeId); // financial is owner-only
const navigate = useNavigate();
const handleBack = () => {
safeGoBack(navigate);
};
const cashFunds = (link: string) => {
navigate(link);
};
const menuItems = [
{
link: `/store/${storeId}/financial-dashboard/cash-funds`,
title: "برداشت وجه",
description: "مدیریت موجودی و درخواست انتقال درآمد به حساب بانکی.",
icon: <HandCoins size={20} />,
},
{
link: `/store/${storeId}/financial-dashboard/transactions`,
title: "لیست تراکنش‌ها",
description: "مشاهده و پیگیری تمام تراکنش‌ها.",
icon: <ArrowLeftRight size={20} />,
},
{
link: `/store/${storeId}/financial-dashboard/reports`,
title: "گزارش مالی",
description: "دریافت گزارش‌های خلاصه یا تفصیلی از عملکرد مالی فروشگاه.",
icon: <ChartNoAxesCombined size={20} />,
},
];
return (
<div className="bg-WHITE">
<div className="lg:hidden">
<HeaderWithSupport title="داشبورد مالی" onBack={handleBack} />
</div>
<h2 className="hidden lg:block text-[24px] font-extrabold mb-6">
داشبورد مالی
</h2>
<div className="lg:grid lg:grid-cols-3 lg:gap-4">
{menuItems.map((item, index) => (
<div
onClick={() => {
cashFunds(item.link);
}}
role="button"
key={index}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter") {
cashFunds(item.link);
}
}}
className="border-b flex items-center w-full p-4 bg-WHITE hover:bg-WHITE3 cursor-pointer lg:border lg:border-WHITE3 lg:rounded-2xl lg:p-6 lg:items-start lg:hover:shadow-md lg:transition-shadow"
>
<div className="flex flex-col w-full gap-2">
<div className="flex flex-row w-full gap-2 lg:items-center">
<span className="lg:w-11 lg:h-11 lg:rounded-xl lg:bg-WHITE2 lg:flex lg:items-center lg:justify-center">
{item.icon}
</span>
<p className="text-R14 lg:text-B16 font-bold">{item.title}</p>
</div>
<p className="text-R12 text-GRAY lg:mt-1">{item.description}</p>
</div>
<ChevronLeft size={16} className="lg:hidden" />
</div>
))}
</div>
</div>
);
}