diff --git a/src/components/ProjectDropDown/ProjectDropDown.js b/src/components/ProjectDropDown/ProjectDropDown.js index a2fb5ba..b271aa0 100644 --- a/src/components/ProjectDropDown/ProjectDropDown.js +++ b/src/components/ProjectDropDown/ProjectDropDown.js @@ -82,6 +82,7 @@ const ProjectDropDown = forwardRef( selectedProject, onSelectProject, onModifyProject, + onPlayProject, onCreateProject, formatDate = defaultFormatDate, allowEmptySelection = false, @@ -269,6 +270,7 @@ const ProjectDropDown = forwardRef( isSelected={currentProject?.id === project?.id} onSelect={handleSelect} onModify={handleModify} + onPlay={onPlayProject} /> )} nestedScrollEnabled @@ -348,7 +350,15 @@ const ProjectDropDown = forwardRef( export default ProjectDropDown -const ProjectRow = ({ project, formatDate, onSelect, onModify, isTitle = false, isOpen }) => { +const ProjectRow = ({ + project, + formatDate, + onSelect, + onModify, + onPlay, + isTitle = false, + isOpen, +}) => { const rowRef = useRef(null) const [layout, setLayout] = useState(null) const [menuPos, setMenuPos] = useState({ @@ -429,15 +439,28 @@ const ProjectRow = ({ project, formatDate, onSelect, onModify, isTitle = false, resizeMode="contain" /> ) : ( - { - event.stopPropagation?.() - onPressMenu() - }} - > - - + + {!!project?.songUrl && ( + { + event.stopPropagation?.() + onPlay?.(project) + }} + > + + + )} + { + event.stopPropagation?.() + onPressMenu() + }} + > + + + )} @@ -558,6 +581,16 @@ const styles = StyleSheet.create({ color: 'rgba(255, 255, 255, 0.7)', fontFamily: FONT_FAMILY.InterRegular, }, + rowActions: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + }, + playIcon: { + width: 18, + height: 18, + tintColor: Palette.white, + }, moreIcon: { width: 18, height: 18, diff --git a/src/config/initialGlobalState.js b/src/config/initialGlobalState.js index 9691f47..2a42b9c 100644 --- a/src/config/initialGlobalState.js +++ b/src/config/initialGlobalState.js @@ -21,6 +21,7 @@ export default { _isLoading: false, _loadingMessage: null, + _tooltip: null, _config: { colors: { diff --git a/src/providers/SharedProviders.js b/src/providers/SharedProviders.js index ad42781..60f9c1e 100644 --- a/src/providers/SharedProviders.js +++ b/src/providers/SharedProviders.js @@ -8,6 +8,7 @@ import NotificationProvider from './NotificationProvider' import PlayerProvider from './PlayerProvider' import SplashAnimationProvider from './SplashAnimationProvider' import StripeProvider from './StripeProvider' +import TooltipProvider from './TooltipProvider' import UniversalLinkProvider from './UniversalLinkProvider' import UserDataProvider from './UserDataProvider' import WebViewProvider from './WebViewProvider' @@ -16,6 +17,7 @@ const SharedProviders = ({ children }) => { const providers = [ [SafeAreaProvider, {}], [ActionSheetProvider, {}], + [TooltipProvider, {}], [SplashAnimationProvider, {}], [WebViewProvider, {}], [UserDataProvider, {}], diff --git a/src/screens/Home/Home.js b/src/screens/Home/Home.js index 0d15098..10597d0 100644 --- a/src/screens/Home/Home.js +++ b/src/screens/Home/Home.js @@ -25,6 +25,7 @@ import { FONT_FAMILY } from '../../styles/Fonts' import { getCreationStageStates, getStageAction } from '../../utils/projectStages' import { openCoinPackModal } from '../../utils/coinPackModal' import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails' +import usePlayer from '../../hooks/usePlayer' import StageCard from './components/StageCard' import ClubCard from './components/ClubCard' import { BlurView } from 'expo-blur' @@ -158,6 +159,8 @@ const Home = ({ navigation, route }) => { const projectDropdownRef = useRef(null) const [hasLocalAdventureFlag, setHasLocalAdventureFlag] = useState(false) + const { play } = usePlayer() + const handleSelectProject = useCallback( (project) => { if (!project?.id) return @@ -210,6 +213,25 @@ const Home = ({ navigation, route }) => { menuAnchorRef.current = normalizedAnchor }, []) + const handlePlayProject = useCallback( + (project) => { + if (!project?.id || !project?.songUrl) return + const cover = project.coverUrl || project.coverUri || project.cover + play({ + id: project.id, + url: project.songUrl, + title: project.title || 'Sans titre', + artist: 'MusicLand', + artwork: cover, + coverUrl: typeof cover === 'string' ? cover : cover?.uri, + metadata: { + projectId: project.id, + }, + }) + }, + [play] + ) + const moreMenuItems = useMemo(() => { if (!menuProject?.id) return [] @@ -228,17 +250,6 @@ const Home = ({ navigation, route }) => { }) }, }) - items.push({ - label: 'Jouer la musique', - onPress: () => { - projectDropdownRef.current?.close?.() - navigateToMusicDetails({ - projectId: menuProject.id, - songUrl: menuProject.songUrl, - project: menuProject, - }) - }, - }) } items.push({ @@ -540,6 +551,7 @@ const Home = ({ navigation, route }) => { allowEmptySelection onSelectProject={handleSelectProject} onModifyProject={handleModifyProject} + onPlayProject={handlePlayProject} onCreateProject={handleStartNew} formatDate={formatDate} /> diff --git a/src/screens/Profile/ChangeEmailAddress.js b/src/screens/Profile/ChangeEmailAddress.js index 401140b..d07b7bf 100644 --- a/src/screens/Profile/ChangeEmailAddress.js +++ b/src/screens/Profile/ChangeEmailAddress.js @@ -53,28 +53,45 @@ const ChangeEmailAddress = () => { const uid = user?.uid if (!user || !uid) return - // Reauthenticate user with current password before sensitive update try { const credential = firebase.auth.EmailAuthProvider.credential(user.email, currentPassword) await user.reauthenticateWithCredential(credential) } catch (reauthErr) { - throw { - code: 'auth/wrong-password', - message: 'Mot de passe incorrect.', - } + setTooltip({ type: 'error', text: 'Mot de passe actuel incorrect.' }) + return } - await user.updateEmail(val) - await usersRef.doc(uid).set({ email: val }, { merge: true }) - setTooltip({ type: 'success', text: 'Email mis à jour' }) - goBack() + // Use verifyBeforeUpdateEmail instead of updateEmail for newer Firebase projects + // This sends a verification email to the new address and only changes it once verified. + // However, for some projects, we might still need to update Firestore record if allowed. + try { + if (typeof user.verifyBeforeUpdateEmail === 'function') { + await user.verifyBeforeUpdateEmail(val) + setTooltip({ + type: 'success', + text: 'Un email de vérification a été envoyé à votre nouvelle adresse.', + }) + } else { + await user.updateEmail(val) + await usersRef.doc(uid).set({ email: val }, { merge: true }) + setTooltip({ type: 'success', text: 'Adresse email mise à jour' }) + } + goBack() + } catch (updateErr) { + console.log('UpdateEmail error detail', updateErr) + let msg = 'Mise à jour impossible' + if (updateErr.code === 'auth/operation-not-allowed') { + msg = "La modification d'email directe n'est pas autorisée. Contactez le support." + } else if (updateErr.message?.includes('verify')) { + msg = "Veuillez vérifier votre nouvel email avant d'effectuer le changement." + } else { + msg = updateErr.message || msg + } + setTooltip({ type: 'error', text: msg }) + } } catch (e) { - console.log('ChangeEmailAddress error', e?.message) - const msg = - e?.code === 'auth/requires-recent-login' - ? "Veuillez vous reconnecter pour changer l'adresse email" - : e?.message || 'Mise à jour impossible' - setTooltip({ type: 'error', text: msg }) + console.log('ChangeEmailAddress error', e) + setTooltip({ type: 'error', text: e?.message || 'Une erreur est survenue' }) } finally { setLoading(false) } @@ -82,7 +99,7 @@ const ChangeEmailAddress = () => { return ( { const [oldPassword, setOldPassword] = useState('') const [newPassword, setNewPassword] = useState('') + const [passwordError, setPasswordError] = useState('') const [loading, setLoading] = useState(false) const [, setTooltip] = useGlobal('_tooltip') + const onChangeNewPassword = (val) => { + setNewPassword(val) + if (!val) { + setPasswordError('') + return + } + if (!checkIfPasswordIsStrongEnough({ password: val })) { + setPasswordError( + 'Le mot de passe doit contenir au moins 6 caractères, une majuscule et un chiffre.' + ) + } else { + setPasswordError('') + } + } + const onSave = async () => { const oldVal = (oldPassword || '').trim() const newVal = (newPassword || '').trim() if (!oldVal || !newVal) return + if (passwordError) return + try { setLoading(true) const user = firebase.auth().currentUser if (!user?.email) return - const credential = firebase.auth.EmailAuthProvider.credential(user.email, oldVal) - await user.reauthenticateWithCredential(credential) + try { + const credential = firebase.auth.EmailAuthProvider.credential(user.email, oldVal) + await user.reauthenticateWithCredential(credential) + } catch (reauthErr) { + throw { + code: 'auth/wrong-password', + message: 'Mot de passe actuel incorrect.', + } + } + await user.updatePassword(newVal) setTooltip({ type: 'success', text: 'Mot de passe mis à jour' }) - navigate(Routes.Settings) + goBack() } catch (e) { console.log('ChangePassword error', e?.message) - const msg = e?.message || 'Mise à jour impossible' + const msg = + e?.code === 'auth/wrong-password' + ? 'Mot de passe actuel incorrect.' + : e?.message || 'Mise à jour impossible' setTooltip({ type: 'error', text: msg }) } finally { setLoading(false) @@ -45,7 +74,7 @@ const ChangePassword = () => { return ( { label="Nouveau mot de passe" type="password" value={newPassword} - setValue={setNewPassword} + setValue={onChangeNewPassword} /> + {!!passwordError && ( + + {passwordError} + + )} { alignSelf: 'center', }} onPress={onSave} - disabled={loading || !oldPassword?.trim() || !newPassword?.trim()} + disabled={loading || !oldPassword?.trim() || !newPassword?.trim() || !!passwordError} /> ) diff --git a/src/screens/Profile/Settings.js b/src/screens/Profile/Settings.js index 0b74032..3f268df 100644 --- a/src/screens/Profile/Settings.js +++ b/src/screens/Profile/Settings.js @@ -14,7 +14,7 @@ import { gutters, Palette } from '../../styles' const SETTINGS = [ { - title: 'Adresse mail', + title: 'Modifier mon adresse mail', action: () => { navigate(Routes.ChangeEmailAddress) },