fix(profil-module): QC improvements based on QC-PROFIL-MODULE.md

- Fix fetch method inconsistency (convert to ApiFetch)
  - programInovasi: findUnique, delete, update methods
  - mediaSosial: findUnique, delete, update methods
- Add loading state to findUnique operations
- Fix iconUrl validation (make optional instead of required)
- Add DOMPurify for HTML sanitization (XSS protection)
  - program-inovasi page.tsx (list & detail)
- Remove console.log in production (use dev-only logging)
- Install dompurify and @types/dompurify

Security: Prevent XSS attacks by sanitizing HTML content
Consistency: Use ApiFetch for all API operations
UX: Proper loading states for better user feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-02-23 15:11:00 +08:00
parent 92b24440fe
commit f2c9a922a6
5 changed files with 121 additions and 152 deletions

View File

@@ -8,6 +8,7 @@ import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import DOMPurify from 'dompurify';
function DetailProgramInovasi() {
const stateProgramInovasi = useProxy(profileLandingPageState.programInovasi)
@@ -85,7 +86,7 @@ function DetailProgramInovasi() {
<Box>
<Text fz="lg" fw="bold">Deskripsi</Text>
<Box pl={5}>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: data.description || '-' }}></Text>
<Text fz="md" c="dimmed" style={{ wordBreak: "break-word", whiteSpace: "normal" }} dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(data.description || '-') }}></Text>
</Box>
</Box>

View File

@@ -6,6 +6,7 @@ import { IconDeviceImacCog, IconPlus, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import DOMPurify from 'dompurify';
import HeaderSearch from '../../../_com/header';
import profileLandingPageState from '../../../_state/landing-page/profile';
@@ -90,7 +91,7 @@ function ListProgramInovasi({ search }: { search: string }) {
<Text fw={500}>{item.name}</Text>
</TableTd>
<TableTd style={{ maxWidth: 250 }}>
<Text fz="sm" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '-' }}></Text>
<Text fz="sm" lineClamp={1} dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.description || '-') }}></Text>
</TableTd>
<TableTd style={{ maxWidth: 250 }}>
<Tooltip label="Buka tautan program" position="top" withArrow>
@@ -144,7 +145,7 @@ function ListProgramInovasi({ search }: { search: string }) {
{/* Description */}
<Box>
<Text fz="sm" fw={600} lh={1.4}>Deskripsi</Text>
<Text dangerouslySetInnerHTML={{ __html: item.description || '-' }} fz="sm" c="gray.7" lineClamp={2} />
<Text dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.description || '-') }} fz="sm" c="gray.7" lineClamp={2} />
</Box>
{/* Link */}