chore: durcit la configuration pour la production
- mobile: externalise l'URL d'API via EXPO_PUBLIC_API_URL (repli localhost en dev) - backend: .env.example adapté au projet (PostgreSQL, JWT, FRONTEND_URL, Redis) - frontend/mobile: ajoute .env.example (+ exception gitignore front) - mobile: supprime les 4 `any` (helper getApiError, type Feed, glyphMap Ionicons) - backend: normalise le style PSR-12 via Pint (13 fichiers) Tests backend 15/15, mobile tsc 0, Pint pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# URL de l'API REST CESIZen utilisée par l'application mobile.
|
||||
# Préfixe EXPO_PUBLIC_ requis pour être exposée au bundle Expo.
|
||||
# En production, pointer vers l'API HTTPS (ex. https://api.cesizen.fr/api/v1).
|
||||
# En dev, laisser vide : l'app retombe sur localhost / 10.0.2.2 selon la plateforme.
|
||||
EXPO_PUBLIC_API_URL=
|
||||
@@ -3,12 +3,13 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useFeeds } from '@/hooks/useFeeds';
|
||||
import { Colors } from '@/lib/colors';
|
||||
import type { Feed } from '@/types';
|
||||
|
||||
export default function InfoListScreen() {
|
||||
const { data: feeds, isLoading } = useFeeds();
|
||||
const router = useRouter();
|
||||
|
||||
const renderItem = ({ item }: { item: any }) => (
|
||||
const renderItem = ({ item }: { item: Feed }) => (
|
||||
<TouchableOpacity
|
||||
style={styles.card}
|
||||
onPress={() => router.push(`/(tabs)/info/${item.slug}`)}
|
||||
|
||||
@@ -67,10 +67,10 @@ export default function ProfileScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
function MenuItem({ icon, label }: { icon: string; label: string }) {
|
||||
function MenuItem({ icon, label }: { icon: keyof typeof Ionicons.glyphMap; label: string }) {
|
||||
return (
|
||||
<TouchableOpacity style={styles.menuItem}>
|
||||
<Ionicons name={icon as any} size={20} color={Colors.gray[600]} />
|
||||
<Ionicons name={icon} size={20} color={Colors.gray[600]} />
|
||||
<Text style={styles.menuLabel}>{label}</Text>
|
||||
<Ionicons name="chevron-forward" size={18} color={Colors.gray[300]} />
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
||||
import { Link, useRouter } from 'expo-router';
|
||||
import { useAuthStore } from '@/lib/auth-store';
|
||||
import { getApiError } from '@/lib/api';
|
||||
import { Colors } from '@/lib/colors';
|
||||
|
||||
export default function LoginScreen() {
|
||||
@@ -20,8 +21,8 @@ export default function LoginScreen() {
|
||||
try {
|
||||
await login(email, password);
|
||||
router.replace('/(tabs)');
|
||||
} catch (error: any) {
|
||||
Alert.alert('Erreur', error.response?.data?.message || 'Identifiants incorrects');
|
||||
} catch (error) {
|
||||
Alert.alert('Erreur', getApiError(error).message || 'Identifiants incorrects');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, KeyboardAvo
|
||||
import { Link, useRouter } from 'expo-router';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useAuthStore } from '@/lib/auth-store';
|
||||
import { getApiError } from '@/lib/api';
|
||||
import { Colors } from '@/lib/colors';
|
||||
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
@@ -43,9 +44,10 @@ export default function RegisterScreen() {
|
||||
consentement_rgpd: consentement,
|
||||
});
|
||||
router.replace('/(tabs)');
|
||||
} catch (error: any) {
|
||||
const msg = error.response?.data?.message || 'Erreur lors de l\'inscription';
|
||||
const apiErrors = error.response?.data?.errors;
|
||||
} catch (error) {
|
||||
const data = getApiError(error);
|
||||
const msg = data.message || 'Erreur lors de l\'inscription';
|
||||
const apiErrors = data.errors;
|
||||
if (apiErrors) {
|
||||
const mapped: Record<string, string> = {};
|
||||
Object.entries(apiErrors).forEach(([k, v]) => { mapped[k] = Array.isArray(v) ? v[0] as string : String(v); });
|
||||
|
||||
+25
-6
@@ -1,12 +1,31 @@
|
||||
import axios from 'axios';
|
||||
import axios, { isAxiosError } from 'axios';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
const API_URL = Platform.select({
|
||||
android: 'http://10.0.2.2:8000/api/v1',
|
||||
ios: 'http://localhost:8000/api/v1',
|
||||
default: 'http://localhost:8000/api/v1',
|
||||
});
|
||||
/** Forme standard d'une réponse d'erreur de l'API Laravel. */
|
||||
export interface ApiErrorData {
|
||||
message?: string;
|
||||
errors?: Record<string, string[] | string>;
|
||||
}
|
||||
|
||||
/** Extrait proprement le corps d'erreur d'une exception (sans recourir à `any`). */
|
||||
export function getApiError(error: unknown): ApiErrorData {
|
||||
if (isAxiosError(error)) {
|
||||
return (error.response?.data as ApiErrorData) ?? {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// En production, définir EXPO_PUBLIC_API_URL (ex. https://api.cesizen.fr/api/v1)
|
||||
// dans l'environnement de build (eas.json / .env). À défaut, on retombe sur la
|
||||
// configuration de développement local par plateforme (émulateur Android = 10.0.2.2).
|
||||
const API_URL =
|
||||
process.env.EXPO_PUBLIC_API_URL ||
|
||||
Platform.select({
|
||||
android: 'http://10.0.2.2:8000/api/v1',
|
||||
ios: 'http://localhost:8000/api/v1',
|
||||
default: 'http://localhost:8000/api/v1',
|
||||
});
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_URL,
|
||||
|
||||
Reference in New Issue
Block a user