From 7a6252063c2b929be5064478be18ade9e37619de Mon Sep 17 00:00:00 2001 From: bokhonglo Date: Fri, 17 Apr 2026 10:44:32 +0700 Subject: [PATCH] application page --- .../profile/applications/page.tsx | 38 +++++++++++++++++++ .../(admin)/(others-pages)/profile/page.tsx | 11 +++++- src/components/tables/ApplicationTable.tsx | 6 +-- src/components/ui/parse/SafeHTMLRenderer.tsx | 27 +++++++++++++ .../user-profile/ApplicationList.tsx | 32 ++++++++++++++++ src/store/features/userSlice.ts | 29 ++++++++++++-- 6 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 src/app/(admin)/(others-pages)/profile/applications/page.tsx create mode 100644 src/components/ui/parse/SafeHTMLRenderer.tsx create mode 100644 src/components/user-profile/ApplicationList.tsx diff --git a/src/app/(admin)/(others-pages)/profile/applications/page.tsx b/src/app/(admin)/(others-pages)/profile/applications/page.tsx new file mode 100644 index 0000000..7d3fc6b --- /dev/null +++ b/src/app/(admin)/(others-pages)/profile/applications/page.tsx @@ -0,0 +1,38 @@ +"use client"; +import { useSelector } from "react-redux"; +import { RootState } from "@/store/store"; +import { SafeHTMLRenderer } from "@/components/ui/parse/SafeHTMLRenderer"; + +export default function ApplicationDetailPage() { + const application = useSelector((state: RootState) => state.user.selectedApplication); + + if (!application) { + return
Đang tải hoặc không có dữ liệu...
; + } + + return ( +
+

Chi tiết Application

+ +
+
+ +

{application.verify_type}

+
+ +
+ + + {/* SỬ DỤNG Ở ĐÂY */} +
+ +
+
+ + {/* Các phần khác như Media... */} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/(admin)/(others-pages)/profile/page.tsx b/src/app/(admin)/(others-pages)/profile/page.tsx index 2212cb4..ff5404e 100644 --- a/src/app/(admin)/(others-pages)/profile/page.tsx +++ b/src/app/(admin)/(others-pages)/profile/page.tsx @@ -1,18 +1,20 @@ "use client"; import AccountDetails from "@/components/user-profile/AccountDetails"; +import ApplicationList from "@/components/user-profile/ApplicationList"; import MediaCard from "@/components/user-profile/Media"; import UserInfoCard from "@/components/user-profile/UserInfoCard"; import UserMetaCard from "@/components/user-profile/UserMetaCard"; import { MediaDto } from "@/interface/media"; import { UserMetaCardProps } from "@/interface/user"; import { apiGetCurrentUser } from "@/service/auth"; -import { apiGetCurrentUserMedia } from "@/service/userService"; +import { apiGetCurrentUserApplications, apiGetCurrentUserMedia } from "@/service/userService"; import { useEffect, useState } from "react"; export default function Profile() { const [user, setUser] = useState(null); const [mediaData, setMediaData] = useState(null); + const [applications, setApplications] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -20,6 +22,12 @@ export default function Profile() { try { const userData = await apiGetCurrentUser(); const mediaResponse = await apiGetCurrentUserMedia(); + const userApplications = await apiGetCurrentUserApplications(); + // console.log("User Applications:", userApplications); + + if (userApplications?.data) { + setApplications(userApplications.data); + } setMediaData(mediaResponse); setUser(userData); @@ -42,6 +50,7 @@ export default function Profile() { {(mediaData?.data?.length ?? 0) > 0 && } + diff --git a/src/components/tables/ApplicationTable.tsx b/src/components/tables/ApplicationTable.tsx index 46af1d4..4bf8de7 100644 --- a/src/components/tables/ApplicationTable.tsx +++ b/src/components/tables/ApplicationTable.tsx @@ -255,13 +255,13 @@ export default function ApplicationTable({ {app.reviewer?.display_name || "-"} - +
{app.review_note || "-"}
{app.review_note && ( -
+
{app.review_note} - {/*
*/} +
)} diff --git a/src/components/ui/parse/SafeHTMLRenderer.tsx b/src/components/ui/parse/SafeHTMLRenderer.tsx new file mode 100644 index 0000000..599bab6 --- /dev/null +++ b/src/components/ui/parse/SafeHTMLRenderer.tsx @@ -0,0 +1,27 @@ +"use client"; +import { useEffect, useRef } from "react"; + +// Component này đóng vai trò như một "vùng an toàn" +export function SafeHTMLRenderer({ html }: { html: string }) { + const containerRef = useRef(null); + + useEffect(() => { + if (containerRef.current) { + // 1. Giải mã HTML Entities (Biến < thành <, " thành ", ...) + const txt = document.createElement("textarea"); + txt.innerHTML = html; + let decoded = txt.value; + + // 2. Xử lý xóa thẻ
 bọc ngoài nếu API trả về dư thừa
+      decoded = decoded.replace(/]*>/g, "").replace(/<\/pre>/g, "");
+
+      // 3. Khởi tạo Shadow DOM (nếu chưa có)
+      const shadowRoot = containerRef.current.shadowRoot || containerRef.current.attachShadow({ mode: "open" });
+      
+      // 4. Render nội dung vào trong vùng cô lập
+      shadowRoot.innerHTML = decoded;
+    }
+  }, [html]);
+
+  return 
; +} \ No newline at end of file diff --git a/src/components/user-profile/ApplicationList.tsx b/src/components/user-profile/ApplicationList.tsx new file mode 100644 index 0000000..bd414f5 --- /dev/null +++ b/src/components/user-profile/ApplicationList.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { setSelectedApplication } from "@/store/features/userSlice"; +import { useRouter } from "next/navigation"; +import { useDispatch } from "react-redux"; + +export default function ApplicationList({ applications }: { applications: any[] }) { + const router = useRouter(); + const dispatch = useDispatch(); + + const handleViewDetail = (app: any) => { + dispatch(setSelectedApplication(app)); + router.push(`/profile/applications`); +}; + + return ( +
+ {applications.map((app) => ( +
handleViewDetail(app)} + className="p-4 border rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition" + > +
+ Loại: {app.verify_type} + Xem chi tiết → +
+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/src/store/features/userSlice.ts b/src/store/features/userSlice.ts index df0381a..d966dfe 100644 --- a/src/store/features/userSlice.ts +++ b/src/store/features/userSlice.ts @@ -1,13 +1,25 @@ import { UserData } from '@/interface/user'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +// Hàm helper để đọc dữ liệu an toàn từ storage +const getStoredApplication = () => { + if (typeof window !== "undefined") { + const saved = sessionStorage.getItem('selected_application'); + return saved ? JSON.parse(saved) : null; + } + return null; +}; + interface UserState { data: UserData | null; isAuthenticated: boolean; + selectedApplication: any | null; } + const initialState: UserState = { data: null, isAuthenticated: false, + selectedApplication: getStoredApplication(), // Khởi tạo từ storage }; const userSlice = createSlice({ @@ -18,12 +30,21 @@ const userSlice = createSlice({ state.data = action.payload; state.isAuthenticated = true; }, - clearUserData: (state) => { - state.data = null; - state.isAuthenticated = false; + setSelectedApplication: (state, action: PayloadAction) => { + state.selectedApplication = action.payload; + // Lưu vào sessionStorage để khi reload trang không bị mất + if (typeof window !== "undefined") { + sessionStorage.setItem('selected_application', JSON.stringify(action.payload)); + } + }, + clearSelectedApplication: (state) => { + state.selectedApplication = null; + if (typeof window !== "undefined") { + sessionStorage.removeItem('selected_application'); + } }, }, }); -export const { setUserData, clearUserData } = userSlice.actions; +export const { setUserData, setSelectedApplication, clearSelectedApplication } = userSlice.actions; export default userSlice.reducer; \ No newline at end of file