modules/frontend/Write-Frontend-RN-Auth.ps1
|
param( [string]$ProjectName, [string]$OutputPath, [string]$Platform, [string]$Language = "tr" ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Get-Locale.ps1") $L = Get-Locale -Language $Language $MobilePath = Join-Path $OutputPath "$ProjectName-mobile" $AppPath = Join-Path $MobilePath "app" $ContextPath = Join-Path $MobilePath "context" if (-not $Global:DryRun) { New-Item -ItemType Directory -Path $ContextPath -Force | Out-Null } # 1. Write context/AuthContext.tsx $authContext = @' import React, { createContext, useState, useContext, useEffect } from 'react'; import * as SecureStore from 'expo-secure-store'; import { api } from '../services/api'; import { useRouter } from 'expo-router'; interface AuthContextType { token: string | null; username: string | null; loading: boolean; login: (u: string, p: string) => Promise<void>; logout: () => Promise<void>; } const AuthContext = createContext<AuthContextType | null>(null); export function AuthProvider({ children }: { children: React.ReactNode }) { const [token, setToken] = useState<string | null>(null); const [username, setUsername] = useState<string | null>(null); const [loading, setLoading] = useState(true); const router = useRouter(); useEffect(() => { async function loadStorage() { try { const savedToken = await SecureStore.getItemAsync('token'); const savedUser = await SecureStore.getItemAsync('username'); if (savedToken && savedUser) { setToken(savedToken); setUsername(savedUser); api.setToken(savedToken); } } catch (e) { console.log(e); } finally { setLoading(false); } } loadStorage(); }, []); const login = async (u: string, p: string) => { const res = await api.request('/auth/login', { method: 'POST', body: JSON.stringify({ username: u, password: p }), }); const { token: jwtToken, username: user } = res; await SecureStore.setItemAsync('token', jwtToken); await SecureStore.setItemAsync('username', user); setToken(jwtToken); setUsername(user); api.setToken(jwtToken); router.replace('/(tabs)'); }; const logout = async () => { await SecureStore.deleteItemAsync('token'); await SecureStore.deleteItemAsync('username'); setToken(null); setUsername(null); api.setToken(null); router.replace('/(auth)/login'); }; return ( <AuthContext.Provider value={{ token, username, loading, login, logout }}> {children} </AuthContext.Provider> ); } export function useAuth() { const context = useContext(AuthContext); if (!context) throw new Error('useAuth must be used inside AuthProvider'); return context; } '@ Write-CoreFile (Join-Path $ContextPath "AuthContext.tsx") $authContext # 2. Write app/(auth)/login.tsx (UI) $loginScreen = @" import { View, Text, TextInput, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native'; import { useState } from 'react'; import { useAuth } from '../../context/AuthContext'; export default function LoginScreen() { const { login } = useAuth(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState<string | null>(null); const [loading, setLoading] = useState(false); const handleLogin = async () => { if (!username.trim() || !password.trim()) { setError('Please fill all fields'); return; } setError(null); setLoading(true); try { await login(username, password); } catch (err: any) { setError(err.message || '$($L.uiLoginFailed)'); } finally { setLoading(false); } }; return ( <View style={styles.container}> <View style={styles.card}> <Text style={styles.title}>$ProjectName</Text> <Text style={styles.subtitle}>$($L.uiLoginSubtitle)</Text> {error && <Text style={styles.error}>{error}</Text>} <Text style={styles.label}>$($L.uiUsername)</Text> <TextInput style={styles.input} value={username} onChangeText={setUsername} autoCapitalize="none" placeholder="admin" placeholderTextColor="#666666" /> <Text style={styles.label}>$($L.uiPassword)</Text> <TextInput style={styles.input} value={password} onChangeText={setPassword} secureTextEntry autoCapitalize="none" placeholder="********" placeholderTextColor="#666666" /> <TouchableOpacity style={[styles.button, loading && styles.disabled]} onPress={handleLogin} disabled={loading} > {loading ? ( <ActivityIndicator color="#ffffff" /> ) : ( <Text style={styles.buttonText}>$($L.uiLogin)</Text> )} </TouchableOpacity> </View> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#121212', justifyContent: 'center', padding: 20, }, card: { backgroundColor: '#1e1e1e', padding: 24, borderRadius: 12, borderWidth: 1, borderColor: '#333333', }, title: { fontSize: 28, fontWeight: 'bold', color: '#ffffff', textAlign: 'center', }, subtitle: { fontSize: 14, color: '#888888', textAlign: 'center', marginBottom: 24, marginTop: 4, }, label: { color: '#ffffff', fontSize: 14, fontWeight: '600', marginBottom: 8, marginTop: 12, }, input: { backgroundColor: '#2a2a2a', color: '#ffffff', padding: 12, borderRadius: 8, fontSize: 16, borderWidth: 1, borderColor: '#444444', }, error: { color: '#ef4444', textAlign: 'center', marginBottom: 16, fontWeight: '600', }, button: { backgroundColor: '#3b82f6', padding: 14, borderRadius: 8, alignItems: 'center', marginTop: 24, }, buttonText: { color: '#ffffff', fontWeight: 'bold', fontSize: 16, }, disabled: { opacity: 0.7, }, }); "@ Write-CoreFile (Join-Path $AppPath "(auth)\login.tsx") $loginScreen |