Promocode system

This commit is contained in:
2026-01-08 10:02:15 +07:00
parent 1751c4dd4c
commit e63d6c8489
19 changed files with 1443 additions and 7 deletions

View File

@@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Link } from 'react-router-dom'
import { useAuthStore } from '@/store/auth'
import { usersApi, telegramApi, authApi } from '@/api'
import { usersApi, telegramApi, authApi, promoApi } from '@/api'
import type { UserStats, ShopItemPublic } from '@/types'
import { useToast } from '@/store/toast'
import {
@@ -14,7 +14,7 @@ import {
User, Camera, Trophy, Target, CheckCircle, Flame,
Loader2, MessageCircle, Link2, Link2Off, ExternalLink,
Eye, EyeOff, Save, KeyRound, Shield, Bell, Sparkles,
AlertTriangle, FileCheck, Backpack, Edit3
AlertTriangle, FileCheck, Backpack, Edit3, Gift
} from 'lucide-react'
import clsx from 'clsx'
@@ -289,6 +289,10 @@ export function ProfilePage() {
const [notifyModeration, setNotifyModeration] = useState(user?.notify_moderation ?? true)
const [notificationUpdating, setNotificationUpdating] = useState<string | null>(null)
// Promo code state
const [promoCode, setPromoCode] = useState('')
const [isRedeemingPromo, setIsRedeemingPromo] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
// Forms
@@ -526,6 +530,27 @@ export function ProfilePage() {
}
}
// Redeem promo code
const handleRedeemPromo = async (e: React.FormEvent) => {
e.preventDefault()
if (!promoCode.trim()) return
setIsRedeemingPromo(true)
try {
const response = await promoApi.redeem(promoCode.trim())
toast.success(response.data.message)
setPromoCode('')
// Update coin balance in store
updateUser({ coins_balance: response.data.new_balance })
} catch (error: unknown) {
const err = error as { response?: { data?: { detail?: string } } }
const message = err.response?.data?.detail || 'Не удалось активировать промокод'
toast.error(message)
} finally {
setIsRedeemingPromo(false)
}
}
const isLinked = !!user?.telegram_id
const displayAvatar = avatarBlobUrl || user?.telegram_avatar_url
@@ -773,6 +798,37 @@ export function ProfilePage() {
)}
</div>
{/* Promo Code */}
<GlassCard>
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-xl bg-yellow-500/20 flex items-center justify-center">
<Gift className="w-5 h-5 text-yellow-400" />
</div>
<div>
<h2 className="text-lg font-semibold text-white">Промокод</h2>
<p className="text-sm text-gray-400">Введите код для получения монет</p>
</div>
</div>
<form onSubmit={handleRedeemPromo} className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<Input
placeholder="Введите промокод"
value={promoCode}
onChange={(e) => setPromoCode(e.target.value.toUpperCase())}
maxLength={50}
/>
</div>
<NeonButton
type="submit"
isLoading={isRedeemingPromo}
disabled={!promoCode.trim()}
icon={<Gift className="w-4 h-4" />}
>
Активировать
</NeonButton>
</form>
</GlassCard>
{/* Telegram */}
<GlassCard>
<div className="flex items-center gap-3 mb-6">