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>
113 lines
4.2 KiB
TypeScript
113 lines
4.2 KiB
TypeScript
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>
|
||
);
|
||
}
|