feat(subscription): implement feature gating for subscription-based access in admin panel

This commit is contained in:
hamed
2026-06-15 12:00:36 +03:30
parent 3d33cf09b8
commit fa6656df48
11 changed files with 534 additions and 26 deletions
+10 -1
View File
@@ -14,6 +14,7 @@ import PriceInput from '../components/ui/PriceInput';
import ConfirmDialog from '../components/ui/ConfirmDialog';
import PageHeader from '../components/ui/PageHeader';
import SearchableSelect from '../components/ui/SearchableSelect';
import FeatureGate from '../components/ui/FeatureGate';
const sectionSchema = z.object({ name: z.string().min(1, 'نام بخش الزامی است') });
const itemSchema = z.object({
@@ -27,7 +28,7 @@ type ItemForm = z.infer<typeof itemSchema>;
const EMPTY_SECTIONS: ServiceSection[] = [];
const EMPTY_ITEMS: ServiceItem[] = [];
export default function ClinicServicesPage() {
function ClinicServicesPageInner() {
const qc = useQueryClient();
const [selectedSection, setSelectedSection] = useState<ServiceSection | null>(null);
@@ -409,3 +410,11 @@ export default function ClinicServicesPage() {
</>
);
}
export default function ClinicServicesPage() {
return (
<FeatureGate feature="services">
<ClinicServicesPageInner />
</FeatureGate>
);
}
+10 -1
View File
@@ -17,6 +17,7 @@ import Pagination from '../components/ui/Pagination';
import Modal from '../components/ui/Modal';
import PageHeader from '../components/ui/PageHeader';
import SearchableSelect from '../components/ui/SearchableSelect';
import FeatureGate from '../components/ui/FeatureGate';
const sessionSchema = z.object({
visit_price_rials: z.coerce.number().min(0),
@@ -44,7 +45,7 @@ function calcFinalPrice(visitPrice: number, baseDiscount: number, suppDiscount:
return Math.round(afterSupp) + servicesTotal;
}
export default function MyPatientsPage() {
function MyPatientsPageInner() {
const qc = useQueryClient();
const [selectedRecord, setSelectedRecord] = useState<PatientRecord | null>(null);
const [page, setPage] = useState(1);
@@ -543,3 +544,11 @@ function EditSessionModal({
</Modal>
);
}
export default function MyPatientsPage() {
return (
<FeatureGate feature="patient_records">
<MyPatientsPageInner />
</FeatureGate>
);
}
+13 -4
View File
@@ -15,6 +15,8 @@ import Modal from '../components/ui/Modal';
import ConfirmDialog from '../components/ui/ConfirmDialog';
import PageHeader from '../components/ui/PageHeader';
import { useAuthStore } from '../stores/authStore';
import { useSubscription } from '../hooks/useSubscription';
import { Link } from 'react-router-dom';
// ── Default permissions & labels ──────────────────────────────────────────
@@ -143,6 +145,7 @@ export default function MySecretariesPage() {
const { doctorUuid, dbUuid, primaryRole } = useAuthStore();
const isClinic = primaryRole === 'clinic';
const { maxSecretaries } = useSubscription();
// for clinic: selected doctor to add secretary for
const [selectedDoctorUuid, setSelectedDoctorUuid] = useState<string>('');
@@ -309,10 +312,16 @@ export default function MySecretariesPage() {
title="منشیان من"
description="مدیریت منشیان و دسترسی‌های آن‌ها"
action={
<button className="btn primary sm" onClick={handleCreateOpen} disabled={isClinic && !selectedDoctorUuid}>
<PlusIcon style={{ width: 16 }} />
افزودن منشی
</button>
secretaries.length >= maxSecretaries ? (
<Link to="/admin/subscription" className="btn sm" style={{ textDecoration: 'none', opacity: 0.8 }} title={`حداکثر ${maxSecretaries} منشی مجاز است`}>
ارتقاء پنل برای افزودن منشی
</Link>
) : (
<button className="btn primary sm" onClick={handleCreateOpen} disabled={isClinic && !selectedDoctorUuid}>
<PlusIcon style={{ width: 16 }} />
افزودن منشی
</button>
)
}
/>
+10 -1
View File
@@ -14,6 +14,7 @@ import { formatRial, formatNumber, formatDateTime } from '../lib/utils';
import Modal from '../components/ui/Modal';
import Pagination from '../components/ui/Pagination';
import PageHeader from '../components/ui/PageHeader';
import FeatureGate from '../components/ui/FeatureGate';
const chargeSchema = z.object({
amount_rials: z.coerce.number().min(10000, 'حداقل مبلغ ۱۰,۰۰۰ ریال است'),
@@ -22,7 +23,7 @@ type ChargeForm = z.infer<typeof chargeSchema>;
const EMPTY_LOGS: SmsWalletLog[] = [];
export default function SmsWalletPage() {
function SmsWalletPageInner() {
const qc = useQueryClient();
const [chargeOpen, setChargeOpen] = useState(false);
const [gateway, setGateway] = useState<'mellat' | 'sep'>('mellat');
@@ -433,3 +434,11 @@ export default function SmsWalletPage() {
</>
);
}
export default function SmsWalletPage() {
return (
<FeatureGate feature="sms_panel">
<SmsWalletPageInner />
</FeatureGate>
);
}