import { useState, useCallback, useEffect } from 'react'; import { View, Text, StyleSheet, Pressable, ActivityIndicator, Dimensions, } from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, FadeIn, SlideInDown, } from 'react-native-reanimated'; import { LinearGradient } from 'expo-linear-gradient'; import * as WebBrowser from 'expo-web-browser'; import * as Linking from 'expo-linking'; import { router } from 'expo-router'; import Svg, { Path, Circle, Polyline, Line } from 'react-native-svg'; import { Colors, Font, Radius, Spacing } from '../constants/theme'; import { buildOAuthUrl, exchangeCode, saveTokens } from '../services/api'; import { useAuth } from '../store'; WebBrowser.maybeCompleteAuthSession(); const { width, height } = Dimensions.get('window'); export default function AuthScreen() { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const { setUser } = useAuth(); const btnScale = useSharedValue(1); const btnStyle = useAnimatedStyle(() => ({ transform: [{ scale: btnScale.value }], })); const handleLogin = useCallback(async () => { setLoading(true); setError(''); try { const state = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2); const url = buildOAuthUrl(state); const result = await WebBrowser.openAuthSessionAsync(url, 'burmalda://auth/callback'); if (result.type !== 'success') { setError('Авторизация отменена'); return; } const parsed = Linking.parse(result.url); const code = parsed.queryParams?.code as string | undefined; if (!code) { setError((parsed.queryParams?.error as string) || 'Ошибка авторизации'); return; } const data = await exchangeCode(code); await saveTokens(data.access_token, data.refresh_token); setUser(data.user); router.replace('/(tabs)'); } catch (e: any) { setError(e.message || 'Что-то пошло не так'); } finally { setLoading(false); } }, [setUser]); return ( Burmalda Music by NodeShift { btnScale.value = withSpring(0.96, { damping: 14 }); }} onPressOut={() => { btnScale.value = withSpring(1, { damping: 14 }); }} onPress={handleLogin} > {loading ? : ( )} {loading ? 'Ожидаем авторизацию…' : 'Войти через NodeShift'} {!!error && ( {error} )} Нажимая «Войти», вы соглашаетесь с{'\n'}условиями использования NodeShift ); } function Blob({ color, size, left, top, delay }: { color: string; size: number; left: number; top: number; delay: number; }) { const tx = useSharedValue(0); const ty = useSharedValue(0); useEffect(() => { const animate = () => { tx.value = withTiming(25, { duration: 8000 }, () => tx.value = withTiming(-20, { duration: 8000 }, () => tx.value = withTiming(0, { duration: 8000 }, animate))); ty.value = withTiming(-35, { duration: 8000 }, () => ty.value = withTiming(18, { duration: 8000 }, () => ty.value = withTiming(0, { duration: 8000 }))); }; const t = setTimeout(animate, delay); return () => clearTimeout(t); }, []); const style = useAnimatedStyle(() => ({ transform: [{ translateX: tx.value }, { translateY: ty.value }], })); return ( ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.bg, alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }, blob: { position: 'absolute', opacity: 0.15 }, card: { width: '100%', maxWidth: 360, alignItems: 'center', paddingHorizontal: Spacing.xl, zIndex: 10 }, logoWrap: { alignItems: 'center', marginBottom: 40 }, logoIcon: { width: 84, height: 84, borderRadius: 22, alignItems: 'center', justifyContent: 'center', marginBottom: Spacing.md, shadowColor: '#1db954', shadowOffset: { width: 0, height: 14 }, shadowOpacity: 0.45, shadowRadius: 22 }, logoTitle: { color: Colors.text, fontSize: Font.xxl, fontWeight: '800', letterSpacing: -0.5, marginBottom: 4 }, logoSub: { color: Colors.text3, fontSize: Font.sm, fontWeight: '500' }, btnWrap: { width: '100%', borderRadius: Radius.md, overflow: 'hidden', shadowColor: '#1db954', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.35, shadowRadius: 18 }, btn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10, paddingVertical: 16, paddingHorizontal: 20 }, btnText: { color: '#000', fontSize: Font.md, fontWeight: '700' }, error: { color: Colors.red, fontSize: Font.sm, textAlign: 'center', marginTop: Spacing.sm }, footer: { marginTop: Spacing.xl, color: Colors.text3, fontSize: Font.xs, textAlign: 'center', lineHeight: 18 }, });