update profile
This commit is contained in:
@@ -3,30 +3,125 @@
|
|||||||
import { setSelectedApplication } from "@/store/features/userSlice";
|
import { setSelectedApplication } from "@/store/features/userSlice";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
|
import { URL_MEDIA } from "../../../api";
|
||||||
|
|
||||||
export default function ApplicationList({ applications }: { applications: any[] }) {
|
const formatFullDateTime = (dateString: string) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const time = date.toLocaleTimeString("vi-VN", { hour: "2-digit", minute: "2-digit" });
|
||||||
|
const day = date.toLocaleDateString("vi-VN", { day: "2-digit", month: "2-digit", year: "numeric" });
|
||||||
|
return `${time} ${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const processMedia = (mediaArray: any[]) => {
|
||||||
|
if (!mediaArray || mediaArray.length === 0) return { type: "empty" };
|
||||||
|
const imageFiles = mediaArray.filter((file) => {
|
||||||
|
const isImageMime = file.mime_type?.startsWith("image/");
|
||||||
|
const isImageExt = /\.(jpg|jpeg|png|webp|gif)$/i.test(file.storage_key);
|
||||||
|
return isImageMime || isImageExt;
|
||||||
|
});
|
||||||
|
const docFiles = mediaArray.filter((file) => {
|
||||||
|
const isImage = file.mime_type?.startsWith("image/") || /\.(jpg|jpeg|png|webp|gif)$/i.test(file.storage_key);
|
||||||
|
return !isImage;
|
||||||
|
});
|
||||||
|
if (imageFiles.length > 0) return { type: "image", src: `${URL_MEDIA}${imageFiles[0].storage_key}` };
|
||||||
|
if (docFiles.length > 0) {
|
||||||
|
const extensions = docFiles.map((file) => file.mime_type ? file.mime_type.split("/")[1] : file.storage_key.split(".").pop() || "file");
|
||||||
|
return { type: "documents", extensions };
|
||||||
|
}
|
||||||
|
return { type: "empty" };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ApplicationSquareCardList({ applications }: { applications: any[] }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const handleViewDetail = (app: any) => {
|
const handleViewDetail = (app: any) => {
|
||||||
dispatch(setSelectedApplication(app));
|
dispatch(setSelectedApplication(app));
|
||||||
router.push(`/profile/applications`);
|
router.push(`/profile/applications`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="p-5 border rounded-xl dark:border-zinc-800 lg:p-6 bg-white dark:bg-zinc-950">
|
||||||
{applications.map((app) => (
|
<h4 className="text-lg font-bold text-zinc-800 dark:text-white/90 mb-5 tracking-tight">
|
||||||
<div
|
Applications CV
|
||||||
key={app.id}
|
</h4>
|
||||||
onClick={() => handleViewDetail(app)}
|
|
||||||
className="p-4 border rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition"
|
<div className="flex flex-wrap gap-4">
|
||||||
>
|
{applications?.map((app) => {
|
||||||
<div className="flex justify-between items-center">
|
const mediaState = processMedia(app.media);
|
||||||
<span>Loại: {app.verify_type}</span>
|
return (
|
||||||
<span className="text-blue-500 font-medium">Xem chi tiết →</span>
|
<div
|
||||||
</div>
|
key={app.id}
|
||||||
</div>
|
onClick={() => handleViewDetail(app)}
|
||||||
))}
|
className="group relative h-40 aspect-square border dark:border-zinc-800 rounded-xl cursor-pointer overflow-hidden transition-all duration-300 hover:ring-2 hover:ring-blue-500/50"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 z-0">
|
||||||
|
{mediaState.type === "image" ? (
|
||||||
|
<img
|
||||||
|
src={mediaState.src}
|
||||||
|
alt=""
|
||||||
|
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-full bg-zinc-100 dark:bg-zinc-900 flex items-center justify-center p-4">
|
||||||
|
{mediaState.type === "documents" ? (
|
||||||
|
<div className="flex flex-wrap gap-1 justify-center">
|
||||||
|
{mediaState.extensions?.slice(0, 3).map((ext, i) => (
|
||||||
|
<span key={i} className="text-[9px] font-black px-1.5 py-0.5 bg-white dark:bg-zinc-800 rounded border dark:border-zinc-700 uppercase">
|
||||||
|
.{ext}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-full bg-gradient-to-br from-zinc-400 to-zinc-600 opacity-20" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 z-10 bg-gradient-to-t from-black/90 via-black/20 to-transparent transition-opacity group-hover:opacity-90" />
|
||||||
|
|
||||||
|
<div className="absolute top-2 left-2 right-2 z-20 flex justify-between items-start">
|
||||||
|
<div className={`flex items-center gap-1.5 rounded-md backdrop-blur-md border border-white/10 ${
|
||||||
|
app.status === "PENDING" ? "bg-amber-500/20 text-amber-400" : "bg-emerald-500/20 text-emerald-400"
|
||||||
|
}`}>
|
||||||
|
<span className={`w-2 h-2 rounded-full animate-pulse ${app.status === "PENDING" ? "bg-amber-500" : "bg-emerald-500"}`} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{app.media?.length > 0 && (
|
||||||
|
<span className="text-[9px] font-bold text-white/60 bg-black/40 px-1.5 py-0.5 rounded-md backdrop-blur-sm">
|
||||||
|
{app.media.length} FILE
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute bottom-2 left-2 right-2 z-20 text-white">
|
||||||
|
<div className="mb-1">
|
||||||
|
<p className="text-[10px] font-black tracking-tighter truncate">
|
||||||
|
{app.verify_type || "VERIFY"}
|
||||||
|
</p>
|
||||||
|
{app?.reviewer?.display_name && (
|
||||||
|
<p className="text-[9px] font-medium text-white/50 truncate">
|
||||||
|
By: {app.reviewer.display_name}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between pt-1 border-t border-white/10">
|
||||||
|
<p className="text-[9px] font-bold text-white/70">
|
||||||
|
{formatFullDateTime(app.created_at)}
|
||||||
|
</p>
|
||||||
|
<div className="transform translate-x-2 opacity-0 group-hover:translate-x-0 group-hover:opacity-100 transition-all duration-300">
|
||||||
|
<svg className="w-4 h-4 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ export default function UserMetaCard({ data }: { data: UserMetaCardProps }) {
|
|||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
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);
|
||||||
|
|
||||||
|
|
||||||
if (uploadedMedia?.url) {
|
if (uploadedMedia?.url) {
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ export const uploadFileToS3 = async (
|
|||||||
"Content-Type": file.type,
|
"Content-Type": file.type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log("Response from S3 upload:", res);
|
// console.log("Response from S3 upload:", res);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const confirmUpload = async (token_id: string) => {
|
export const confirmUpload = async (token_id: string) => {
|
||||||
const res = await api.post("/media/presigned/complete", {
|
const res = await api.post("/media/presigned/complete", {
|
||||||
token_id,
|
token_id,
|
||||||
});
|
});
|
||||||
console.log("Response from confirm upload:", res);
|
// console.log("Response from confirm upload:", res);
|
||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,12 +80,12 @@ export const uploadMedia = async (file: File) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log("Presigned URL:", presigned);
|
// console.log("Presigned URL:", presigned);
|
||||||
|
|
||||||
await uploadFileToS3(file, presigned);
|
await uploadFileToS3(file, presigned);
|
||||||
|
|
||||||
const media = await confirmUpload(presigned.token_id);
|
const media = await confirmUpload(presigned.token_id);
|
||||||
console.log("Media sau khi upload:", media);
|
// console.log("Media sau khi upload:", media);
|
||||||
return media;
|
return media;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ export const getPresignedUrl = async (file: File) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log("Presigned URL:", presigned);
|
// console.log("Presigned URL:", presigned);
|
||||||
return presigned;
|
return presigned;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { UserData } from '@/interface/user';
|
import { UserData } from '@/interface/user';
|
||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
// Hàm helper để đọc dữ liệu an toàn từ storage
|
|
||||||
const getStoredApplication = () => {
|
const getStoredApplication = () => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
const saved = sessionStorage.getItem('selected_application');
|
const saved = sessionStorage.getItem('selected_application');
|
||||||
@@ -19,7 +18,7 @@ interface UserState {
|
|||||||
const initialState: UserState = {
|
const initialState: UserState = {
|
||||||
data: null,
|
data: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
selectedApplication: getStoredApplication(), // Khởi tạo từ storage
|
selectedApplication: getStoredApplication(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const userSlice = createSlice({
|
const userSlice = createSlice({
|
||||||
@@ -32,7 +31,6 @@ const userSlice = createSlice({
|
|||||||
},
|
},
|
||||||
setSelectedApplication: (state, action: PayloadAction<any>) => {
|
setSelectedApplication: (state, action: PayloadAction<any>) => {
|
||||||
state.selectedApplication = action.payload;
|
state.selectedApplication = action.payload;
|
||||||
// Lưu vào sessionStorage để khi reload trang không bị mất
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
sessionStorage.setItem('selected_application', JSON.stringify(action.payload));
|
sessionStorage.setItem('selected_application', JSON.stringify(action.payload));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user