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)
},