feat: iphone app
This commit is contained in:
@@ -2,11 +2,12 @@ import { Stack } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { useFonts } from 'expo-font';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import {AuthProvider} from '../src/firebase/auth-state';
|
||||
import { AppProvider } from '../src/state/app-state';
|
||||
import { theme } from '../src/design/theme';
|
||||
|
||||
export default function Layout() {
|
||||
const [loaded, error] = useFonts({Inter: require('../assets/fonts/Inter.ttf')});
|
||||
if (!loaded && !error) return null;
|
||||
return <SafeAreaProvider><AppProvider><StatusBar style="light" /><Stack screenOptions={{headerShown: false, contentStyle: {backgroundColor: theme.background}, animation: 'fade', gestureEnabled: true}} /></AppProvider></SafeAreaProvider>;
|
||||
return <SafeAreaProvider><AuthProvider><AppProvider><StatusBar style="light" /><Stack screenOptions={{headerShown: false, contentStyle: {backgroundColor: theme.background}, animation: 'fade', gestureEnabled: true}} /></AppProvider></AuthProvider></SafeAreaProvider>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React,{useState} from 'react';
|
||||
import {Alert,ActivityIndicator,Pressable,ScrollView,Text,TextInput,View} from 'react-native';
|
||||
import {useRouter} from 'expo-router';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
import {createUserWithEmailAndPassword,signInWithEmailAndPassword,sendPasswordResetEmail,signOut} from '@react-native-firebase/auth';
|
||||
import {auth} from '../src/firebase/client';
|
||||
import {useAuth} from '../src/firebase/auth-state';
|
||||
import {useAppState} from '../src/state/app-state';
|
||||
import {theme} from '../src/design/theme';
|
||||
|
||||
export default function AccountScreen(){
|
||||
const router=useRouter();const {user}=useAuth();const {syncStatus,syncError,retry}=useAppState();
|
||||
const [email,setEmail]=useState(''),[password,setPassword]=useState(''),[register,setRegister]=useState(false),[busy,setBusy]=useState(false),[error,setError]=useState<string|null>(null);
|
||||
async function submit(){setBusy(true);setError(null);try{if(register)await createUserWithEmailAndPassword(auth,email.trim(),password);else await signInWithEmailAndPassword(auth,email.trim(),password);setPassword('');}catch(e){const code=(e as {code?:string}).code;setError(code==='auth/network-request-failed'?'Connexion indisponible. Réessaie lorsque le réseau est disponible.':code==='auth/weak-password'?'Choisis un mot de passe d’au moins 6 caractères.':code==='auth/email-already-in-use'?'Un compte utilise déjà cette adresse.':'Connexion impossible. Vérifie ton adresse et ton mot de passe.');}finally{setBusy(false);}}
|
||||
const button=(title:string,action:()=>void)=> <Pressable accessibilityRole="button" disabled={busy} onPress={action} style={{backgroundColor:theme.accent,borderRadius:28,padding:16,marginTop:16}}><Text style={{color:'#06101d',fontSize:16,textAlign:'center',fontWeight:'600'}}>{title}</Text></Pressable>;
|
||||
return <SafeAreaView style={{flex:1,backgroundColor:theme.background}}><ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={{padding:24,gap:18}}><Pressable accessibilityRole="button" onPress={()=>router.canGoBack()?router.back():router.replace('/')}><Text style={{color:theme.accent}}>Retour</Text></Pressable><Text style={{fontSize:28,color:theme.text,fontWeight:'600'}}>Mon compte</Text>
|
||||
{user?<><Text selectable style={{color:theme.text}}>{user.email}</Text><Text accessibilityLiveRegion="polite" style={{color:theme.muted}}>{syncStatus==='synced'?'Profil synchronisé':syncStatus==='pending'?'Modifications en attente':syncStatus==='loading'?'Chargement…':'Synchronisation indisponible'}</Text>{syncError&&<Text style={{color:'#ffb4ab'}}>{syncError}</Text>}{syncStatus==='error'&&button('Réessayer',retry)}{button('Se déconnecter',()=>{if(syncStatus==='pending'||syncStatus==='error'){Alert.alert('Modifications en attente','Elles resteront sur cet appareil, liées à ce compte.',[{text:'Annuler',style:'cancel'},{text:'Se déconnecter',onPress:()=>void signOut(auth).catch(()=>setError('Déconnexion impossible.'))}]);}else void signOut(auth).catch(()=>setError('Déconnexion impossible.'));})}</>:<><Text style={{color:theme.muted}}>Connecte-toi pour retrouver ton profil et tes préférences. Les données du mode local restent sur cet appareil.</Text><TextInput accessibilityLabel="Adresse e-mail" placeholder="Adresse e-mail" placeholderTextColor={theme.muted} autoCapitalize="none" autoCorrect={false} keyboardType="email-address" textContentType="emailAddress" value={email} onChangeText={setEmail} style={{color:theme.text,borderWidth:1,borderColor:theme.muted,borderRadius:14,padding:16}}/><TextInput accessibilityLabel="Mot de passe" placeholder="Mot de passe" placeholderTextColor={theme.muted} secureTextEntry textContentType={register?'newPassword':'password'} value={password} onChangeText={setPassword} style={{color:theme.text,borderWidth:1,borderColor:theme.muted,borderRadius:14,padding:16}}/>{button(register?'Créer mon compte':'Se connecter',()=>void submit())}<Pressable disabled={busy} onPress={()=>{setRegister(!register);setError(null);}}><Text style={{color:theme.accent}}>{register?'J’ai déjà un compte':'Créer un compte'}</Text></Pressable><Pressable disabled={busy} onPress={()=>{if(!email.trim()){setError('Renseigne ton adresse e-mail.');return;}setBusy(true);void sendPasswordResetEmail(auth,email.trim()).then(()=>Alert.alert('Demande envoyée','Si un compte correspond à cette adresse, tu recevras un lien de réinitialisation.')).catch(()=>setError('Impossible d’envoyer la demande. Vérifie ton adresse et réessaie.')).finally(()=>setBusy(false));}}><Text style={{color:theme.accent}}>Mot de passe oublié</Text></Pressable></>}
|
||||
{busy&&<ActivityIndicator color={theme.accent}/>}{error&&<Text accessibilityRole="alert" style={{color:'#ffb4ab'}}>{error}</Text>}
|
||||
</ScrollView></SafeAreaView>;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Redirect } from 'expo-router';
|
||||
import { useAppState } from '../src/state/app-state';
|
||||
export default function Index() {
|
||||
const {state, ready} = useAppState();
|
||||
if (!ready) return null;
|
||||
const {state, ready, syncStatus} = useAppState();
|
||||
if (!ready || syncStatus === 'loading') return null;
|
||||
return <Redirect href={{pathname: '/screen/[id]', params: {id: state.onboarded ? (state.variant === 'watch' ? '560-251' : '431-2') : '2-2'}}} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user