feat: change mail and change password

This commit is contained in:
2026-03-05 13:58:26 +01:00
parent 849d3a1c43
commit 7ae41d3225
7 changed files with 153 additions and 47 deletions
@@ -82,6 +82,7 @@ const ProjectDropDown = forwardRef(
selectedProject, selectedProject,
onSelectProject, onSelectProject,
onModifyProject, onModifyProject,
onPlayProject,
onCreateProject, onCreateProject,
formatDate = defaultFormatDate, formatDate = defaultFormatDate,
allowEmptySelection = false, allowEmptySelection = false,
@@ -269,6 +270,7 @@ const ProjectDropDown = forwardRef(
isSelected={currentProject?.id === project?.id} isSelected={currentProject?.id === project?.id}
onSelect={handleSelect} onSelect={handleSelect}
onModify={handleModify} onModify={handleModify}
onPlay={onPlayProject}
/> />
)} )}
nestedScrollEnabled nestedScrollEnabled
@@ -348,7 +350,15 @@ const ProjectDropDown = forwardRef(
export default ProjectDropDown 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 rowRef = useRef(null)
const [layout, setLayout] = useState(null) const [layout, setLayout] = useState(null)
const [menuPos, setMenuPos] = useState({ const [menuPos, setMenuPos] = useState({
@@ -429,6 +439,18 @@ const ProjectRow = ({ project, formatDate, onSelect, onModify, isTitle = false,
resizeMode="contain" resizeMode="contain"
/> />
) : ( ) : (
<View style={styles.rowActions}>
{!!project?.songUrl && (
<Pressable
hitSlop={8}
onPress={(event) => {
event.stopPropagation?.()
onPlay?.(project)
}}
>
<Image source={icons.play} style={styles.playIcon} resizeMode="contain" />
</Pressable>
)}
<Pressable <Pressable
hitSlop={8} hitSlop={8}
onPress={(event) => { onPress={(event) => {
@@ -438,6 +460,7 @@ const ProjectRow = ({ project, formatDate, onSelect, onModify, isTitle = false,
> >
<Image source={icons.threeDots} style={styles.moreIcon} resizeMode="contain" /> <Image source={icons.threeDots} style={styles.moreIcon} resizeMode="contain" />
</Pressable> </Pressable>
</View>
)} )}
</ThemedBlurView> </ThemedBlurView>
</Pressable> </Pressable>
@@ -558,6 +581,16 @@ const styles = StyleSheet.create({
color: 'rgba(255, 255, 255, 0.7)', color: 'rgba(255, 255, 255, 0.7)',
fontFamily: FONT_FAMILY.InterRegular, fontFamily: FONT_FAMILY.InterRegular,
}, },
rowActions: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
playIcon: {
width: 18,
height: 18,
tintColor: Palette.white,
},
moreIcon: { moreIcon: {
width: 18, width: 18,
height: 18, height: 18,
+1
View File
@@ -21,6 +21,7 @@ export default {
_isLoading: false, _isLoading: false,
_loadingMessage: null, _loadingMessage: null,
_tooltip: null,
_config: { _config: {
colors: { colors: {
+2
View File
@@ -8,6 +8,7 @@ import NotificationProvider from './NotificationProvider'
import PlayerProvider from './PlayerProvider' import PlayerProvider from './PlayerProvider'
import SplashAnimationProvider from './SplashAnimationProvider' import SplashAnimationProvider from './SplashAnimationProvider'
import StripeProvider from './StripeProvider' import StripeProvider from './StripeProvider'
import TooltipProvider from './TooltipProvider'
import UniversalLinkProvider from './UniversalLinkProvider' import UniversalLinkProvider from './UniversalLinkProvider'
import UserDataProvider from './UserDataProvider' import UserDataProvider from './UserDataProvider'
import WebViewProvider from './WebViewProvider' import WebViewProvider from './WebViewProvider'
@@ -16,6 +17,7 @@ const SharedProviders = ({ children }) => {
const providers = [ const providers = [
[SafeAreaProvider, {}], [SafeAreaProvider, {}],
[ActionSheetProvider, {}], [ActionSheetProvider, {}],
[TooltipProvider, {}],
[SplashAnimationProvider, {}], [SplashAnimationProvider, {}],
[WebViewProvider, {}], [WebViewProvider, {}],
[UserDataProvider, {}], [UserDataProvider, {}],
+23 -11
View File
@@ -25,6 +25,7 @@ import { FONT_FAMILY } from '../../styles/Fonts'
import { getCreationStageStates, getStageAction } from '../../utils/projectStages' import { getCreationStageStates, getStageAction } from '../../utils/projectStages'
import { openCoinPackModal } from '../../utils/coinPackModal' import { openCoinPackModal } from '../../utils/coinPackModal'
import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails' import useNavigateToMusicDetails from '../../hooks/useNavigateToMusicDetails'
import usePlayer from '../../hooks/usePlayer'
import StageCard from './components/StageCard' import StageCard from './components/StageCard'
import ClubCard from './components/ClubCard' import ClubCard from './components/ClubCard'
import { BlurView } from 'expo-blur' import { BlurView } from 'expo-blur'
@@ -158,6 +159,8 @@ const Home = ({ navigation, route }) => {
const projectDropdownRef = useRef(null) const projectDropdownRef = useRef(null)
const [hasLocalAdventureFlag, setHasLocalAdventureFlag] = useState(false) const [hasLocalAdventureFlag, setHasLocalAdventureFlag] = useState(false)
const { play } = usePlayer()
const handleSelectProject = useCallback( const handleSelectProject = useCallback(
(project) => { (project) => {
if (!project?.id) return if (!project?.id) return
@@ -210,6 +213,25 @@ const Home = ({ navigation, route }) => {
menuAnchorRef.current = normalizedAnchor 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(() => { const moreMenuItems = useMemo(() => {
if (!menuProject?.id) return [] 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({ items.push({
@@ -540,6 +551,7 @@ const Home = ({ navigation, route }) => {
allowEmptySelection allowEmptySelection
onSelectProject={handleSelectProject} onSelectProject={handleSelectProject}
onModifyProject={handleModifyProject} onModifyProject={handleModifyProject}
onPlayProject={handlePlayProject}
onCreateProject={handleStartNew} onCreateProject={handleStartNew}
formatDate={formatDate} formatDate={formatDate}
/> />
+30 -13
View File
@@ -53,28 +53,45 @@ const ChangeEmailAddress = () => {
const uid = user?.uid const uid = user?.uid
if (!user || !uid) return if (!user || !uid) return
// Reauthenticate user with current password before sensitive update
try { try {
const credential = firebase.auth.EmailAuthProvider.credential(user.email, currentPassword) const credential = firebase.auth.EmailAuthProvider.credential(user.email, currentPassword)
await user.reauthenticateWithCredential(credential) await user.reauthenticateWithCredential(credential)
} catch (reauthErr) { } catch (reauthErr) {
throw { setTooltip({ type: 'error', text: 'Mot de passe actuel incorrect.' })
code: 'auth/wrong-password', return
message: 'Mot de passe incorrect.',
}
} }
// 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 user.updateEmail(val)
await usersRef.doc(uid).set({ email: val }, { merge: true }) await usersRef.doc(uid).set({ email: val }, { merge: true })
setTooltip({ type: 'success', text: 'Email mis à jour' }) setTooltip({ type: 'success', text: 'Adresse email mise à jour' })
}
goBack() goBack()
} catch (e) { } catch (updateErr) {
console.log('ChangeEmailAddress error', e?.message) console.log('UpdateEmail error detail', updateErr)
const msg = let msg = 'Mise à jour impossible'
e?.code === 'auth/requires-recent-login' if (updateErr.code === 'auth/operation-not-allowed') {
? "Veuillez vous reconnecter pour changer l'adresse email" msg = "La modification d'email directe n'est pas autorisée. Contactez le support."
: e?.message || 'Mise à jour impossible' } 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 }) setTooltip({ type: 'error', text: msg })
}
} catch (e) {
console.log('ChangeEmailAddress error', e)
setTooltip({ type: 'error', text: e?.message || 'Une erreur est survenue' })
} finally { } finally {
setLoading(false) setLoading(false)
} }
@@ -82,7 +99,7 @@ const ChangeEmailAddress = () => {
return ( return (
<Page <Page
headerType="NAVIGATE" headerType="NAVIGATE"
title="Paramètres" title="Modifier mon email"
backgroundImg={background.profileBG} backgroundImg={background.profileBG}
contentContainerStyle={{ contentContainerStyle={{
paddingBottom: gutters * 4, paddingBottom: gutters * 4,
+48 -7
View File
@@ -3,13 +3,13 @@ import React, { useState } from 'react'
import { Platform, Pressable, Text, View } from 'react-native' import { Platform, Pressable, Text, View } from 'react-native'
import { responsiveHeight } from 'react-native-responsive-dimensions' import { responsiveHeight } from 'react-native-responsive-dimensions'
import { useGlobal } from 'reactn' import { useGlobal } from 'reactn'
import { checkIfPasswordIsStrongEnough } from '../../actions/signupActions'
import { background } from '../../assets' import { background } from '../../assets'
import GradientButton from '../../components/GradientButton' import GradientButton from '../../components/GradientButton'
import firebase from '../../config/firebase' import firebase from '../../config/firebase'
import { isWeb } from '../../hooks/useLayoutType' import { isWeb } from '../../hooks/useLayoutType'
import Page from '../../layouts/Page' import Page from '../../layouts/Page'
import { Routes } from '../../navigation' import { goBack } from '../../navigation/NavigationService'
import { navigate } from '../../navigation/NavigationService'
import { gutters, Palette } from '../../styles' import { gutters, Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts' import { FONT_FAMILY } from '../../styles/Fonts'
import EditInput from './components/EditInput' import EditInput from './components/EditInput'
@@ -17,26 +17,55 @@ import EditInput from './components/EditInput'
const ChangePassword = () => { const ChangePassword = () => {
const [oldPassword, setOldPassword] = useState('') const [oldPassword, setOldPassword] = useState('')
const [newPassword, setNewPassword] = useState('') const [newPassword, setNewPassword] = useState('')
const [passwordError, setPasswordError] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [, setTooltip] = useGlobal('_tooltip') 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 onSave = async () => {
const oldVal = (oldPassword || '').trim() const oldVal = (oldPassword || '').trim()
const newVal = (newPassword || '').trim() const newVal = (newPassword || '').trim()
if (!oldVal || !newVal) return if (!oldVal || !newVal) return
if (passwordError) return
try { try {
setLoading(true) setLoading(true)
const user = firebase.auth().currentUser const user = firebase.auth().currentUser
if (!user?.email) return if (!user?.email) return
try {
const credential = firebase.auth.EmailAuthProvider.credential(user.email, oldVal) const credential = firebase.auth.EmailAuthProvider.credential(user.email, oldVal)
await user.reauthenticateWithCredential(credential) await user.reauthenticateWithCredential(credential)
} catch (reauthErr) {
throw {
code: 'auth/wrong-password',
message: 'Mot de passe actuel incorrect.',
}
}
await user.updatePassword(newVal) await user.updatePassword(newVal)
setTooltip({ type: 'success', text: 'Mot de passe mis à jour' }) setTooltip({ type: 'success', text: 'Mot de passe mis à jour' })
navigate(Routes.Settings) goBack()
} catch (e) { } catch (e) {
console.log('ChangePassword error', e?.message) 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 }) setTooltip({ type: 'error', text: msg })
} finally { } finally {
setLoading(false) setLoading(false)
@@ -45,7 +74,7 @@ const ChangePassword = () => {
return ( return (
<Page <Page
headerType="NAVIGATE" headerType="NAVIGATE"
title="Paramètres" title="Modifier mon mot de passe"
backgroundImg={background.profileBG} backgroundImg={background.profileBG}
contentContainerStyle={{ contentContainerStyle={{
paddingBottom: gutters * 4, paddingBottom: gutters * 4,
@@ -81,8 +110,20 @@ const ChangePassword = () => {
label="Nouveau mot de passe" label="Nouveau mot de passe"
type="password" type="password"
value={newPassword} value={newPassword}
setValue={setNewPassword} setValue={onChangeNewPassword}
/> />
{!!passwordError && (
<Text
style={{
fontSize: 12,
color: Palette.red,
fontFamily: FONT_FAMILY.InterRegular,
marginTop: 6,
}}
>
{passwordError}
</Text>
)}
<Pressable> <Pressable>
<Text <Text
style={{ style={{
@@ -103,7 +144,7 @@ const ChangePassword = () => {
alignSelf: 'center', alignSelf: 'center',
}} }}
onPress={onSave} onPress={onSave}
disabled={loading || !oldPassword?.trim() || !newPassword?.trim()} disabled={loading || !oldPassword?.trim() || !newPassword?.trim() || !!passwordError}
/> />
</Page> </Page>
) )
+1 -1
View File
@@ -14,7 +14,7 @@ import { gutters, Palette } from '../../styles'
const SETTINGS = [ const SETTINGS = [
{ {
title: 'Adresse mail', title: 'Modifier mon adresse mail',
action: () => { action: () => {
navigate(Routes.ChangeEmailAddress) navigate(Routes.ChangeEmailAddress)
}, },