update user profile

This commit is contained in:
2026-04-09 14:39:14 +07:00
parent 867884db55
commit 430063e913
2 changed files with 109 additions and 61 deletions

View File

@@ -89,46 +89,53 @@ export default function UserInfoCard({ data }: { data: UserMetaCardProps }) {
{data.data?.email || "Email address"} {data.data?.email || "Email address"}
</p> </p>
</div> </div>
{data.data?.profile?.phone && (
<div>
<p className="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">
Phone
</p>
<p className="text-sm font-medium text-gray-800 dark:text-white/90">
{data.data?.profile?.phone || "+XXX XXX XXX"}
</p>
</div>
)}
<div> {data.data?.profile?.bio && (
<p className="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400"> <div>
Phone <p className="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">
</p> Bio
<p className="text-sm font-medium text-gray-800 dark:text-white/90"> </p>
{data.data?.profile?.phone || "+XXX XXX XXX"} <p className="text-sm font-medium text-gray-800 dark:text-white/90">
</p> {data.data?.profile?.bio || "No bio available"}
</div> </p>
</div>
)}
<div> {data.data?.profile?.location && (
<p className="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400"> <div>
Bio <p className="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">
</p> Address
<p className="text-sm font-medium text-gray-800 dark:text-white/90"> </p>
{data.data?.profile?.bio || "No bio available"} <p className="text-sm font-medium text-gray-800 dark:text-white/90">
</p> {data.data?.profile?.location || "No location available"}
</div> </p>
</div>
<div> )}
<p className="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400"> {data.data?.profile?.website && (
Address <div>
</p> <p className="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400">
<p className="text-sm font-medium text-gray-800 dark:text-white/90"> Website
{data.data?.profile?.location || "No location available"} </p>
</p> <Link
</div> href={data.data?.profile?.website || "#"}
<div> target="_blank"
<p className="mb-2 text-xs leading-normal text-gray-500 dark:text-gray-400"> rel="noopener noreferrer"
Website className="text-sm font-medium text-gray-800 dark:text-white/90 hover:text-blue-500 dark:hover:text-blue-400"
</p> >
<Link {data.data?.profile?.website || "No website"}
href={data.data?.profile?.website || "#"} </Link>
target="_blank" </div>
rel="noopener noreferrer" )}
className="text-sm font-medium text-gray-800 dark:text-white/90 hover:text-blue-500 dark:hover:text-blue-400"
>
{data.data?.profile?.website || "No website"}
</Link>
</div>
</div> </div>
</div> </div>
@@ -230,7 +237,7 @@ export default function UserInfoCard({ data }: { data: UserMetaCardProps }) {
onChange={handleChange} onChange={handleChange}
/> />
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<Label>Website</Label> <Label>Website</Label>
<Input <Input
type="text" type="text"

View File

@@ -7,7 +7,7 @@ import { uploadMedia } from "@/service/mediaService";
export default function UserMetaCard({ data }: { data: UserMetaCardProps }) { export default function UserMetaCard({ data }: { data: UserMetaCardProps }) {
const [previewImage, setPreviewImage] = useState( const [previewImage, setPreviewImage] = useState(
data.data?.profile?.avatar_url || "/images/no-images.jpg" data.data?.profile?.avatar_url || "/images/no-images.jpg",
); );
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
@@ -18,7 +18,9 @@ export default function UserMetaCard({ data }: { data: UserMetaCardProps }) {
} }
}; };
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
if (!file) return; if (!file) return;
@@ -31,10 +33,11 @@ export default function UserMetaCard({ data }: { data: UserMetaCardProps }) {
const uploadedMedia = await uploadMedia(file); const uploadedMedia = await uploadMedia(file);
console.log("Upload thành công:", uploadedMedia); console.log("Upload thành công:", uploadedMedia);
} catch (error) { } catch (error) {
console.error("Lỗi khi upload avatar:", error); console.error("Lỗi khi upload avatar:", error);
setPreviewImage(data.data?.profile?.avatar_url || "/images/no-images.jpg"); setPreviewImage(
data.data?.profile?.avatar_url || "/images/no-images.jpg",
);
alert("Không thể tải ảnh lên. Vui lòng thử lại!"); alert("Không thể tải ảnh lên. Vui lòng thử lại!");
} finally { } finally {
setIsUploading(false); setIsUploading(false);
@@ -49,8 +52,7 @@ export default function UserMetaCard({ data }: { data: UserMetaCardProps }) {
<div className="p-5 border border-gray-200 rounded-2xl dark:border-gray-800 lg:p-6"> <div className="p-5 border border-gray-200 rounded-2xl dark:border-gray-800 lg:p-6">
<div className="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between"> <div className="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
<div className="flex flex-col items-center w-full gap-6 xl:flex-row"> <div className="flex flex-col items-center w-full gap-6 xl:flex-row">
<div
<div
onClick={handleAvatarClick} onClick={handleAvatarClick}
className="relative w-20 h-20 overflow-hidden border border-gray-200 rounded-full cursor-pointer dark:border-gray-800 group shrink-0" className="relative w-20 h-20 overflow-hidden border border-gray-200 rounded-full cursor-pointer dark:border-gray-800 group shrink-0"
> >
@@ -61,17 +63,48 @@ export default function UserMetaCard({ data }: { data: UserMetaCardProps }) {
alt="avatar" alt="avatar"
className="object-cover w-full h-full" className="object-cover w-full h-full"
/> />
<div className="absolute inset-0 flex items-center justify-center transition-opacity bg-black/50 opacity-0 group-hover:opacity-100"> <div className="absolute inset-0 flex items-center justify-center transition-opacity bg-black/50 opacity-0 group-hover:opacity-100">
{isUploading ? ( {isUploading ? (
<svg className="w-6 h-6 text-white animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <svg
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> className="w-6 h-6 text-white animate-spin"
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg> </svg>
) : ( ) : (
<svg className="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" /> className="w-6 h-6 text-white"
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" /> fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg> </svg>
)} )}
</div> </div>
@@ -94,14 +127,22 @@ export default function UserMetaCard({ data }: { data: UserMetaCardProps }) {
{data.data?.roles?.map((role) => role.name).join(", ") || {data.data?.roles?.map((role) => role.name).join(", ") ||
"No roles available"} "No roles available"}
</p> </p>
<div className="hidden h-3.5 w-px bg-gray-300 dark:bg-gray-700 xl:block"></div> {data.data?.profile?.bio && (
<p className="text-sm text-gray-500 dark:text-gray-400 max-w-[450px] truncate"> <>
{data.data?.profile?.bio || "No bio available"} <div className="hidden h-3.5 w-px bg-gray-300 dark:bg-gray-700 xl:block"></div>
</p> <p className="text-sm text-gray-500 dark:text-gray-400 max-w-[450px] truncate">
<div className="hidden h-3.5 w-px bg-gray-300 dark:bg-gray-700 xl:block"></div> {data.data?.profile?.bio || "No bio available"}
<p className="text-sm text-gray-500 dark:text-gray-400"> </p>
{data.data?.profile?.location || "user location"} </>
</p> )}
{data.data?.profile?.location && (
<>
<div className="hidden h-3.5 w-px bg-gray-300 dark:bg-gray-700 xl:block"></div>
<p className="text-sm text-gray-500 dark:text-gray-400">
{data.data?.profile?.location || "user location"}
</p>
</>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -109,4 +150,4 @@ export default function UserMetaCard({ data }: { data: UserMetaCardProps }) {
</div> </div>
</> </>
); );
} }