/* * SCR-01 로그인 — 이메일/비번 + 아이디 기억 + 비밀번호 찾기·회원가입 링크 + 2FA(OTP) 대응. * 브랜드 패널(딥블루 그라디언트 대체 카피) + AI 이미지 고지 캡션. * 비밀번호는 저장하지 않는다. 아이디(이메일)만 AsyncStorage에 기억. */ import { Link, router } from 'expo-router'; import React, { useEffect, useState } from 'react'; import { KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Banner } from '../components/Banner'; import { Button } from '../components/Button'; import { Field } from '../components/Field'; import { useAuth } from '../context/AuthContext'; import { ApiRequestError } from '../lib/api'; import { isOtpRequired, login } from '../lib/auth'; import { prefDelete, prefGet, prefSet } from '../lib/secureStore'; import { colors, radius, spacing, touch, type } from '../theme'; const REMEMBER_KEY = 'kintex.rememberedEmail'; export default function LoginScreen() { const insets = useSafeAreaInsets(); const { signIn } = useAuth(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [otp, setOtp] = useState(''); const [otpRequired, setOtpRequired] = useState(false); const [remember, setRemember] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { (async () => { const saved = await prefGet(REMEMBER_KEY); if (saved) { setEmail(saved); setRemember(true); } })(); }, []); async function onSubmit() { setError(null); if (!email.trim() || !password) { setError('이메일과 비밀번호를 입력해 주세요.'); return; } if (otpRequired && !otp.trim()) { setError('2차 인증 코드를 입력해 주세요.'); return; } setLoading(true); try { const res = await login({ email, password, otp: otpRequired ? otp : undefined }); if (remember) await prefSet(REMEMBER_KEY, email.trim()); else await prefDelete(REMEMBER_KEY); await signIn(res); router.replace('/(tabs)'); } catch (e) { if (isOtpRequired(e)) { setOtpRequired(true); setError('2차 인증 코드가 필요합니다. 인증 앱의 코드를 입력해 주세요.'); } else if (e instanceof ApiRequestError && e.code === 'OTP_INVALID') { setError('인증 코드가 올바르지 않거나 만료되었습니다.'); } else if (e instanceof ApiRequestError && e.code === 'ACCOUNT_LOCKED') { setError('로그인 시도 초과로 계정이 잠겼습니다. 잠시 후 다시 시도해 주세요.'); } else if (e instanceof ApiRequestError) { setError(e.message); } else { setError('로그인에 실패했습니다.'); } } finally { setLoading(false); } } return ( {/* 브랜드 패널 */} KINTEX 신청서를 내는 순간,{'\n'}시공 후 사진을 먼저 봅니다 AI 생성 예상 이미지 · 실제 시공 결과와 다를 수 있습니다 {/* 로그인 카드 */} 로그인 {error ? ( {error} ) : null} {otpRequired ? ( ) : null} {/* 아이디 기억 */} setRemember((v) => !v)} accessibilityRole="checkbox" accessibilityState={{ checked: remember }} > {remember ? : null} 아이디 기억