Promocode system
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user