mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
feat: add granular user permissions (balance, subscription, promo_group, referral, send_offer)
Split users:edit into fine-grained permissions. Guard UI sections with hasPermission checks so moderators only see what they can do.
This commit is contained in:
@@ -2618,7 +2618,12 @@
|
|||||||
"manage": "Manage",
|
"manage": "Manage",
|
||||||
"ban": "Ban",
|
"ban": "Ban",
|
||||||
"unban": "Unban",
|
"unban": "Unban",
|
||||||
"assign": "Assign"
|
"assign": "Assign",
|
||||||
|
"promo_group": "Promo group",
|
||||||
|
"balance": "Balance",
|
||||||
|
"subscription": "Subscription",
|
||||||
|
"send_offer": "Send offer",
|
||||||
|
"referral": "Referral %"
|
||||||
},
|
},
|
||||||
"toggleSection": "Toggle all {{section}} permissions",
|
"toggleSection": "Toggle all {{section}} permissions",
|
||||||
"selectedPermissions_one": "{{count}} permission selected",
|
"selectedPermissions_one": "{{count}} permission selected",
|
||||||
|
|||||||
@@ -2407,7 +2407,12 @@
|
|||||||
"manage": "مدیریت",
|
"manage": "مدیریت",
|
||||||
"ban": "مسدود کردن",
|
"ban": "مسدود کردن",
|
||||||
"unban": "رفع مسدودیت",
|
"unban": "رفع مسدودیت",
|
||||||
"assign": "اختصاص"
|
"assign": "اختصاص",
|
||||||
|
"promo_group": "گروه تبلیغاتی",
|
||||||
|
"balance": "موجودی",
|
||||||
|
"subscription": "اشتراک",
|
||||||
|
"send_offer": "ارسال پیشنهاد",
|
||||||
|
"referral": "درصد ارجاع"
|
||||||
},
|
},
|
||||||
"toggleSection": "تغییر وضعیت تمام مجوزهای {{section}}",
|
"toggleSection": "تغییر وضعیت تمام مجوزهای {{section}}",
|
||||||
"selectedPermissions_other": "{{count}} مجوز انتخاب شده",
|
"selectedPermissions_other": "{{count}} مجوز انتخاب شده",
|
||||||
|
|||||||
@@ -3166,7 +3166,12 @@
|
|||||||
"manage": "Управление",
|
"manage": "Управление",
|
||||||
"ban": "Бан",
|
"ban": "Бан",
|
||||||
"unban": "Разбан",
|
"unban": "Разбан",
|
||||||
"assign": "Назначение"
|
"assign": "Назначение",
|
||||||
|
"promo_group": "Промо-группа",
|
||||||
|
"balance": "Баланс",
|
||||||
|
"subscription": "Подписка",
|
||||||
|
"send_offer": "Промо-предложение",
|
||||||
|
"referral": "Реф. процент"
|
||||||
},
|
},
|
||||||
"toggleSection": "Переключить все права {{section}}",
|
"toggleSection": "Переключить все права {{section}}",
|
||||||
"selectedPermissions_one": "{{count}} разрешение выбрано",
|
"selectedPermissions_one": "{{count}} разрешение выбрано",
|
||||||
|
|||||||
@@ -2406,7 +2406,12 @@
|
|||||||
"manage": "管理",
|
"manage": "管理",
|
||||||
"ban": "封禁",
|
"ban": "封禁",
|
||||||
"unban": "解封",
|
"unban": "解封",
|
||||||
"assign": "分配"
|
"assign": "分配",
|
||||||
|
"promo_group": "促销组",
|
||||||
|
"balance": "余额",
|
||||||
|
"subscription": "订阅",
|
||||||
|
"send_offer": "发送优惠",
|
||||||
|
"referral": "推荐佣金"
|
||||||
},
|
},
|
||||||
"toggleSection": "切换所有 {{section}} 权限",
|
"toggleSection": "切换所有 {{section}} 权限",
|
||||||
"selectedPermissions_other": "已选择 {{count}} 个权限",
|
"selectedPermissions_other": "已选择 {{count}} 个权限",
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ export default function AdminUserDetail() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeTab === 'info') {
|
if (activeTab === 'info') {
|
||||||
loadReferrals();
|
loadReferrals();
|
||||||
if (hasPermission('promo_groups:read')) loadPromoGroups();
|
if (hasPermission('users:promo_group')) loadPromoGroups();
|
||||||
}
|
}
|
||||||
if (activeTab === 'sync' && hasPermission('users:sync')) loadSyncStatus();
|
if (activeTab === 'sync' && hasPermission('users:sync')) loadSyncStatus();
|
||||||
if (activeTab === 'subscription') {
|
if (activeTab === 'subscription') {
|
||||||
@@ -935,7 +935,7 @@ export default function AdminUserDetail() {
|
|||||||
<div className="rounded-xl bg-dark-800/50 p-3">
|
<div className="rounded-xl bg-dark-800/50 p-3">
|
||||||
<div className="mb-1 flex items-center justify-between">
|
<div className="mb-1 flex items-center justify-between">
|
||||||
<span className="text-xs text-dark-500">{t('admin.users.detail.promoGroup')}</span>
|
<span className="text-xs text-dark-500">{t('admin.users.detail.promoGroup')}</span>
|
||||||
{hasPermission('promo_groups:read') && (
|
{hasPermission('users:promo_group') && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditingPromoGroup(!editingPromoGroup)}
|
onClick={() => setEditingPromoGroup(!editingPromoGroup)}
|
||||||
className="text-xs text-accent-400 transition-colors hover:text-accent-300"
|
className="text-xs text-accent-400 transition-colors hover:text-accent-300"
|
||||||
@@ -989,6 +989,7 @@ export default function AdminUserDetail() {
|
|||||||
<span className="text-sm font-medium text-dark-200">
|
<span className="text-sm font-medium text-dark-200">
|
||||||
{t('admin.users.detail.referral.title')}
|
{t('admin.users.detail.referral.title')}
|
||||||
</span>
|
</span>
|
||||||
|
{hasPermission('users:referral') && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!editingReferralCommission) {
|
if (!editingReferralCommission) {
|
||||||
@@ -1000,6 +1001,7 @@ export default function AdminUserDetail() {
|
|||||||
>
|
>
|
||||||
{editingReferralCommission ? t('common.cancel') : t('common.edit')}
|
{editingReferralCommission ? t('common.cancel') : t('common.edit')}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-3 text-center">
|
<div className="grid grid-cols-3 gap-3 text-center">
|
||||||
<div>
|
<div>
|
||||||
@@ -1358,6 +1360,7 @@ export default function AdminUserDetail() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
|
{hasPermission('users:subscription') && (
|
||||||
<div className="rounded-xl bg-dark-800/50 p-4">
|
<div className="rounded-xl bg-dark-800/50 p-4">
|
||||||
<div className="mb-3 font-medium text-dark-200">
|
<div className="mb-3 font-medium text-dark-200">
|
||||||
{t('admin.users.detail.subscription.actions')}
|
{t('admin.users.detail.subscription.actions')}
|
||||||
@@ -1368,11 +1371,15 @@ export default function AdminUserDetail() {
|
|||||||
onChange={(e) => setSubAction(e.target.value)}
|
onChange={(e) => setSubAction(e.target.value)}
|
||||||
className="input"
|
className="input"
|
||||||
>
|
>
|
||||||
<option value="extend">{t('admin.users.detail.subscription.extend')}</option>
|
<option value="extend">
|
||||||
|
{t('admin.users.detail.subscription.extend')}
|
||||||
|
</option>
|
||||||
<option value="change_tariff">
|
<option value="change_tariff">
|
||||||
{t('admin.users.detail.subscription.changeTariff')}
|
{t('admin.users.detail.subscription.changeTariff')}
|
||||||
</option>
|
</option>
|
||||||
<option value="cancel">{t('admin.users.detail.subscription.cancel')}</option>
|
<option value="cancel">
|
||||||
|
{t('admin.users.detail.subscription.cancel')}
|
||||||
|
</option>
|
||||||
<option value="activate">
|
<option value="activate">
|
||||||
{t('admin.users.detail.subscription.activate')}
|
{t('admin.users.detail.subscription.activate')}
|
||||||
</option>
|
</option>
|
||||||
@@ -1422,8 +1429,9 @@ export default function AdminUserDetail() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : hasPermission('users:subscription') ? (
|
||||||
<div className="rounded-xl bg-dark-800/50 p-4">
|
<div className="rounded-xl bg-dark-800/50 p-4">
|
||||||
<div className="mb-4 text-center text-dark-400">
|
<div className="mb-4 text-center text-dark-400">
|
||||||
{t('admin.users.detail.subscription.noActive')}
|
{t('admin.users.detail.subscription.noActive')}
|
||||||
@@ -1463,6 +1471,12 @@ export default function AdminUserDetail() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-xl bg-dark-800/50 p-4">
|
||||||
|
<div className="text-center text-dark-400">
|
||||||
|
{t('admin.users.detail.subscription.noActive')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Panel Info */}
|
{/* Panel Info */}
|
||||||
@@ -1788,6 +1802,7 @@ export default function AdminUserDetail() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add/subtract form */}
|
{/* Add/subtract form */}
|
||||||
|
{hasPermission('users:balance') && (
|
||||||
<div className="space-y-3 rounded-xl bg-dark-800/50 p-4">
|
<div className="space-y-3 rounded-xl bg-dark-800/50 p-4">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -1821,6 +1836,7 @@ export default function AdminUserDetail() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Active promo offer */}
|
{/* Active promo offer */}
|
||||||
{user.promo_offer_discount_percent > 0 && (
|
{user.promo_offer_discount_percent > 0 && (
|
||||||
@@ -1869,6 +1885,7 @@ export default function AdminUserDetail() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Send promo offer */}
|
{/* Send promo offer */}
|
||||||
|
{hasPermission('users:send_offer') && (
|
||||||
<div className="rounded-xl bg-dark-800/50 p-4">
|
<div className="rounded-xl bg-dark-800/50 p-4">
|
||||||
<div className="mb-3 text-sm font-medium text-dark-200">
|
<div className="mb-3 text-sm font-medium text-dark-200">
|
||||||
{t('admin.users.detail.sendOffer')}
|
{t('admin.users.detail.sendOffer')}
|
||||||
@@ -1901,6 +1918,7 @@ export default function AdminUserDetail() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Recent transactions */}
|
{/* Recent transactions */}
|
||||||
{user.recent_transactions.length > 0 && (
|
{user.recent_transactions.length > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user