mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix: show all campaigns in assign list, add dual links and bonus details
1. Remove is_active filter from campaign assign page so inactive campaigns are also visible (with badge) 2. Redesign partner campaign cards with separate bot/cabinet links, detailed bonus descriptions, and per-link copy buttons
This commit is contained in:
@@ -671,10 +671,18 @@
|
|||||||
"submitApplication": "Submit Application",
|
"submitApplication": "Submit Application",
|
||||||
"applyError": "Failed to submit application. Please try again.",
|
"applyError": "Failed to submit application. Please try again.",
|
||||||
"yourCampaigns": "Your campaigns",
|
"yourCampaigns": "Your campaigns",
|
||||||
|
"campaignLinks": {
|
||||||
|
"bot": "Bot link",
|
||||||
|
"web": "Cabinet link"
|
||||||
|
},
|
||||||
"campaignBonus": {
|
"campaignBonus": {
|
||||||
|
"title": "Referral bonus",
|
||||||
"balance": "+{{amount}} to balance",
|
"balance": "+{{amount}} to balance",
|
||||||
|
"balanceDesc": "{{amount}} credited on signup",
|
||||||
"subscription": "{{days}} days subscription",
|
"subscription": "{{days}} days subscription",
|
||||||
"tariff": "Bonus tariff"
|
"subscriptionDesc": "{{days}} days free subscription",
|
||||||
|
"tariff": "Bonus tariff",
|
||||||
|
"tariffDesc": "Free tariff plan"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"companyName": "Company Name",
|
"companyName": "Company Name",
|
||||||
|
|||||||
@@ -562,10 +562,18 @@
|
|||||||
"submitApplication": "ارسال درخواست",
|
"submitApplication": "ارسال درخواست",
|
||||||
"applyError": "ارسال درخواست ناموفق بود. لطفاً دوباره تلاش کنید.",
|
"applyError": "ارسال درخواست ناموفق بود. لطفاً دوباره تلاش کنید.",
|
||||||
"yourCampaigns": "کمپینهای شما",
|
"yourCampaigns": "کمپینهای شما",
|
||||||
|
"campaignLinks": {
|
||||||
|
"bot": "لینک ربات",
|
||||||
|
"web": "لینک کابینت"
|
||||||
|
},
|
||||||
"campaignBonus": {
|
"campaignBonus": {
|
||||||
|
"title": "جایزه معرفی",
|
||||||
"balance": "+{{amount}} به موجودی",
|
"balance": "+{{amount}} به موجودی",
|
||||||
|
"balanceDesc": "{{amount}} هنگام ثبتنام",
|
||||||
"subscription": "{{days}} روز اشتراک",
|
"subscription": "{{days}} روز اشتراک",
|
||||||
"tariff": "تعرفه جایزه"
|
"subscriptionDesc": "{{days}} روز اشتراک رایگان",
|
||||||
|
"tariff": "تعرفه جایزه",
|
||||||
|
"tariffDesc": "طرح تعرفه رایگان"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"companyName": "نام شرکت",
|
"companyName": "نام شرکت",
|
||||||
|
|||||||
@@ -698,10 +698,18 @@
|
|||||||
"submitApplication": "Отправить заявку",
|
"submitApplication": "Отправить заявку",
|
||||||
"applyError": "Не удалось отправить заявку. Попробуйте снова.",
|
"applyError": "Не удалось отправить заявку. Попробуйте снова.",
|
||||||
"yourCampaigns": "Ваши кампании",
|
"yourCampaigns": "Ваши кампании",
|
||||||
|
"campaignLinks": {
|
||||||
|
"bot": "Ссылка в бот",
|
||||||
|
"web": "Ссылка в кабинет"
|
||||||
|
},
|
||||||
"campaignBonus": {
|
"campaignBonus": {
|
||||||
|
"title": "Бонус для реферала",
|
||||||
"balance": "+{{amount}} на баланс",
|
"balance": "+{{amount}} на баланс",
|
||||||
|
"balanceDesc": "{{amount}} на баланс при регистрации",
|
||||||
"subscription": "{{days}} дн. подписки",
|
"subscription": "{{days}} дн. подписки",
|
||||||
"tariff": "Бонусный тариф"
|
"subscriptionDesc": "{{days}} дней бесплатной подписки",
|
||||||
|
"tariff": "Бонусный тариф",
|
||||||
|
"tariffDesc": "Бесплатный тарифный план"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"companyName": "Название компании",
|
"companyName": "Название компании",
|
||||||
|
|||||||
@@ -562,10 +562,18 @@
|
|||||||
"submitApplication": "提交申请",
|
"submitApplication": "提交申请",
|
||||||
"applyError": "提交申请失败,请重试。",
|
"applyError": "提交申请失败,请重试。",
|
||||||
"yourCampaigns": "您的活动",
|
"yourCampaigns": "您的活动",
|
||||||
|
"campaignLinks": {
|
||||||
|
"bot": "机器人链接",
|
||||||
|
"web": "网页链接"
|
||||||
|
},
|
||||||
"campaignBonus": {
|
"campaignBonus": {
|
||||||
|
"title": "推荐奖励",
|
||||||
"balance": "+{{amount}} 余额",
|
"balance": "+{{amount}} 余额",
|
||||||
|
"balanceDesc": "注册时充值 {{amount}}",
|
||||||
"subscription": "{{days}} 天订阅",
|
"subscription": "{{days}} 天订阅",
|
||||||
"tariff": "奖励套餐"
|
"subscriptionDesc": "{{days}} 天免费订阅",
|
||||||
|
"tariff": "奖励套餐",
|
||||||
|
"tariffDesc": "免费套餐方案"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"companyName": "公司名称",
|
"companyName": "公司名称",
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ export default function AdminPartnerCampaignAssign() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const assignedIds = new Set(partner?.campaigns.map((c) => c.id) ?? []);
|
const assignedIds = new Set(partner?.campaigns.map((c) => c.id) ?? []);
|
||||||
const available =
|
const available = campaignsData?.campaigns.filter((c) => !assignedIds.has(c.id)) ?? [];
|
||||||
campaignsData?.campaigns.filter((c) => !assignedIds.has(c.id) && c.is_active) ?? [];
|
|
||||||
|
|
||||||
const partnerName = partner
|
const partnerName = partner
|
||||||
? partner.first_name || partner.username || `#${partner.user_id}`
|
? partner.first_name || partner.username || `#${partner.user_id}`
|
||||||
@@ -75,7 +74,14 @@ export default function AdminPartnerCampaignAssign() {
|
|||||||
<div key={campaign.id} className="rounded-xl border border-dark-700 bg-dark-800 p-4">
|
<div key={campaign.id} className="rounded-xl border border-dark-700 bg-dark-800 p-4">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="font-medium text-dark-100">{campaign.name}</div>
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-medium text-dark-100">{campaign.name}</span>
|
||||||
|
{!campaign.is_active && (
|
||||||
|
<span className="rounded bg-dark-600 px-1.5 py-0.5 text-xs text-dark-400">
|
||||||
|
{t('admin.campaigns.status.inactive')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="mt-1 flex items-center gap-3 text-xs text-dark-500">
|
<div className="mt-1 flex items-center gap-3 text-xs text-dark-500">
|
||||||
<span className="font-mono">?start={campaign.start_parameter}</span>
|
<span className="font-mono">?start={campaign.start_parameter}</span>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export default function Referral() {
|
|||||||
const { formatAmount, currencySymbol, formatPositive, formatWithCurrency } = useCurrency();
|
const { formatAmount, currencySymbol, formatPositive, formatWithCurrency } = useCurrency();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [copiedCampaignId, setCopiedCampaignId] = useState<number | null>(null);
|
const [copiedLink, setCopiedLink] = useState<string | null>(null);
|
||||||
|
|
||||||
const { data: info, isLoading } = useQuery({
|
const { data: info, isLoading } = useQuery({
|
||||||
queryKey: ['referral-info'],
|
queryKey: ['referral-info'],
|
||||||
@@ -522,8 +522,8 @@ export default function Referral() {
|
|||||||
{/* ==================== Partner Campaigns Section ==================== */}
|
{/* ==================== Partner Campaigns Section ==================== */}
|
||||||
|
|
||||||
{isPartner && partnerStatus?.campaigns && partnerStatus.campaigns.length > 0 && (
|
{isPartner && partnerStatus?.campaigns && partnerStatus.campaigns.length > 0 && (
|
||||||
<div className="bento-card">
|
<div className="space-y-4">
|
||||||
<div className="mb-4 flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-accent-500/10 text-accent-400">
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-accent-500/10 text-accent-400">
|
||||||
<LinkIcon />
|
<LinkIcon />
|
||||||
</div>
|
</div>
|
||||||
@@ -532,70 +532,100 @@ export default function Referral() {
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
{partnerStatus.campaigns.map((campaign) => {
|
{partnerStatus.campaigns.map((campaign) => {
|
||||||
const shareUrl = campaign.deep_link || campaign.web_link || '';
|
const copyLink = (url: string, key: string) => {
|
||||||
const isCopied = copiedCampaignId === campaign.id;
|
navigator.clipboard.writeText(url);
|
||||||
|
setCopiedLink(key);
|
||||||
const copyCampaignLink = () => {
|
setTimeout(() => setCopiedLink(null), 2000);
|
||||||
if (!shareUrl) return;
|
|
||||||
navigator.clipboard.writeText(shareUrl);
|
|
||||||
setCopiedCampaignId(campaign.id);
|
|
||||||
setTimeout(() => setCopiedCampaignId(null), 2000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const bonusBadge = (() => {
|
const botKey = `${campaign.id}-bot`;
|
||||||
switch (campaign.bonus_type) {
|
const webKey = `${campaign.id}-web`;
|
||||||
case 'balance':
|
|
||||||
return {
|
|
||||||
label: t('referral.partner.campaignBonus.balance', {
|
|
||||||
amount: formatWithCurrency(campaign.balance_bonus_kopeks / 100, 0),
|
|
||||||
}),
|
|
||||||
className: 'badge-success',
|
|
||||||
};
|
|
||||||
case 'subscription':
|
|
||||||
return {
|
|
||||||
label: t('referral.partner.campaignBonus.subscription', {
|
|
||||||
days: campaign.subscription_duration_days ?? 0,
|
|
||||||
}),
|
|
||||||
className: 'badge-info',
|
|
||||||
};
|
|
||||||
case 'tariff':
|
|
||||||
return {
|
|
||||||
label: t('referral.partner.campaignBonus.tariff'),
|
|
||||||
className: 'badge-info',
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={campaign.id} className="bento-card space-y-4">
|
||||||
key={campaign.id}
|
{/* Campaign header */}
|
||||||
className="rounded-xl border border-dark-700/30 bg-dark-800/30 p-4"
|
<div className="flex items-center gap-2">
|
||||||
>
|
<h3 className="text-base font-semibold text-dark-100">{campaign.name}</h3>
|
||||||
<div className="mb-2 flex items-center gap-2">
|
</div>
|
||||||
<span className="font-medium text-dark-100">{campaign.name}</span>
|
|
||||||
{bonusBadge && <span className={bonusBadge.className}>{bonusBadge.label}</span>}
|
{/* Bonus info */}
|
||||||
|
{campaign.bonus_type !== 'none' && (
|
||||||
|
<div className="rounded-lg bg-success-500/10 p-3">
|
||||||
|
<div className="mb-1 text-xs font-medium text-success-500">
|
||||||
|
{t('referral.partner.campaignBonus.title')}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm font-semibold text-success-400">
|
||||||
|
{campaign.bonus_type === 'balance' &&
|
||||||
|
t('referral.partner.campaignBonus.balanceDesc', {
|
||||||
|
amount: formatWithCurrency(campaign.balance_bonus_kopeks / 100, 0),
|
||||||
|
})}
|
||||||
|
{campaign.bonus_type === 'subscription' &&
|
||||||
|
t('referral.partner.campaignBonus.subscriptionDesc', {
|
||||||
|
days: campaign.subscription_duration_days ?? 0,
|
||||||
|
...(campaign.subscription_traffic_gb
|
||||||
|
? { traffic: campaign.subscription_traffic_gb }
|
||||||
|
: {}),
|
||||||
|
})}
|
||||||
|
{campaign.bonus_type === 'tariff' &&
|
||||||
|
t('referral.partner.campaignBonus.tariffDesc')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Bot link */}
|
||||||
|
{campaign.deep_link && (
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 text-xs font-medium text-dark-500">
|
||||||
|
{t('referral.partner.campaignLinks.bot')}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input type="text" readOnly value={shareUrl} className="input flex-1 text-xs" />
|
<input
|
||||||
|
type="text"
|
||||||
|
readOnly
|
||||||
|
value={campaign.deep_link}
|
||||||
|
className="input flex-1 text-xs"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={copyCampaignLink}
|
onClick={() => copyLink(campaign.deep_link!, botKey)}
|
||||||
disabled={!shareUrl}
|
|
||||||
className={`btn-primary shrink-0 px-3 py-2 ${
|
className={`btn-primary shrink-0 px-3 py-2 ${
|
||||||
isCopied ? 'bg-success-500 hover:bg-success-500' : ''
|
copiedLink === botKey ? 'bg-success-500 hover:bg-success-500' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isCopied ? <CheckIcon /> : <CopyIcon />}
|
{copiedLink === botKey ? <CheckIcon /> : <CopyIcon />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Web link */}
|
||||||
|
{campaign.web_link && (
|
||||||
|
<div>
|
||||||
|
<div className="mb-1 text-xs font-medium text-dark-500">
|
||||||
|
{t('referral.partner.campaignLinks.web')}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readOnly
|
||||||
|
value={campaign.web_link}
|
||||||
|
className="input flex-1 text-xs"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => copyLink(campaign.web_link!, webKey)}
|
||||||
|
className={`btn-primary shrink-0 px-3 py-2 ${
|
||||||
|
copiedLink === webKey ? 'bg-success-500 hover:bg-success-500' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{copiedLink === webKey ? <CheckIcon /> : <CopyIcon />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ==================== Withdrawal Section (approved partners only) ==================== */}
|
{/* ==================== Withdrawal Section (approved partners only) ==================== */}
|
||||||
|
|||||||
Reference in New Issue
Block a user