header 导航中的 ReactJS 组件在上下文值更改后不会再次 re-render
ReactJS component in header navigation does not re-render again after context value changed
我有身份验证上下文提供程序来存储登录用户信息并在 header 导航中显示用户照片。
我有个人资料编辑页面来编辑用户的显示名称和照片。通过 api 将用户信息更新到 firebase 和数据库后,我再次将用户的更新信息设置为上下文值。但是 header 导航上的图像不会改变,直到手动刷新页面或单击其他路线。我知道刷新上下文值有什么问题。
auth_prodiver.jsx
import React, { useState, useEffect, useContext, createContext } from "react";
import { useLocation, Navigate } from "react-router-dom";
import { signIn, signUp, updateCurrentUser } from "../helpers/gql_auth_helpers";
import paths from "../routes/paths";
import { store } from "../store/configure_store";
import { saveUser } from "../store/slice/auth_slice";
import { auth } from "../helpers/init-firebase";
import {
getAuth,
sendEmailVerification,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
sendPasswordResetEmail,
onAuthStateChanged,
signInWithPopup,
GoogleAuthProvider,
FacebookAuthProvider,
signOut,
confirmPasswordReset,
updateProfile,
verifyPasswordResetCode,
applyActionCode,
checkActionCode,
updatePassword,
} from "firebase/auth";
const AuthContext = createContext(null);
let accessToken = "";
export const getAccessToken = () => accessToken;
export const setAccessToken = (token) => {
accessToken = token;
};
export const AuthProvider = ({ user: usr, children }) => {
const [user, setUser] = useState(usr);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user && user.emailVerified) {
setUser(user);
setAccessToken(user.getIdToken(true));
store.dispatch(saveUser(user));
} else {
setUser(null);
setAccessToken(null);
store.dispatch(saveUser(null));
}
});
return () => {
unsubscribe();
};
}, []);
async function login(email, password) {
return signInWithEmailAndPassword(auth, email, password).then(
async function (res) {
const auth = getAuth();
return await auth.currentUser
.getIdToken(true)
.then(async function (token) {
return await signIn(token);
});
}
);
}
async function register(name, email, password) {
return createUserWithEmailAndPassword(auth, email, password).then(
async function (res) {
const auth = getAuth();
return await updateProfile(auth.currentUser, {
displayName: name,
})
.then(async function () {
const auth = getAuth();
if (auth.currentUser != null) {
return await sendEmailVerification(auth.currentUser).then(
async function (res) {
return await auth.currentUser
.getIdToken(true)
.then(async function (token) {
await signUp(token);
});
}
);
}
})
.catch((error) => {
throw error;
});
}
);
}
async function resendEmailVerification() {
return sendEmailVerification(auth.currentUser);
}
async function forgotPassword(email) {
return sendPasswordResetEmail(auth, email, {
url: `https://myanmargita.com/login`,
});
}
async function confirmPassword(oobCode, newPassword) {
return confirmPasswordReset(auth, oobCode, newPassword);
}
async function updateCurrentUserPassword(newPassword) {
const auth = getAuth();
const user = auth.currentUser;
return updatePassword(user, newPassword);
}
async function updateUser(updateUserId, displayName, photoUrl) {
const auth = getAuth();
const currentUser = getAuth().currentUser;
return await updateProfile(auth.currentUser, {
displayName: displayName,
photoUrl: photoUrl,
})
.then(async function () {
const auth = getAuth();
if (auth.currentUser != null) {
updateCurrentUser(updateUserId, displayName, photoUrl)
.then(async(updatedUser) => {
if (updatedUser) {
await currentUser.reload();
setUser(currentUser);
setAccessToken(currentUser.getIdToken(true));
store.dispatch(saveUser(currentUser));
console.log("User Name : ", user.displayName)
} else {
setUser(null);
setAccessToken(null);
store.dispatch(saveUser(null));
}
})
.catch((error) => {
throw error;
});
}
})
.catch((error) => {
throw error;
});
}
async function resetPassword(actionCode) {
return verifyPasswordResetCode(auth, actionCode);
}
async function applyAction(actionCode) {
return applyActionCode(auth, actionCode);
}
async function checkAction(actionCode) {
return checkActionCode(auth, actionCode);
}
async function logout() {
return signOut(auth);
}
async function signInWithGoogle() {
const provider = new GoogleAuthProvider();
provider.setCustomParameters({
display: "popup",
});
return signInWithPopup(auth, provider);
}
async function signInWithFacebook() {
const provider = new FacebookAuthProvider();
provider.setCustomParameters({
display: "popup",
});
return signInWithPopup(auth, provider);
}
const value = {
user,
accessToken,
login,
register,
forgotPassword,
confirmPassword,
logout,
signInWithGoogle,
signInWithFacebook,
resetPassword,
applyAction,
checkAction,
resendEmailVerification,
updateCurrentUserPassword,
updateUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};
export const RequireAuth = ({ children }) => {
let auth = useAuth();
let location = useLocation();
if (!auth?.user) {
return <Navigate to={paths.login} state={{ from: location }} />;
}
return children;
};
profile_edit_page.jsx
import React, { useEffect,useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as yup from "yup";
import PicturePicker from "../../components/picture_picker";
import ProgressBar from "../../controls/progress_bar";
import LoadingIndicator from "../../components/loading_indicator";
import {getUserWithId } from "../../gql/user";
import { useAuth } from "../../auth/auth_provider";
import { useQuery } from "@apollo/client";
import paths from '../../routes/paths';
import { storage} from "../../helpers/init-firebase";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
const formSchema = yup.object().shape({
name: yup.string().required("Name is required."),
email: yup.string().required("Email is required."),
});
const formData = {
name: "",
email: "",
photoURL: "",
};
const ProfileEditPage = (props) => {
const { id } = useParams();
const [uploadProgress, setUploadProgress] = useState(0);
const [pictureUrl, setPictureUrl] = useState();
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const { data, loading: dataLoading, error } = useQuery(getUserWithId,{variables:{
"where": {
"id": id
}
}});
let auth = useAuth();
const handleUpdate = async (edit_data) => {
console.log("Id : "+ id);
const user_name = edit_data.name.trim();
const email = edit_data.email.trim();
const file = edit_data.photoURL;
if (pictureUrl === file) {
auth.updateUser(id, user_name, edit_data.photoURL);
} else {
const file = edit_data.photoURL;
const storageRef = ref(storage, "images/users/" + file.name);
const uploadTask = uploadBytesResumable(storageRef,file);
uploadTask.on(
"state_changed",
(snapshot) => {
var progress = Math.round(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Progrss: ", progress);
setUploadProgress({ progress });
},
(error) => {
console.log("File Upload Error: ", error.message);
throw error;
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then(async(url) => {
await auth.updateUser(id, user_name, url);
navigate(paths.getProfile(id));
});
}
);
}
};
const handleBack = () => {
navigate(-1);
};
useEffect(()=>{
if(!dataLoading && !error){
if(data?.user) {
setPictureUrl(data?.user?.photoURL);
formData.name = data?.user.displayName;
formData.email = data?.user.email;
formData.photoURL = data?.user.photoURL;
}
}
},[dataLoading,error]);
const EntryForm = () => {
return (
<Formik initialValues={formData} enableReinitialize={true} onSubmit={handleUpdate} validationSchema={formSchema}>
{({ dirty, values, errors, touched, handleChange, handleSubmit, handleReset, setFieldValue }) => {
return (
<Form autoComplete="off">
<div className="text-sm">
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Email</div>
<div className="p-2 m-2">
{values.email}
</div>
</div>
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Name</div>
<div className="p-2 m-2">
<Field
type="text"
className="p-2 w-96 textarea textarea-primary"
id="name"
name="name"
placeholder="Display Name"
value={values.name}
onChange={handleChange}
/>
<ErrorMessage name="name" component="span" className="text-sm text-red-500 px-2" />
</div>
</div>
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Profile Image</div>
<div className="flex flex-col items-left align-middle">
<div className="flex flex-col px-4 mt-8 mx-4 h-60 items-cente p-2 m-2" style={{ height: "250px", width: "200px" }}>
<PicturePicker url={pictureUrl} onChange={(file) => setFieldValue("photoURL", file)} value={values.photoURL} />
<ProgressBar className="px-2 pt-2 pb-1" percent={uploadProgress} />
<span className="text-red-600 self-center text-sm">{touched.photoURL && errors.photoURL}</span>
<ErrorMessage name="photoURL" component="span" className="text-sm text-red-500 px-2" />
</div>
</div>
</div>
<div className="flex flex-nowrap p-3 mt-5">
<button type="submit" onClick={handleSubmit} disabled={!dirty} className="btn btn-primary btn-sm mx-2 my-1 w-[66px]">
Update
</button>
<button disabled={!dirty} className="btn btn-primary btn-sm mx-2 my-1 w-[66px]" onClick={handleReset}>
Clear
</button>
</div>
</div>
</Form>
);
}}
</Formik>
);
};
return (
<div className="card">
<header className="px-5 py-4 border-b border-gray-100">
<h2 className="font-semibold text-gray-800">Edit Profile</h2>
</header>
<EntryForm />
<LoadingIndicator loading={loading} color="#000099" label="Uploading..." />
</div>
);
};
export default ProfileEditPage;
profile.jsx(在 header.jsx 中使用的配置文件组件)
import { useState, useRef, useEffect } from "react";
import {
ProfileIcon,
ResetPasswordIcon,
LogoutIcon,
} from "../assets/icons/svg_icons";
import { useAuth } from "../auth/auth_provider";
import { useNavigate } from "react-router-dom";
import paths from "../routes/paths";
import ProfileModal from "./profile_modal";
const Profile = () => {
const [isOpen, setIsOpen] = useState(false);
const [modal, setModal] = useState({
open: false,
id: "",
});
const wrapperRef = useRef(null);
const auth = useAuth();
const navigate = useNavigate();
const handleLogout = (e) => {
e.preventDefault();
auth.logout();
};
const handleResetPassword = (e) => {
e.preventDefault();
navigate(paths.resetpassword, { replace: true });
};
const handleProfile = (e) => {
e.preventDefault();
navigate(paths.getProfile(auth.user.uid));
};
const handleModalClose = async () => {
setModal({ ...modal, open: false });
};
const handleModalOpen = async () => {
setModal({ ...modal, id: auth.user.uid, open: true });
};
useOutsideClick(wrapperRef);
function useOutsideClick(ref) {
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(false);
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
}
return (
<div className="flex justify-end" ref={wrapperRef}>
<div className="relative">
<div className="cursor-pointer" onClick={() => setIsOpen(!isOpen)}>
<div className="avatar online placeholder">
<div className="bg-neutral-focus text-neutral-content rounded-full w-12 h-12">
{auth.user && (
<div className="avatar online placeholder">
<div className="bg-neutral-focus text-neutral-content rounded-full w-12 h-12">
{auth.user ? (
auth.user?.photoURL == null ? (
<ProfileIcon className="w-10 h-10" />
) : (
<img src={auth.user?.photoURL} />
)
) : (
<div></div>
)}
</div>
</div>
)}
</div>
</div>
</div>
{isOpen && (
<div
className="origin-top-right absolute right-0 mt-2 w-80 rounded-md shadow-lg
bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100
focus:outline-none cursor-pointer z-50"
>
<div className="py-5">
<div className="flex justify-center" onClick={handleModalOpen}>
<div className="relative text-gray-700">
{auth.user ? (
auth.user?.photoURL == null ? (
<ProfileIcon className="w-20 h-20" />
) : (
<img
className="object-cover w-20 h-20 rounded-full"
src={auth.user?.photoURL}
alt={auth.user?.displayName}
/>
)
) : (
<div></div>
)}
<div className="flex absolute right-0 bottom-0 w-8 h-8 bg-gray-400 rounded-full justify-center items-center">
<svg
className="w-6 h-6"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="1em"
height="1em"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 1024 1024"
>
<path
fill="currentColor"
d="M864 260H728l-32.4-90.8a32.07 32.07 0 0 0-30.2-21.2H358.6c-13.5 0-25.6 8.5-30.1 21.2L296 260H160c-44.2 0-80 35.8-80 80v456c0 44.2 35.8 80 80 80h704c44.2 0 80-35.8 80-80V340c0-44.2-35.8-80-80-80zM512 716c-88.4 0-160-71.6-160-160s71.6-160 160-160s160 71.6 160 160s-71.6 160-160 160zm-96-160a96 96 0 1 0 192 0a96 96 0 1 0-192 0z"
/>
</svg>
</div>
</div>
</div>
<div className="pt-2" onClick={handleProfile}>
{auth.user ? (
<h1 className="text-sm text-gray-700 p-0 m-0">
{auth.user.displayName}
</h1>
) : (
<div></div>
)}
{auth.user ? (
<h2 className="text-sm text-gray-700 p-0 m-0">
{auth.user.email}
</h2>
) : (
<div></div>
)}
</div>
<div className="pt-4">
<button
type="submit"
className="btn btn-sm btn-ghost rounded-lg mx-6 my-1 px-4 w-auto text-black border border-gray-400"
onClick={handleProfile}
>
Manage your account
</button>
</div>
</div>
<div className="py-1 mx-2">
<a
className="group flex items-center px-4 py-2 text-sm text-gray-700
hover:bg-primary hover:text-white"
onClick={handleResetPassword}
>
<ResetPasswordIcon className="w-8 h-8" />
<span className="px-1">Reset Password</span>
</a>
</div>
<div className="divide-x" />
<div className="py-1 mx-2">
<a
className="group flex items-center px-4 py-2 text-sm text-gray-700
hover:bg-primary hover:text-white"
onClick={handleLogout}
>
<LogoutIcon className="w-8 h-8" />
<span className="px-1">Logout</span>
</a>
</div>
</div>
)}
</div>
{modal.open && (
<ProfileModal id={auth.user.uid} onClose={handleModalClose} />
)}
</div>
);
};
export default Profile;
如果您确定 url 已更改将密钥添加到 img 标签
我有身份验证上下文提供程序来存储登录用户信息并在 header 导航中显示用户照片。
我有个人资料编辑页面来编辑用户的显示名称和照片。通过 api 将用户信息更新到 firebase 和数据库后,我再次将用户的更新信息设置为上下文值。但是 header 导航上的图像不会改变,直到手动刷新页面或单击其他路线。我知道刷新上下文值有什么问题。
auth_prodiver.jsx
import React, { useState, useEffect, useContext, createContext } from "react";
import { useLocation, Navigate } from "react-router-dom";
import { signIn, signUp, updateCurrentUser } from "../helpers/gql_auth_helpers";
import paths from "../routes/paths";
import { store } from "../store/configure_store";
import { saveUser } from "../store/slice/auth_slice";
import { auth } from "../helpers/init-firebase";
import {
getAuth,
sendEmailVerification,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
sendPasswordResetEmail,
onAuthStateChanged,
signInWithPopup,
GoogleAuthProvider,
FacebookAuthProvider,
signOut,
confirmPasswordReset,
updateProfile,
verifyPasswordResetCode,
applyActionCode,
checkActionCode,
updatePassword,
} from "firebase/auth";
const AuthContext = createContext(null);
let accessToken = "";
export const getAccessToken = () => accessToken;
export const setAccessToken = (token) => {
accessToken = token;
};
export const AuthProvider = ({ user: usr, children }) => {
const [user, setUser] = useState(usr);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user && user.emailVerified) {
setUser(user);
setAccessToken(user.getIdToken(true));
store.dispatch(saveUser(user));
} else {
setUser(null);
setAccessToken(null);
store.dispatch(saveUser(null));
}
});
return () => {
unsubscribe();
};
}, []);
async function login(email, password) {
return signInWithEmailAndPassword(auth, email, password).then(
async function (res) {
const auth = getAuth();
return await auth.currentUser
.getIdToken(true)
.then(async function (token) {
return await signIn(token);
});
}
);
}
async function register(name, email, password) {
return createUserWithEmailAndPassword(auth, email, password).then(
async function (res) {
const auth = getAuth();
return await updateProfile(auth.currentUser, {
displayName: name,
})
.then(async function () {
const auth = getAuth();
if (auth.currentUser != null) {
return await sendEmailVerification(auth.currentUser).then(
async function (res) {
return await auth.currentUser
.getIdToken(true)
.then(async function (token) {
await signUp(token);
});
}
);
}
})
.catch((error) => {
throw error;
});
}
);
}
async function resendEmailVerification() {
return sendEmailVerification(auth.currentUser);
}
async function forgotPassword(email) {
return sendPasswordResetEmail(auth, email, {
url: `https://myanmargita.com/login`,
});
}
async function confirmPassword(oobCode, newPassword) {
return confirmPasswordReset(auth, oobCode, newPassword);
}
async function updateCurrentUserPassword(newPassword) {
const auth = getAuth();
const user = auth.currentUser;
return updatePassword(user, newPassword);
}
async function updateUser(updateUserId, displayName, photoUrl) {
const auth = getAuth();
const currentUser = getAuth().currentUser;
return await updateProfile(auth.currentUser, {
displayName: displayName,
photoUrl: photoUrl,
})
.then(async function () {
const auth = getAuth();
if (auth.currentUser != null) {
updateCurrentUser(updateUserId, displayName, photoUrl)
.then(async(updatedUser) => {
if (updatedUser) {
await currentUser.reload();
setUser(currentUser);
setAccessToken(currentUser.getIdToken(true));
store.dispatch(saveUser(currentUser));
console.log("User Name : ", user.displayName)
} else {
setUser(null);
setAccessToken(null);
store.dispatch(saveUser(null));
}
})
.catch((error) => {
throw error;
});
}
})
.catch((error) => {
throw error;
});
}
async function resetPassword(actionCode) {
return verifyPasswordResetCode(auth, actionCode);
}
async function applyAction(actionCode) {
return applyActionCode(auth, actionCode);
}
async function checkAction(actionCode) {
return checkActionCode(auth, actionCode);
}
async function logout() {
return signOut(auth);
}
async function signInWithGoogle() {
const provider = new GoogleAuthProvider();
provider.setCustomParameters({
display: "popup",
});
return signInWithPopup(auth, provider);
}
async function signInWithFacebook() {
const provider = new FacebookAuthProvider();
provider.setCustomParameters({
display: "popup",
});
return signInWithPopup(auth, provider);
}
const value = {
user,
accessToken,
login,
register,
forgotPassword,
confirmPassword,
logout,
signInWithGoogle,
signInWithFacebook,
resetPassword,
applyAction,
checkAction,
resendEmailVerification,
updateCurrentUserPassword,
updateUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};
export const RequireAuth = ({ children }) => {
let auth = useAuth();
let location = useLocation();
if (!auth?.user) {
return <Navigate to={paths.login} state={{ from: location }} />;
}
return children;
};
profile_edit_page.jsx
import React, { useEffect,useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as yup from "yup";
import PicturePicker from "../../components/picture_picker";
import ProgressBar from "../../controls/progress_bar";
import LoadingIndicator from "../../components/loading_indicator";
import {getUserWithId } from "../../gql/user";
import { useAuth } from "../../auth/auth_provider";
import { useQuery } from "@apollo/client";
import paths from '../../routes/paths';
import { storage} from "../../helpers/init-firebase";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
const formSchema = yup.object().shape({
name: yup.string().required("Name is required."),
email: yup.string().required("Email is required."),
});
const formData = {
name: "",
email: "",
photoURL: "",
};
const ProfileEditPage = (props) => {
const { id } = useParams();
const [uploadProgress, setUploadProgress] = useState(0);
const [pictureUrl, setPictureUrl] = useState();
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
const { data, loading: dataLoading, error } = useQuery(getUserWithId,{variables:{
"where": {
"id": id
}
}});
let auth = useAuth();
const handleUpdate = async (edit_data) => {
console.log("Id : "+ id);
const user_name = edit_data.name.trim();
const email = edit_data.email.trim();
const file = edit_data.photoURL;
if (pictureUrl === file) {
auth.updateUser(id, user_name, edit_data.photoURL);
} else {
const file = edit_data.photoURL;
const storageRef = ref(storage, "images/users/" + file.name);
const uploadTask = uploadBytesResumable(storageRef,file);
uploadTask.on(
"state_changed",
(snapshot) => {
var progress = Math.round(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Progrss: ", progress);
setUploadProgress({ progress });
},
(error) => {
console.log("File Upload Error: ", error.message);
throw error;
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then(async(url) => {
await auth.updateUser(id, user_name, url);
navigate(paths.getProfile(id));
});
}
);
}
};
const handleBack = () => {
navigate(-1);
};
useEffect(()=>{
if(!dataLoading && !error){
if(data?.user) {
setPictureUrl(data?.user?.photoURL);
formData.name = data?.user.displayName;
formData.email = data?.user.email;
formData.photoURL = data?.user.photoURL;
}
}
},[dataLoading,error]);
const EntryForm = () => {
return (
<Formik initialValues={formData} enableReinitialize={true} onSubmit={handleUpdate} validationSchema={formSchema}>
{({ dirty, values, errors, touched, handleChange, handleSubmit, handleReset, setFieldValue }) => {
return (
<Form autoComplete="off">
<div className="text-sm">
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Email</div>
<div className="p-2 m-2">
{values.email}
</div>
</div>
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Name</div>
<div className="p-2 m-2">
<Field
type="text"
className="p-2 w-96 textarea textarea-primary"
id="name"
name="name"
placeholder="Display Name"
value={values.name}
onChange={handleChange}
/>
<ErrorMessage name="name" component="span" className="text-sm text-red-500 px-2" />
</div>
</div>
<div className="flex flex-nowrap">
<div className="w-48 p-2 m-2">Profile Image</div>
<div className="flex flex-col items-left align-middle">
<div className="flex flex-col px-4 mt-8 mx-4 h-60 items-cente p-2 m-2" style={{ height: "250px", width: "200px" }}>
<PicturePicker url={pictureUrl} onChange={(file) => setFieldValue("photoURL", file)} value={values.photoURL} />
<ProgressBar className="px-2 pt-2 pb-1" percent={uploadProgress} />
<span className="text-red-600 self-center text-sm">{touched.photoURL && errors.photoURL}</span>
<ErrorMessage name="photoURL" component="span" className="text-sm text-red-500 px-2" />
</div>
</div>
</div>
<div className="flex flex-nowrap p-3 mt-5">
<button type="submit" onClick={handleSubmit} disabled={!dirty} className="btn btn-primary btn-sm mx-2 my-1 w-[66px]">
Update
</button>
<button disabled={!dirty} className="btn btn-primary btn-sm mx-2 my-1 w-[66px]" onClick={handleReset}>
Clear
</button>
</div>
</div>
</Form>
);
}}
</Formik>
);
};
return (
<div className="card">
<header className="px-5 py-4 border-b border-gray-100">
<h2 className="font-semibold text-gray-800">Edit Profile</h2>
</header>
<EntryForm />
<LoadingIndicator loading={loading} color="#000099" label="Uploading..." />
</div>
);
};
export default ProfileEditPage;
profile.jsx(在 header.jsx 中使用的配置文件组件)
import { useState, useRef, useEffect } from "react";
import {
ProfileIcon,
ResetPasswordIcon,
LogoutIcon,
} from "../assets/icons/svg_icons";
import { useAuth } from "../auth/auth_provider";
import { useNavigate } from "react-router-dom";
import paths from "../routes/paths";
import ProfileModal from "./profile_modal";
const Profile = () => {
const [isOpen, setIsOpen] = useState(false);
const [modal, setModal] = useState({
open: false,
id: "",
});
const wrapperRef = useRef(null);
const auth = useAuth();
const navigate = useNavigate();
const handleLogout = (e) => {
e.preventDefault();
auth.logout();
};
const handleResetPassword = (e) => {
e.preventDefault();
navigate(paths.resetpassword, { replace: true });
};
const handleProfile = (e) => {
e.preventDefault();
navigate(paths.getProfile(auth.user.uid));
};
const handleModalClose = async () => {
setModal({ ...modal, open: false });
};
const handleModalOpen = async () => {
setModal({ ...modal, id: auth.user.uid, open: true });
};
useOutsideClick(wrapperRef);
function useOutsideClick(ref) {
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(false);
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
}
return (
<div className="flex justify-end" ref={wrapperRef}>
<div className="relative">
<div className="cursor-pointer" onClick={() => setIsOpen(!isOpen)}>
<div className="avatar online placeholder">
<div className="bg-neutral-focus text-neutral-content rounded-full w-12 h-12">
{auth.user && (
<div className="avatar online placeholder">
<div className="bg-neutral-focus text-neutral-content rounded-full w-12 h-12">
{auth.user ? (
auth.user?.photoURL == null ? (
<ProfileIcon className="w-10 h-10" />
) : (
<img src={auth.user?.photoURL} />
)
) : (
<div></div>
)}
</div>
</div>
)}
</div>
</div>
</div>
{isOpen && (
<div
className="origin-top-right absolute right-0 mt-2 w-80 rounded-md shadow-lg
bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100
focus:outline-none cursor-pointer z-50"
>
<div className="py-5">
<div className="flex justify-center" onClick={handleModalOpen}>
<div className="relative text-gray-700">
{auth.user ? (
auth.user?.photoURL == null ? (
<ProfileIcon className="w-20 h-20" />
) : (
<img
className="object-cover w-20 h-20 rounded-full"
src={auth.user?.photoURL}
alt={auth.user?.displayName}
/>
)
) : (
<div></div>
)}
<div className="flex absolute right-0 bottom-0 w-8 h-8 bg-gray-400 rounded-full justify-center items-center">
<svg
className="w-6 h-6"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="1em"
height="1em"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 1024 1024"
>
<path
fill="currentColor"
d="M864 260H728l-32.4-90.8a32.07 32.07 0 0 0-30.2-21.2H358.6c-13.5 0-25.6 8.5-30.1 21.2L296 260H160c-44.2 0-80 35.8-80 80v456c0 44.2 35.8 80 80 80h704c44.2 0 80-35.8 80-80V340c0-44.2-35.8-80-80-80zM512 716c-88.4 0-160-71.6-160-160s71.6-160 160-160s160 71.6 160 160s-71.6 160-160 160zm-96-160a96 96 0 1 0 192 0a96 96 0 1 0-192 0z"
/>
</svg>
</div>
</div>
</div>
<div className="pt-2" onClick={handleProfile}>
{auth.user ? (
<h1 className="text-sm text-gray-700 p-0 m-0">
{auth.user.displayName}
</h1>
) : (
<div></div>
)}
{auth.user ? (
<h2 className="text-sm text-gray-700 p-0 m-0">
{auth.user.email}
</h2>
) : (
<div></div>
)}
</div>
<div className="pt-4">
<button
type="submit"
className="btn btn-sm btn-ghost rounded-lg mx-6 my-1 px-4 w-auto text-black border border-gray-400"
onClick={handleProfile}
>
Manage your account
</button>
</div>
</div>
<div className="py-1 mx-2">
<a
className="group flex items-center px-4 py-2 text-sm text-gray-700
hover:bg-primary hover:text-white"
onClick={handleResetPassword}
>
<ResetPasswordIcon className="w-8 h-8" />
<span className="px-1">Reset Password</span>
</a>
</div>
<div className="divide-x" />
<div className="py-1 mx-2">
<a
className="group flex items-center px-4 py-2 text-sm text-gray-700
hover:bg-primary hover:text-white"
onClick={handleLogout}
>
<LogoutIcon className="w-8 h-8" />
<span className="px-1">Logout</span>
</a>
</div>
</div>
)}
</div>
{modal.open && (
<ProfileModal id={auth.user.uid} onClose={handleModalClose} />
)}
</div>
);
};
export default Profile;
如果您确定 url 已更改将密钥添加到 img 标签