merge: integrate Google OAuth and latest application fixes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import UserNav from '../components/UserNav'
|
||||
import { changePasswordApi} from '../services/auth'
|
||||
import { changePasswordApi, meApi, forgotPasswordApi } from '../services/auth'
|
||||
import { useHandleAuthError } from '../hooks/useHandleAuthError'
|
||||
|
||||
export default function Profile() {
|
||||
@@ -14,6 +14,35 @@ export default function Profile() {
|
||||
const [pwError, setPwError] = useState('')
|
||||
const [pwSuccess, setPwSuccess] = useState(false)
|
||||
|
||||
// Un compte créé via Google peut ne pas avoir de mot de passe : le
|
||||
// formulaire de modification n'aurait alors aucun sens. L'information vient
|
||||
// de l'API, car le profil stocké en localStorage ne la contient pas.
|
||||
const [hasPassword, setHasPassword] = useState(null)
|
||||
const [linkSending, setLinkSending] = useState(false)
|
||||
const [linkSent, setLinkSent] = useState(false)
|
||||
const [linkError, setLinkError] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
meApi()
|
||||
.then((data) => setHasPassword(data.user.hasPassword))
|
||||
.catch((e) => {
|
||||
if (!handleAuthError(e)) setHasPassword(true)
|
||||
})
|
||||
}, [handleAuthError])
|
||||
|
||||
const handleSendSetupLink = async () => {
|
||||
setLinkError('')
|
||||
setLinkSending(true)
|
||||
try {
|
||||
await forgotPasswordApi(user.email)
|
||||
setLinkSent(true)
|
||||
} catch (e) {
|
||||
if (!handleAuthError(e)) setLinkError(e.message)
|
||||
} finally {
|
||||
setLinkSending(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePwChange = (e) => setPwForm({ ...pwForm, [e.target.name]: e.target.value })
|
||||
|
||||
const handlePwSubmit = async (e) => {
|
||||
@@ -65,19 +94,19 @@ export default function Profile() {
|
||||
{/* Infos du compte (lecture seule) */}
|
||||
<div className="space-y-4 mb-8">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-400 uppercase tracking-wider mb-1">Adresse email</label>
|
||||
<div className="w-full border border-gray-100 rounded-lg px-4 py-3 text-sm text-gray-400 bg-gray-50 cursor-not-allowed select-none">
|
||||
<label className="block text-xs font-medium text-gray-500 uppercase tracking-wider mb-1">Adresse email</label>
|
||||
<div className="w-full border border-gray-100 rounded-lg px-4 py-3 text-sm text-gray-500 bg-gray-50 cursor-not-allowed select-none">
|
||||
{user.email || '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-400 uppercase tracking-wider mb-1">Nom</label>
|
||||
<label className="block text-xs font-medium text-gray-500 uppercase tracking-wider mb-1">Nom</label>
|
||||
<div className="w-full border border-gray-100 rounded-lg px-4 py-3 text-sm text-gray-500 bg-gray-50">
|
||||
{user.lastname || '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-400 uppercase tracking-wider mb-1">Prénom</label>
|
||||
<label className="block text-xs font-medium text-gray-500 uppercase tracking-wider mb-1">Prénom</label>
|
||||
<div className="w-full border border-gray-100 rounded-lg px-4 py-3 text-sm text-gray-500 bg-gray-50">
|
||||
{user.firstname || '—'}
|
||||
</div>
|
||||
@@ -89,9 +118,37 @@ export default function Profile() {
|
||||
|
||||
{/* Section changement de mot de passe */}
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold text-gray-800 mb-4">Changer mon mot de passe</h2>
|
||||
<h2 className="text-sm font-semibold text-gray-800 mb-4">
|
||||
{hasPassword === false ? 'Définir un mot de passe' : 'Changer mon mot de passe'}
|
||||
</h2>
|
||||
|
||||
{pwSuccess && (
|
||||
{hasPassword === false && (
|
||||
<div className="px-4 py-4 rounded-lg bg-gray-50 border border-gray-200">
|
||||
<p className="text-sm text-gray-600">
|
||||
Votre compte a été créé avec Google et n'a pas encore de mot de passe.
|
||||
Définissez-en un pour pouvoir aussi vous connecter avec votre email.
|
||||
</p>
|
||||
|
||||
{linkSent ? (
|
||||
<p className="mt-3 text-sm text-green-700">
|
||||
Lien envoyé à {user.email}. Consultez votre boîte mail.
|
||||
</p>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSendSetupLink}
|
||||
disabled={linkSending}
|
||||
className="mt-3 bg-[#006B6B] hover:bg-[#005959] disabled:opacity-50 text-white font-medium px-4 py-2.5 rounded-lg transition-colors text-sm"
|
||||
>
|
||||
{linkSending ? 'Envoi…' : 'Recevoir un lien par email'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{linkError && <p className="mt-2 text-sm text-red-600">{linkError}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasPassword !== false && pwSuccess && (
|
||||
<div className="mb-4 px-4 py-3 rounded-lg bg-green-50 border border-green-200 text-green-700 text-sm flex items-center gap-2">
|
||||
<svg className="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" strokeWidth={2.5} viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
@@ -100,12 +157,13 @@ export default function Profile() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pwError && (
|
||||
{hasPassword !== false && pwError && (
|
||||
<div className="mb-4 px-4 py-3 rounded-lg bg-red-50 border border-red-200 text-red-600 text-sm">
|
||||
{pwError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasPassword !== false && (
|
||||
<form onSubmit={handlePwSubmit} className="space-y-4">
|
||||
{[
|
||||
{ name: 'currentPassword', label: 'Mot de passe actuel', key: 'current' },
|
||||
@@ -123,12 +181,12 @@ export default function Profile() {
|
||||
onChange={handlePwChange}
|
||||
required
|
||||
placeholder="••••••••"
|
||||
className="w-full border border-gray-200 rounded-lg px-4 py-3 pr-11 text-sm text-gray-700 placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]"
|
||||
className="w-full border border-gray-200 rounded-lg px-4 py-3 pr-11 text-sm text-gray-700 placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPw((s) => ({ ...s, [key]: !s[key] }))}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-600"
|
||||
aria-label="Afficher/masquer"
|
||||
>
|
||||
<EyeIcon visible={showPw[key]} />
|
||||
@@ -145,6 +203,7 @@ export default function Profile() {
|
||||
{pwLoading ? 'Modification…' : 'Modifier le mot de passe'}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user