feat: change mail and change password
This commit is contained in:
@@ -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,15 +439,28 @@ const ProjectRow = ({ project, formatDate, onSelect, onModify, isTitle = false,
|
|||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Pressable
|
<View style={styles.rowActions}>
|
||||||
hitSlop={8}
|
{!!project?.songUrl && (
|
||||||
onPress={(event) => {
|
<Pressable
|
||||||
event.stopPropagation?.()
|
hitSlop={8}
|
||||||
onPressMenu()
|
onPress={(event) => {
|
||||||
}}
|
event.stopPropagation?.()
|
||||||
>
|
onPlay?.(project)
|
||||||
<Image source={icons.threeDots} style={styles.moreIcon} resizeMode="contain" />
|
}}
|
||||||
</Pressable>
|
>
|
||||||
|
<Image source={icons.play} style={styles.playIcon} resizeMode="contain" />
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
|
<Pressable
|
||||||
|
hitSlop={8}
|
||||||
|
onPress={(event) => {
|
||||||
|
event.stopPropagation?.()
|
||||||
|
onPressMenu()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image source={icons.threeDots} style={styles.moreIcon} resizeMode="contain" />
|
||||||
|
</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,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default {
|
|||||||
|
|
||||||
_isLoading: false,
|
_isLoading: false,
|
||||||
_loadingMessage: null,
|
_loadingMessage: null,
|
||||||
|
_tooltip: null,
|
||||||
|
|
||||||
_config: {
|
_config: {
|
||||||
colors: {
|
colors: {
|
||||||
|
|||||||
@@ -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
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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.',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await user.updateEmail(val)
|
// Use verifyBeforeUpdateEmail instead of updateEmail for newer Firebase projects
|
||||||
await usersRef.doc(uid).set({ email: val }, { merge: true })
|
// This sends a verification email to the new address and only changes it once verified.
|
||||||
setTooltip({ type: 'success', text: 'Email mis à jour' })
|
// However, for some projects, we might still need to update Firestore record if allowed.
|
||||||
goBack()
|
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) {
|
} catch (e) {
|
||||||
console.log('ChangeEmailAddress error', e?.message)
|
console.log('ChangeEmailAddress error', e)
|
||||||
const msg =
|
setTooltip({ type: 'error', text: e?.message || 'Une erreur est survenue' })
|
||||||
e?.code === 'auth/requires-recent-login'
|
|
||||||
? "Veuillez vous reconnecter pour changer l'adresse email"
|
|
||||||
: e?.message || 'Mise à jour impossible'
|
|
||||||
setTooltip({ type: 'error', text: msg })
|
|
||||||
} 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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
const credential = firebase.auth.EmailAuthProvider.credential(user.email, oldVal)
|
try {
|
||||||
await user.reauthenticateWithCredential(credential)
|
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)
|
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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user