Feature gates and the resource cap are enforced against the environment the user is standing in, but /subscription/my only ever returned the plan of the environment they own. A doctor working as a guest in another clinic consumed the host clinic's resource quota while the panel showed their own plan's cap, so the quota number and the menu locks disagreed with what the server would allow. /subscription/my now also returns context_plan — limits and features of the acting environment, without the other environment's plan identity. effective_plan, subscription and used_trial stay on the owned environment so the purchase flow is unchanged, and useSubscription reads its caps and hasFeature from context_plan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
47 lines
2.5 KiB
TypeScript
47 lines
2.5 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { api } from '../lib/api';
|
|
import type { ApiResponse } from '../lib/api';
|
|
import { useAuthStore } from '../stores/authStore';
|
|
import type { MySubscriptionData } from '../types';
|
|
|
|
export function useSubscription() {
|
|
const primaryRole = useAuthStore((s) => s.primaryRole);
|
|
const dbUuid = useAuthStore((s) => s.dbUuid);
|
|
const enabled = primaryRole === 'doctor' || primaryRole === 'clinic' || primaryRole === 'secretary';
|
|
|
|
// کلید شامل محیط فعال است: اشتراک روی محیط مینشیند، نه روی کاربر. پزشکی که هم
|
|
// مطب شخصی دارد و هم کلینیک، با کلیدِ بدون محیط پلنِ محیط قبلی را میدید و هر دو
|
|
// محیط ارتقایافته بهنظر میرسیدند.
|
|
const { data } = useQuery<ApiResponse<MySubscriptionData>>({
|
|
queryKey: ['subscription-my', dbUuid],
|
|
queryFn: () => api.get('/api/v1/subscription/my'),
|
|
enabled,
|
|
staleTime: 2 * 60 * 1000,
|
|
});
|
|
|
|
const sub = data?.data?.subscription ?? null;
|
|
const effectivePlan = data?.data?.effective_plan ?? sub?.plan ?? null;
|
|
// سقفها و قابلیتها از پلنِ محیطِ فعال میآیند، نه از پلنِ محیطِ مالکیت: سرور هم با
|
|
// همین محیط میسنجد. پزشکِ مهمانِ یک کلینیک، منابعش را از سهمیهٔ کلینیک میزبان
|
|
// برمیدارد ولی سقفِ پلنِ خودش نمایش داده میشد و عددِ سهمیه بیمعنی بود.
|
|
const gatePlan = data?.data?.context_plan ?? effectivePlan;
|
|
const features: Record<string, boolean> = gatePlan?.features ?? {};
|
|
const maxSecretaries: number = gatePlan?.max_secretaries ?? 1;
|
|
// `-1` یعنی بینهایت.
|
|
const maxResources: number = gatePlan?.max_resources ?? 1;
|
|
// نقشهایی که اشتراک ندارند (ادمین) و لحظهٔ پیش از رسیدن پاسخ: سقف ناشناخته است و
|
|
// نباید با پیشفرضِ ۱ بهجای کاربر تصمیم گرفت — گیتکردن کارِ سرور است.
|
|
const planLoaded = gatePlan !== null;
|
|
const hasPlan = sub !== null;
|
|
|
|
return {
|
|
subscription: sub,
|
|
hasFeature: (key: string) => features[key] ?? false,
|
|
maxSecretaries,
|
|
maxResources,
|
|
planLoaded,
|
|
hasPlan,
|
|
isExpiringSoon: (sub?.days_remaining ?? 0) > 0 && (sub?.days_remaining ?? 0) <= 7,
|
|
};
|
|
}
|