Add files via upload

This commit is contained in:
Egor
2026-01-25 08:44:25 +03:00
committed by GitHub
parent 9d07e3ad6c
commit fe5be1f08d
2 changed files with 150 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo, useCallback } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useQuery } from '@tanstack/react-query'
import { useAuthStore } from '../store/auth'
@@ -12,9 +12,18 @@ export default function Login() {
const { t } = useTranslation()
const navigate = useNavigate()
const location = useLocation()
const [searchParams] = useSearchParams()
const { isAuthenticated, loginWithTelegram, loginWithEmail, registerWithEmail } = useAuthStore()
const [activeTab, setActiveTab] = useState<'telegram' | 'email'>('telegram')
const [authMode, setAuthMode] = useState<'login' | 'register'>('login')
// Extract referral code from URL
const referralCode = searchParams.get('ref') || ''
const [activeTab, setActiveTab] = useState<'telegram' | 'email'>(() =>
referralCode ? 'email' : 'telegram'
)
const [authMode, setAuthMode] = useState<'login' | 'register'>(() =>
referralCode ? 'register' : 'login'
)
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
@@ -126,7 +135,7 @@ export default function Login() {
if (authMode === 'login') {
await loginWithEmail(email, password)
} else {
await registerWithEmail(email, password, firstName || undefined)
await registerWithEmail(email, password, firstName || undefined, referralCode || undefined)
}
navigate(getReturnUrl(), { replace: true })
} catch (err: unknown) {
@@ -185,6 +194,18 @@ export default function Login() {
<p className="mt-2 text-dark-400">
{t('auth.loginSubtitle')}
</p>
{/* Referral Banner */}
{referralCode && (
<div className="mt-4 p-3 rounded-xl bg-accent-500/10 border border-accent-500/30">
<div className="flex items-center gap-2 text-accent-400">
<svg className="w-5 h-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
</svg>
<span className="text-sm font-medium">{t('auth.referralInvite')}</span>
</div>
</div>
)}
</div>
{/* Card */}

View File

@@ -1,9 +1,38 @@
import { useState } from 'react'
import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useAuthStore } from '../store/auth'
import { authApi } from '../api/auth'
import { notificationsApi, NotificationSettings, NotificationSettingsUpdate } from '../api/notifications'
import { referralApi } from '../api/referral'
import { brandingApi } from '../api/branding'
// Icons
const CopyIcon = () => (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
</svg>
)
const CheckIcon = () => (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
)
const ShareIcon = () => (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M7 8l5-5m0 0l5 5m-5-5v12" />
<path strokeLinecap="round" strokeLinejoin="round" d="M4 15v3a2 2 0 002 2h12a2 2 0 002-2v-3" />
</svg>
)
const ArrowRightIcon = () => (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
</svg>
)
export default function Profile() {
const { t } = useTranslation()
@@ -15,6 +44,58 @@ export default function Profile() {
const [confirmPassword, setConfirmPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null)
const [copied, setCopied] = useState(false)
// Referral data
const { data: referralInfo } = useQuery({
queryKey: ['referral-info'],
queryFn: referralApi.getReferralInfo,
})
const { data: referralTerms } = useQuery({
queryKey: ['referral-terms'],
queryFn: referralApi.getReferralTerms,
})
const { data: branding } = useQuery({
queryKey: ['branding'],
queryFn: brandingApi.getBranding,
staleTime: 60000,
})
// Build referral link
const botUsername = import.meta.env.VITE_TELEGRAM_BOT_USERNAME
const referralLink = referralInfo?.referral_code && botUsername
? `https://t.me/${botUsername}?start=${referralInfo.referral_code}`
: referralInfo?.referral_link || ''
const copyReferralLink = () => {
if (referralLink) {
navigator.clipboard.writeText(referralLink)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}
const shareReferralLink = () => {
if (!referralLink) return
const shareText = t('referral.shareMessage', {
percent: referralInfo?.commission_percent || 0,
botName: branding?.name || import.meta.env.VITE_APP_NAME || 'Cabinet',
})
if (navigator.share) {
navigator.share({
title: t('referral.title'),
text: shareText,
url: referralLink,
}).catch(() => {})
return
}
const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(referralLink)}&text=${encodeURIComponent(shareText)}`
window.open(telegramUrl, '_blank', 'noopener,noreferrer')
}
const registerEmailMutation = useMutation({
mutationFn: ({ email, password }: { email: string; password: string }) =>
@@ -127,6 +208,50 @@ export default function Profile() {
</div>
</div>
{/* Referral Link Widget */}
{referralTerms?.is_enabled && referralLink && (
<div className="bento-card">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-dark-100">{t('referral.yourLink')}</h2>
<Link to="/referral" className="text-accent-400 hover:text-accent-300 transition-colors flex items-center gap-1">
<span className="text-sm">{t('referral.title')}</span>
<ArrowRightIcon />
</Link>
</div>
<div className="flex flex-col gap-3 sm:flex-row">
<div className="flex-1">
<input
type="text"
readOnly
value={referralLink}
className="input w-full text-sm"
/>
</div>
<div className="flex gap-2">
<button
onClick={copyReferralLink}
className={`btn-primary px-4 py-2 flex items-center gap-2 text-sm ${
copied ? 'bg-success-500 hover:bg-success-500' : ''
}`}
>
{copied ? <CheckIcon /> : <CopyIcon />}
<span>{copied ? t('referral.copied') : t('referral.copyLink')}</span>
</button>
<button
onClick={shareReferralLink}
className="btn-secondary px-4 py-2 flex items-center gap-2 text-sm"
>
<ShareIcon />
<span className="hidden sm:inline">{t('referral.shareButton')}</span>
</button>
</div>
</div>
<p className="mt-3 text-sm text-dark-500">
{t('referral.shareHint', { percent: referralInfo?.commission_percent || 0 })}
</p>
</div>
)}
{/* Email Section */}
<div className="bento-card">
<h2 className="text-lg font-semibold text-dark-100 mb-6">{t('profile.emailAuth')}</h2>