mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
feat: wheel subscription picker for multi-tariff mode
- API: spin() accepts subscriptionId, WheelConfig has eligible_subscriptions - Auto-select subscription when only one eligible - Show subscription picker when multiple eligible (days payment) - Disable spin button until subscription selected (when >1 eligible) - Daily tariffs excluded from eligible list
This commit is contained in:
@@ -8,6 +8,12 @@ export interface WheelPrize {
|
|||||||
prize_type: string;
|
prize_type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EligibleSubscription {
|
||||||
|
id: number;
|
||||||
|
tariff_name: string | null;
|
||||||
|
days_left: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WheelConfig {
|
export interface WheelConfig {
|
||||||
is_enabled: boolean;
|
is_enabled: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -25,6 +31,7 @@ export interface WheelConfig {
|
|||||||
user_balance_kopeks: number;
|
user_balance_kopeks: number;
|
||||||
required_balance_kopeks: number;
|
required_balance_kopeks: number;
|
||||||
has_subscription: boolean;
|
has_subscription: boolean;
|
||||||
|
eligible_subscriptions: EligibleSubscription[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpinAvailability {
|
export interface SpinAvailability {
|
||||||
@@ -187,9 +194,13 @@ export const wheelApi = {
|
|||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
spin: async (paymentType: 'telegram_stars' | 'subscription_days'): Promise<SpinResult> => {
|
spin: async (
|
||||||
|
paymentType: 'telegram_stars' | 'subscription_days',
|
||||||
|
subscriptionId?: number,
|
||||||
|
): Promise<SpinResult> => {
|
||||||
const response = await apiClient.post<SpinResult>('/cabinet/wheel/spin', {
|
const response = await apiClient.post<SpinResult>('/cabinet/wheel/spin', {
|
||||||
payment_type: paymentType,
|
payment_type: paymentType,
|
||||||
|
...(subscriptionId != null && { subscription_id: subscriptionId }),
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ export default function Wheel() {
|
|||||||
const [isPayingStars, setIsPayingStars] = useState(false);
|
const [isPayingStars, setIsPayingStars] = useState(false);
|
||||||
const [historyExpanded, setHistoryExpanded] = useState(false);
|
const [historyExpanded, setHistoryExpanded] = useState(false);
|
||||||
const [showStarsConfirm, setShowStarsConfirm] = useState(false);
|
const [showStarsConfirm, setShowStarsConfirm] = useState(false);
|
||||||
|
const [selectedSubscriptionId, setSelectedSubscriptionId] = useState<number | null>(null);
|
||||||
const paymentTypeInitialized = useRef(false);
|
const paymentTypeInitialized = useRef(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -136,6 +137,11 @@ export default function Wheel() {
|
|||||||
} else if (daysEnabled) {
|
} else if (daysEnabled) {
|
||||||
setPaymentType('subscription_days');
|
setPaymentType('subscription_days');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-select subscription if only one eligible
|
||||||
|
if (config.eligible_subscriptions?.length === 1) {
|
||||||
|
setSelectedSubscriptionId(config.eligible_subscriptions[0].id);
|
||||||
|
}
|
||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
// Function to poll for new spin result after Stars payment
|
// Function to poll for new spin result after Stars payment
|
||||||
@@ -368,7 +374,7 @@ export default function Wheel() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const spinMutation = useMutation({
|
const spinMutation = useMutation({
|
||||||
mutationFn: () => wheelApi.spin(paymentType),
|
mutationFn: () => wheelApi.spin(paymentType, selectedSubscriptionId ?? undefined),
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setTargetRotation(result.rotation_degrees);
|
setTargetRotation(result.rotation_degrees);
|
||||||
@@ -506,11 +512,18 @@ export default function Wheel() {
|
|||||||
// Stars via Telegram invoice don't require ruble balance, so only check daily limit
|
// Stars via Telegram invoice don't require ruble balance, so only check daily limit
|
||||||
const dailyLimitReached = config.daily_limit > 0 && config.user_spins_today >= config.daily_limit;
|
const dailyLimitReached = config.daily_limit > 0 && config.user_spins_today >= config.daily_limit;
|
||||||
const noSubscription = !config.has_subscription;
|
const noSubscription = !config.has_subscription;
|
||||||
|
const needsSubscriptionPick =
|
||||||
|
paymentType === 'subscription_days' &&
|
||||||
|
config.eligible_subscriptions &&
|
||||||
|
config.eligible_subscriptions.length > 1 &&
|
||||||
|
!selectedSubscriptionId;
|
||||||
|
|
||||||
const spinDisabled =
|
const spinDisabled =
|
||||||
isSpinning ||
|
isSpinning ||
|
||||||
isPayingStars ||
|
isPayingStars ||
|
||||||
dailyLimitReached ||
|
dailyLimitReached ||
|
||||||
noSubscription ||
|
noSubscription ||
|
||||||
|
needsSubscriptionPick ||
|
||||||
(paymentType === 'telegram_stars' ? !starsEnabled : !config.can_spin);
|
(paymentType === 'telegram_stars' ? !starsEnabled : !config.can_spin);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -582,6 +595,38 @@ export default function Wheel() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Subscription selector for days payment in multi-tariff */}
|
||||||
|
{paymentType === 'subscription_days' &&
|
||||||
|
config.eligible_subscriptions &&
|
||||||
|
config.eligible_subscriptions.length > 1 && (
|
||||||
|
<div className="rounded-xl border border-dark-700/30 bg-dark-800/30 p-3">
|
||||||
|
<p className="mb-2 text-center text-xs text-dark-400">
|
||||||
|
{t('wheel.selectSubscription', 'Выберите подписку')}
|
||||||
|
</p>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
{config.eligible_subscriptions.map((sub) => (
|
||||||
|
<button
|
||||||
|
key={sub.id}
|
||||||
|
onClick={() => setSelectedSubscriptionId(sub.id)}
|
||||||
|
disabled={isSpinning}
|
||||||
|
className={`flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm transition-all ${
|
||||||
|
selectedSubscriptionId === sub.id
|
||||||
|
? 'bg-accent-500/15 text-accent-400'
|
||||||
|
: 'text-dark-400 hover:text-dark-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="font-medium">
|
||||||
|
{sub.tariff_name || t('subscription.defaultName', 'Подписка')}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs opacity-60">
|
||||||
|
{sub.days_left} {t('common.units.days', 'дней')}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Stars confirmation panel */}
|
{/* Stars confirmation panel */}
|
||||||
{showStarsConfirm && !isSpinning && !isPayingStars ? (
|
{showStarsConfirm && !isSpinning && !isPayingStars ? (
|
||||||
<div className="space-y-3 rounded-xl border border-accent-500/30 bg-accent-500/5 p-4">
|
<div className="space-y-3 rounded-xl border border-accent-500/30 bg-accent-500/5 p-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user