feat: mobile app complet + flashcards fixes + drag handle améliorations
Mobile app: - Révision flashcards : liste decks, session flip-card SM-2, couleurs harmonisées web - Génération flashcards depuis note (FlashcardSheet + route /api/mobile/flashcards/generate) - Audio Whisper : hook useAudioRecorder reécrit, MicButton avec erreurs - IA : AISheet (améliorer/clarifier/résumer), TitleSheet (titre automatique) - Suppression note (soft delete + confirmation Alert) - Note du jour : titre lisible + HTML (plus JSON TipTap brut) - Parser TipTap→HTML côté mobile (tipTapToHtml) - Icône 🎓 dans header note → génération flashcards - Endpoint flashcardGenerate dans config.ts Web fixes: - Bug flashcards groupées par carnet → deck par note (migration + schema) - Bug filtre 'cartes dues' ignoré (suppression fallback buildSessionQueue) - Suppression UI création deck manuelle (inutile) - Fix setViewType is not defined dans home-client.tsx Drag handle menu: - Fix : clearNodes() avant transformation (heading→liste/code/citation) - Ajout : option 'Texte' (paragraphe) dans Transformer en - Ajout : Monter / Descendre le bloc - Ajout : Copier le contenu du bloc - Fix : sous-menu hover stable (délai 200ms) - Fix : Supprimer en rouge via classe --danger (plus :first-child) - i18n : nouvelles clés dans 15 locales Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import {
|
||||
View, Text, TextInput, TouchableOpacity,
|
||||
KeyboardAvoidingView, Platform, ActivityIndicator,
|
||||
@@ -20,34 +20,35 @@ export default function LoginScreen() {
|
||||
const [googleLoading, setGoogleLoading] = useState(false)
|
||||
const { login, loginWithToken } = useAuthStore()
|
||||
|
||||
// Écoute le deep link memento://auth?token=...
|
||||
useEffect(() => {
|
||||
const handleUrl = async (event: { url: string }) => {
|
||||
const parsed = Linking.parse(event.url)
|
||||
if (parsed.scheme === 'memento' && parsed.path === 'auth') {
|
||||
const p = parsed.queryParams as Record<string, string> | undefined
|
||||
if (p?.error) {
|
||||
Alert.alert('Connexion Google échouée', p.error === 'unauthorized' ? 'Non autorisé' : 'Erreur serveur')
|
||||
setGoogleLoading(false)
|
||||
return
|
||||
}
|
||||
if (p?.token && p?.id) {
|
||||
await loginWithToken(p.token, {
|
||||
id: p.id,
|
||||
name: p.name || null,
|
||||
email: p.email || '',
|
||||
tier: p.tier || 'FREE',
|
||||
})
|
||||
}
|
||||
setGoogleLoading(false)
|
||||
}
|
||||
}
|
||||
// Traitement du deep link OAuth — mémorisé pour éviter les re-créations
|
||||
const handleOAuthCallback = useCallback(async (url: string) => {
|
||||
const parsed = Linking.parse(url)
|
||||
// memento://auth?token=... → scheme='memento', hostname='auth', path=''
|
||||
if (parsed.scheme !== 'memento' || parsed.hostname !== 'auth') return
|
||||
|
||||
const sub = Linking.addEventListener('url', handleUrl)
|
||||
// Vérifier si l'app a été lancée via un deep link
|
||||
Linking.getInitialURL().then((url) => { if (url) handleUrl({ url }) })
|
||||
const p = parsed.queryParams as Record<string, string> | undefined
|
||||
if (p?.error) {
|
||||
Alert.alert('Connexion Google échouée', p.error === 'unauthorized' ? 'Non autorisé' : 'Erreur serveur')
|
||||
setGoogleLoading(false)
|
||||
return
|
||||
}
|
||||
if (p?.token && p?.id) {
|
||||
await loginWithToken(p.token, {
|
||||
id: p.id,
|
||||
name: p.name || null,
|
||||
email: p.email || '',
|
||||
tier: p.tier || 'FREE',
|
||||
})
|
||||
}
|
||||
setGoogleLoading(false)
|
||||
}, [loginWithToken])
|
||||
|
||||
// Écoute le deep link (Android + app lancée via deep link)
|
||||
useEffect(() => {
|
||||
const sub = Linking.addEventListener('url', (event) => handleOAuthCallback(event.url))
|
||||
Linking.getInitialURL().then((url) => { if (url) handleOAuthCallback(url) })
|
||||
return () => sub.remove()
|
||||
}, [])
|
||||
}, [handleOAuthCallback])
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!email.trim() || !password.trim()) return
|
||||
@@ -71,11 +72,13 @@ export default function LoginScreen() {
|
||||
try {
|
||||
const url = `${API_URL}/api/mobile/auth/google-start`
|
||||
const result = await WebBrowser.openAuthSessionAsync(url, 'memento://auth')
|
||||
// Si l'utilisateur ferme sans terminer
|
||||
if (result.type !== 'success') {
|
||||
if (result.type === 'success') {
|
||||
// iOS : le deep link est capturé dans result.url, pas via Linking.addEventListener
|
||||
await handleOAuthCallback(result.url)
|
||||
} else {
|
||||
// Annulé par l'utilisateur
|
||||
setGoogleLoading(false)
|
||||
}
|
||||
// Si succès, le deep link listener prend le relais
|
||||
} catch (e: any) {
|
||||
Alert.alert('Erreur', e.message)
|
||||
setGoogleLoading(false)
|
||||
|
||||
Reference in New Issue
Block a user