feat(mobile): Phase M-1 모바일 프로젝트 기반 + V3 마스터 플랜

- V3 모바일 마스터 플랜 작성 (docs/v3/00-mobile-master-plan.md)
  11개 Phase, 5주 로드맵, 전략 결정사항 확정
- 7개 디자인 HTML 레퍼런스 저장 (docs/v3/designs/)
- app.json → app.config.ts 전환 + 환경변수 체계
- 5탭 네비게이션 (DASH/HIST/REC FAB/TALK/SET)
- metro.config.js monorepo 격리 (root RN 0.84 충돌 해결)
- ui-native 테마 디자인 목업 색상 동기화
- SafeArea 적용 (useSafeAreaInsets)
- DEV 로그인 우회 (__DEV__ 전용)
- Supabase .env 연동
- iOS 시뮬레이터 검증 완료
This commit is contained in:
윤찬 2026-04-13 03:22:15 +09:00
parent ffca07d120
commit 211673bc6c
30 changed files with 15010 additions and 525 deletions

View file

@ -10,14 +10,26 @@ interface AuthContextValue {
session: Session | null
user: User | null
loading: boolean
devBypass: () => void
}
const AuthContext = createContext<AuthContextValue>({
session: null,
user: null,
loading: true
loading: true,
devBypass: () => {}
})
// DEV 전용 — 가짜 유저로 로그인 우회
const DEV_USER: User = {
id: 'dev-user-00000',
email: 'dev@d3ro.local',
app_metadata: {},
user_metadata: {},
aud: 'authenticated',
created_at: new Date().toISOString()
} as User
export function AuthProvider({ children }: { children: ReactNode }): React.ReactElement {
const [session, setSession] = useState<Session | null>(null)
const [loading, setLoading] = useState(true)
@ -47,8 +59,16 @@ export function AuthProvider({ children }: { children: ReactNode }): React.React
}
}, [])
const [devMode, setDevMode] = useState(false)
function devBypass(): void {
setDevMode(true)
}
const effectiveUser = devMode ? DEV_USER : (session?.user ?? null)
return (
<AuthContext.Provider value={{ session, user: session?.user ?? null, loading }}>
<AuthContext.Provider value={{ session, user: effectiveUser, loading, devBypass }}>
{children}
</AuthContext.Provider>
)

View file

@ -3,29 +3,33 @@
import 'react-native-url-polyfill/auto'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'
import { createClient, type SupabaseClient } from '@supabase/supabase-js'
import Constants from 'expo-constants'
const supabaseUrl = (Constants.expoConfig?.extra?.supabaseUrl as string | undefined) ?? ''
const supabaseAnonKey = (Constants.expoConfig?.extra?.supabaseAnonKey as string | undefined) ?? ''
if (!supabaseUrl || !supabaseAnonKey) {
// env 미설정 — placeholder로 client 생성, 런타임에 isConfigured 체크
export function isSupabaseConfigured(): boolean {
return Boolean(supabaseUrl && supabaseAnonKey)
}
export const supabase = createClient(
supabaseUrl || 'https://placeholder.supabase.co',
supabaseAnonKey || 'placeholder-anon-key',
{
function createSupabaseClient(): SupabaseClient {
if (!isSupabaseConfigured()) {
// env 미설정 시에도 크래시 방지용 더미 클라이언트 생성
// 런타임에서 isSupabaseConfigured()로 체크 후 사용
return createClient('https://localhost.invalid', 'no-key', {
auth: { storage: AsyncStorage, persistSession: false, detectSessionInUrl: false }
})
}
return createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false
}
}
)
export function isSupabaseConfigured(): boolean {
return Boolean(supabaseUrl && supabaseAnonKey)
})
}
export const supabase = createSupabaseClient()