diff --git a/package-lock.json b/package-lock.json index 8bb1642..2f10041 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "axios": "^1.8.1", + "chart.js": "^4.4.8", "cra-template": "1.2.0", "react": "^19.0.0", "react-cookie": "^7.2.2", @@ -2998,6 +2999,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -5506,6 +5513,18 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", + "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", diff --git a/package.json b/package.json index f0fa293..0a9c901 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "axios": "^1.8.1", + "chart.js": "^4.4.8", "cra-template": "1.2.0", "react": "^19.0.0", "react-cookie": "^7.2.2", diff --git a/src/App.js b/src/App.js index a205814..8ad93e9 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,6 @@ -import React from 'react'; -import AppRoutes from './appRoutes/routes' -import { BrowserRouter } from 'react-router-dom'; +import React from "react"; +import AppRoutes from "./appRoutes/routes"; +import { BrowserRouter } from "react-router-dom"; import { UserProvider } from "./contexts/UserContext"; // UserProvider 추가 function App() { diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 4b96854..8278ebd 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -1,45 +1,826 @@ -import React, { useEffect, useState } from "react"; // useEffect를 import -import { useNavigate } from "react-router-dom"; // useNavigate를 import -//import axios from "axios"; +import React, { useEffect, useState, useRef } from "react"; +import { useNavigate } from "react-router-dom"; +import { useUser } from "../contexts/UserContext"; // UserContext 추가 import styles from "../styles/Dashboard.module.css"; import NavBar from "./NavBar"; +import Chart from "chart.js/auto"; const Dashboard = () => { const navigate = useNavigate(); - //const [user, setUser] = useState(null); + const { user } = useUser(); // UserContext에서 user 가져오기 const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const chartRef = useRef(null); + const chartInstance = useRef(null); + const checklistChartRef = useRef(null); + const checklistChartInstance = useRef(null); + const monthlyChartRef = useRef(null); + const monthlyChartInstance = useRef(null); + const [monthlyRiskData, setMonthlyRiskData] = useState([]); + const monthlyRiskChartRef = useRef(null); + const monthlyRiskChartInstance = useRef(null); + const [sensitiveInfoData, setSensitiveInfoData] = useState([]); + const sensitiveInfoChartRef = useRef(null); + const sensitiveInfoChartInstance = useRef(null); - const securityData = [ - { type: "주소", count: 150 }, - { type: "전화번호", count: 120 }, - { type: "이메일", count: 100 }, - { type: "주민번호", count: 80 }, - { type: "신용카드", count: 50 }, - ]; + // 월간 데이터 상태 + const [monthlyData, setMonthlyData] = useState({ + totalCount: 0, + dateData: [], + }); - useEffect(() => { - // 로그인 상태 확인 - const token = localStorage.getItem("token"); - const storedUser = localStorage.getItem("user"); + // 체크리스트 상태 + const [checklistData, setChecklistData] = useState([]); + const [checklistInput, setChecklistInput] = useState(""); + + // 서버에서 가져온 데이터를 저장할 상태 + const [dashboardData, setDashboardData] = useState({ + today: { + detectedCount: 0, + sensitiveTypes: [], + lastExecutionDate: null, + lastExecutionCount: 0, + changeRate: 0, + }, + monthly: { + thisMonth: 0, + lastMonth: 0, + changePercent: 0, + }, + safetyScore: 85, + globalPercentile: 15, + }); - if (!token || !storedUser) { + // 안전 점수 데이터 + const [safetyData, setSafetyData] = useState({ + currentScore: 0, + previousScore: 0, + scoreDifference: 0, + percentile: 0, + }); + + useEffect(() => { + // 로그인 상태 확인 - useUser 훅 사용 + if (!user) { alert("로그인이 필요합니다."); navigate("/login"); return; } - //setUser(JSON.parse(storedUser)); - setIsLoading(false); + // 사용자 이메일 가져오기 + const userEmail = user.email; + + // 대시보드 데이터 가져오기 + fetchDashboardData(userEmail); + + // 안전 점수 데이터 가져오기 + fetchSafetyData(userEmail); + + // 체크리스트 데이터 로드 (localStorage) + loadChecklistData(); + + // 월간 데이터 가져오기 + fetchMonthlyData(userEmail); + + // 월간 위험 감지 데이터 가져오기 + fetchMonthlyRiskData(userEmail); + + // 유형별 민감 정보 데이터 가져오기 + fetchSensitiveInfoData(userEmail); + + return () => { + // 컴포넌트 언마운트 시 차트 정리 + if (chartInstance.current) { + chartInstance.current.destroy(); + } + if (checklistChartInstance.current) { + checklistChartInstance.current.destroy(); + } + if (monthlyChartInstance.current) { + monthlyChartInstance.current.destroy(); + } + + if (monthlyRiskChartInstance.current) { + monthlyRiskChartInstance.current.destroy(); + } + + if (sensitiveInfoChartInstance.current) { + sensitiveInfoChartInstance.current.destroy(); + } + }; + }, [user, navigate]); // user 상태 추가 + + // 월간 데이터 가져오기 + // 1. fetchMonthlyData 함수 수정 + // fetchMonthlyData 함수 수정 + const fetchMonthlyData = async (email) => { + try { + const response = await fetch( + `http://localhost:5000/api/dashboard/monthly-data?email=${encodeURIComponent( + email + )}` + ); + + if (!response.ok) { + throw new Error("월간 데이터를 가져오는데 실패했습니다."); + } + + const data = await response.json(); + console.log("월간 데이터 API 응답:", data); + + if (data.success) { + // API 응답 데이터 형식에 맞게 조정 (한 번만 호출) + setMonthlyData({ + totalCount: data.data.totalCount || 0, + dateData: data.data.dailyData || [], + }); + } else { + throw new Error(data.message || "월간 데이터 로드 실패"); + } + } catch (error) { + console.error("월간 데이터 로드 오류:", error); + setMonthlyData({ + totalCount: 0, + dateData: [], + }); + } + }; + + // 2. 월간 차트 생성 부분 수정 + // 월간 차트 생성 및 업데이트 + useEffect(() => { + // 로드 중이거나 오류가 있다면 차트를 그리지 않음 + if (isLoading || error) return; + + // 약간의 지연을 두어 DOM이 완전히 렌더링된 후 차트 생성 + const timer = setTimeout(() => { + if (!monthlyChartRef.current) { + console.log("차트 ref가 없습니다 - 지연 후 체크"); + return; + } + + console.log("월간 차트 useEffect 실행:", monthlyData); + + // 기존 차트가 있으면 파괴 + if (monthlyChartInstance.current) { + monthlyChartInstance.current.destroy(); + } + + // 데이터 준비 + const labels = monthlyData.dateData.map((item) => item.date); + const values = monthlyData.dateData.map((item) => item.detection_count); + + console.log("차트 데이터:", { labels, values }); + + // 차트 생성 + const ctx = monthlyChartRef.current.getContext("2d"); + monthlyChartInstance.current = new Chart(ctx, { + // 기존 차트 옵션 유지 + type: "bar", + data: { + labels: labels, + datasets: [ + { + data: values, + backgroundColor: "rgba(104, 132, 245, 0.7)", + borderColor: "rgba(104, 132, 245, 1)", + borderWidth: 1, + }, + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + }, + scales: { + x: { + grid: { + display: false, + }, + ticks: { + color: "#94a3b8", + }, + }, + y: { + grid: { + display: false, + }, + beginAtZero: true, + ticks: { + color: "#94a3b8", + }, + }, + }, + }, + }); + }, 100); // 100ms 지연 + + return () => clearTimeout(timer); + }, [monthlyData, isLoading, error]); + + // 월간 위험 감지 차트 생성 및 업데이트 + useEffect(() => { + if (isLoading || error || !monthlyRiskChartRef.current) return; + + // 데이터가 비어있는지 확인 + if (!monthlyRiskData.length) return; + + // 약간의 지연을 두어 DOM이 완전히 렌더링된 후 차트 생성 + const timer = setTimeout(() => { + // 기존 차트가 있으면 파괴 + if (monthlyRiskChartInstance.current) { + monthlyRiskChartInstance.current.destroy(); + } + + // 데이터 준비 - 월 표시를 더 읽기 쉽게 변환 (YYYY-MM -> YYYY년 MM월) + const labels = monthlyRiskData.map((item) => { + const [year, month] = item.month.split("-"); + return `${year}년 ${month}월`; + }); + const values = monthlyRiskData.map((item) => item.count); + + // 차트 생성 + const ctx = monthlyRiskChartRef.current.getContext("2d"); + monthlyRiskChartInstance.current = new Chart(ctx, { + type: "line", + data: { + labels: labels, + datasets: [ + { + label: "감지 건수", + data: values, + borderColor: "#818cf8", + backgroundColor: "rgba(129, 140, 248, 0.2)", + borderWidth: 2, + pointRadius: 4, + fill: true, + tension: 0.4, // 곡선 형태로 표시 + }, + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + tooltip: { + callbacks: { + title: function (tooltipItems) { + return tooltipItems[0].label; + }, + label: function (context) { + return `감지 건수: ${context.parsed.y}건`; + }, + }, + }, + }, + scales: { + x: { + grid: { + display: false, + }, + ticks: { + color: "#94a3b8", + }, + }, + y: { + grid: { + display: false, + }, + beginAtZero: true, + ticks: { + color: "#94a3b8", + }, + }, + }, + }, + }); + }, 100); + + return () => clearTimeout(timer); + }, [monthlyRiskData, isLoading, error]); + + // 월간 위험 감지 데이터 가져오기 함수 + const fetchMonthlyRiskData = async (email) => { + try { + const response = await fetch( + `http://localhost:5000/api/dashboard/monthly-risk?email=${encodeURIComponent( + email + )}` + ); + + if (!response.ok) { + throw new Error("월간 위험 감지 데이터를 가져오는데 실패했습니다."); + } + + const data = await response.json(); + console.log("월간 위험 감지 API 응답:", data); + + if (data.success) { + setMonthlyRiskData(data.data || []); + } else { + throw new Error(data.message || "월간 위험 감지 데이터 로드 실패"); + } + } catch (error) { + console.error("월간 위험 감지 데이터 로드 오류:", error); + setMonthlyRiskData([]); + } + }; + + // 유형별 민감 정보 데이터 가져오기 함수 + const fetchSensitiveInfoData = async (email) => { + try { + const response = await fetch( + `http://localhost:5000/api/dashboard/sensitive-info?email=${encodeURIComponent( + email + )}` + ); + + if (!response.ok) { + throw new Error("유형별 민감 정보를 가져오는데 실패했습니다."); + } + + const data = await response.json(); + console.log("유형별 민감 정보 API 응답:", data); + + if (data.success) { + setSensitiveInfoData(data.data || []); + } else { + throw new Error(data.message || "유형별 민감 정보 로드 실패"); + } + } catch (error) { + console.error("유형별 민감 정보 로드 오류:", error); + setSensitiveInfoData([]); + } + }; + + // 유형별 민감 정보 차트 생성 및 업데이트 + useEffect(() => { + if (isLoading || error || !sensitiveInfoChartRef.current) return; + + // 데이터가 비어있는지 확인 + if (!sensitiveInfoData.length) return; + + // 약간의 지연을 두어 DOM이 완전히 렌더링된 후 차트 생성 + const timer = setTimeout(() => { + // 기존 차트가 있으면 파괴 + if (sensitiveInfoChartInstance.current) { + sensitiveInfoChartInstance.current.destroy(); + } + + // 데이터 준비 + const labels = sensitiveInfoData.map((item) => item.content_type); + const values = sensitiveInfoData.map((item) => item.count); + + // 배경색 배열 + const backgroundColors = [ + "rgba(104, 132, 245, 0.8)", + "rgba(235, 87, 87, 0.8)", + "rgba(240, 180, 41, 0.8)", + "rgba(46, 213, 115, 0.8)", + "rgba(156, 136, 255, 0.8)", + ]; + + // 차트 생성 + const ctx = sensitiveInfoChartRef.current.getContext("2d"); + sensitiveInfoChartInstance.current = new Chart(ctx, { + type: "pie", + data: { + labels: labels, + datasets: [ + { + data: values, + backgroundColor: backgroundColors.slice(0, labels.length), + borderWidth: 1, + borderColor: "rgba(255, 255, 255, 0.2)", + }, + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: "bottom", + labels: { + color: "#94a3b8", + font: { size: 12 }, + padding: 15, + }, + }, + tooltip: { + callbacks: { + label: function (context) { + const label = context.label || ""; + const value = context.parsed || 0; + const total = context.dataset.data.reduce((a, b) => a + b, 0); + const percentage = ((value / total) * 100).toFixed(1); + return `${label}: ${value}건 (${percentage}%)`; + }, + }, + }, + }, + }, + }); + }, 100); + + return () => clearTimeout(timer); + }, [sensitiveInfoData, isLoading, error]); + + // 안전 점수 차트 생성 및 업데이트 + useEffect(() => { + if (!chartRef.current) return; + + // 기존 차트가 있으면 파괴 + if (chartInstance.current) { + chartInstance.current.destroy(); + } + + // 중앙 텍스트 플러그인 등록 + const centerTextPlugin = { + id: "centerText", + beforeDraw(chart) { + const { width, height } = chart; + const { ctx } = chart; + const text = chart.options.plugins.centerText.text || "0"; + + ctx.save(); + ctx.font = "bold 40px Arial"; + ctx.fillStyle = "#ffffff"; // 흰색 텍스트 (다크 테마) + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(text, width / 2, height / 2); + ctx.restore(); + }, + }; + + // 차트 생성 + const ctx = chartRef.current.getContext("2d"); + chartInstance.current = new Chart(ctx, { + type: "doughnut", + data: { + datasets: [ + { + data: [safetyData.currentScore, 100 - safetyData.currentScore], + backgroundColor: ["#3B82F6", "rgba(255, 255, 255, 0.1)"], // 파란색, 투명 흰색 (다크 테마) + }, + ], + }, + options: { + responsive: true, + plugins: { + tooltip: { + callbacks: { + label: function (context) { + return context.label + ": " + context.parsed + "%"; + }, + }, + }, + centerText: { + text: safetyData.currentScore.toString(), + }, + legend: { + display: false, // 범례 숨김 + }, + }, + cutout: "80%", + }, + plugins: [centerTextPlugin], + }); + }, [safetyData.currentScore]); + + // 체크리스트 차트 생성 및 업데이트 + useEffect(() => { + if (!checklistChartRef.current) return; + + // 기존 차트가 있으면 파괴 + if (checklistChartInstance.current) { + checklistChartInstance.current.destroy(); + } + + // 차트 중앙에 완료/전체 항목 수를 표시하는 커스텀 플러그인 + const centerTextPlugin = { + id: "centerTextChecklist", + afterDraw: (chart) => { + const { ctx, width, height } = chart; + ctx.save(); + const completedCount = checklistData.filter( + (item) => item.completed + ).length; + const totalCount = checklistData.length; + const text = totalCount ? `${completedCount}/${totalCount}` : `0/0`; + ctx.font = "bold 20px Arial"; + ctx.fillStyle = "#ffffff"; // 흰색 텍스트 (다크 테마) + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + ctx.fillText(text, width / 2, height / 2); + ctx.restore(); + }, + }; + + const completedCount = checklistData.filter( + (item) => item.completed + ).length; + const totalCount = checklistData.length; + const uncompletedCount = totalCount - completedCount; + + // 체크리스트 차트 생성 + const ctx = checklistChartRef.current.getContext("2d"); + checklistChartInstance.current = new Chart(ctx, { + type: "doughnut", + data: { + labels: ["완료", "미완료"], + datasets: [ + { + data: [completedCount, uncompletedCount], + backgroundColor: ["#3B82F6", "rgba(255, 255, 255, 0.1)"], // 파란색, 투명 흰색 (다크 테마) + borderWidth: 0, + }, + ], + }, + options: { + cutout: "70%", + plugins: { + legend: { display: false }, + tooltip: { enabled: false }, + }, + }, + plugins: [centerTextPlugin], + }); + }, [checklistData]); + + // 체크리스트 데이터 로드 + const loadChecklistData = () => { + try { + const savedData = localStorage.getItem("securityChecklist"); + if (savedData) { + setChecklistData(JSON.parse(savedData)); + } + } catch (error) { + console.error("체크리스트 데이터 로드 오류:", error); + } + }; + + // 체크리스트 데이터 저장 + const saveChecklistData = (data) => { + try { + localStorage.setItem("securityChecklist", JSON.stringify(data)); + } catch (error) { + console.error("체크리스트 데이터 저장 오류:", error); + } + }; + + // 체크리스트 항목 추가 핸들러 + const handleAddChecklistItem = (e) => { + e.preventDefault(); + const trimmedInput = checklistInput.trim(); + + if (trimmedInput) { + // 한 줄씩 입력 (공백 제거) + const lines = trimmedInput.split("\n"); + const newItems = lines + .map((line) => line.trim()) + .filter((line) => line !== "") + .map((line) => ({ + text: line, + completed: false, + })); + + const updatedList = [...checklistData, ...newItems]; + setChecklistData(updatedList); + saveChecklistData(updatedList); + setChecklistInput(""); + } + }; + + // 체크리스트 항목 상태 변경 핸들러 + const handleToggleChecklistItem = (index) => { + const updatedList = [...checklistData]; + updatedList[index].completed = !updatedList[index].completed; + setChecklistData(updatedList); + saveChecklistData(updatedList); + }; + + // 체크리스트 항목 삭제 핸들러 + const handleDeleteChecklistItem = (index) => { + const updatedList = checklistData.filter((_, i) => i !== index); + setChecklistData(updatedList); + saveChecklistData(updatedList); + }; + + // 안전 점수 계산 함수 + const calculateSafetyScore = (leakData) => { + const sensitivityWeights = { 낮음: 0.1, 중간: 0.3, 높음: 0.7 }; + + // 카테고리별 유출 개수 집계 + let categoryCounts = {}; + let categoryWeights = {}; + + leakData.forEach(({ content_type, sensitivity_level, count }) => { + categoryCounts[content_type] = + (categoryCounts[content_type] || 0) + count; + categoryWeights[content_type] = + sensitivityWeights[sensitivity_level] || 0.3; // 기본값 중간 + }); + + const categories = Object.keys(categoryCounts); + const counts = Object.values(categoryCounts); + const weights = categories.map((cat) => categoryWeights[cat]); + + const N = counts.reduce((acc, val) => acc + val, 0); + const w_total = 0.05 + Math.min(0.02 * N, 2); + + let weightedSum = 0; + for (let i = 0; i < categories.length; i++) { + weightedSum += weights[i] * counts[i]; + } + + const logTerm = Math.log(1 + w_total * N); + const S = 100 - weightedSum * logTerm; + + return Math.min(100, Math.max(0, Math.floor(S))); + }; + + // 안전 점수 데이터 가져오기 + const fetchSafetyData = async (email) => { + try { + // 실제로는 API 요청을 보내서 데이터를 가져와야 함 + // 여기서는 대시보드 데이터로 계산 + + // 이전 유출 기록 (어제까지의 데이터) + const previousLeaksResponse = await fetch( + `http://localhost:5000/api/dashboard/previous-leaks?email=${encodeURIComponent( + email + )}` + ); + + if (!previousLeaksResponse.ok) { + throw new Error("이전 유출 기록을 가져오는데 실패했습니다."); + } + + const previousLeaksData = await previousLeaksResponse.json(); + const previousScore = calculateSafetyScore(previousLeaksData.data || []); + + // 현재 유출 기록 (오늘 데이터) + const currentLeaksResponse = await fetch( + `http://localhost:5000/api/dashboard/current-leaks?email=${encodeURIComponent( + email + )}` + ); + + if (!currentLeaksResponse.ok) { + throw new Error("현재 유출 기록을 가져오는데 실패했습니다."); + } + + const currentLeaksData = await currentLeaksResponse.json(); + const currentScore = calculateSafetyScore(currentLeaksData.data || []); + + // 전체 사용자 데이터 + const allUsersResponse = await fetch( + `http://localhost:5000/api/dashboard/all-users-leaks` + ); + + if (!allUsersResponse.ok) { + throw new Error("전체 사용자 데이터를 가져오는데 실패했습니다."); + } + + const allUsersData = await allUsersResponse.json(); + + // 각 사용자별 안전 점수 계산 + const allScores = allUsersData.data.map((user) => { + return { + email: user.email, + score: calculateSafetyScore(user.leaks || []), + }; + }); + + // 현재 사용자보다 점수가 높은 사용자 수 + const higherScores = allScores.filter( + (item) => item.score > currentScore + ).length; + + // 백분위 계산 + const percentile = + allScores.length > 0 + ? Math.round((higherScores / allScores.length) * 100) + : 50; // 데이터가 없을 경우 기본값 + + setSafetyData({ + currentScore, + previousScore, + scoreDifference: currentScore - previousScore, + percentile, + }); + } catch (error) { + console.error("안전 점수 데이터 로드 오류:", error); + // 오류 발생 시 기본 데이터 + setSafetyData({ + currentScore: dashboardData.safetyScore, + previousScore: + dashboardData.safetyScore - + parseInt(dashboardData.monthly.changePercent), + scoreDifference: parseInt(dashboardData.monthly.changePercent), + percentile: dashboardData.globalPercentile, + }); + } + }; + + // 대시보드 데이터 가져오기 + const fetchDashboardData = async (email) => { + try { + setIsLoading(true); + setError(null); - // 토큰 유효성 검증 (선택적) - // 서버에 토큰 검증 요청을 보내고 유효하지 않으면 로그아웃 처리 - }, [navigate]); + const response = await fetch( + `http://localhost:5000/api/dashboard/summary?email=${encodeURIComponent( + email + )}` + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error("API 응답:", errorText); + throw new Error("대시보드 데이터를 불러오는데 실패했습니다."); + } + + const data = await response.json(); + console.log("받은 대시보드 데이터:", data); + + if (data.success) { + setDashboardData(data.data); + + // 백엔드 API가 없는 경우 대시보드 데이터로 안전 점수 설정 + setSafetyData({ + currentScore: data.data.safetyScore, + previousScore: + data.data.safetyScore - parseInt(data.data.monthly.changePercent), + scoreDifference: parseInt(data.data.monthly.changePercent), + percentile: data.data.globalPercentile, + }); + } else { + throw new Error(data.message || "데이터 로드 실패"); + } + } catch (error) { + console.error("데이터 로드 오류:", error); + setError(error.message); + + // 오류 발생 시 기본 데이터 설정 + setDashboardData({ + today: { + detectedCount: 0, + sensitiveTypes: [], + lastExecutionDate: null, + lastExecutionCount: 0, + changeRate: 0, + }, + monthly: { + thisMonth: 0, + lastMonth: 0, + changePercent: 0, + }, + safetyScore: 100, + globalPercentile: 15, + }); + } finally { + setIsLoading(false); + } + }; + + // 증가/감소에 따른 색상 및 기호 반환 + const getChangeStyle = (value) => { + const numValue = parseFloat(value); + if (numValue > 0) { + return { color: "#FF4D4F", symbol: "↑", text: "증가" }; + } else if (numValue < 0) { + return { color: "#52C41A", symbol: "↓", text: "감소" }; + } else { + return { color: "#8C8C8C", symbol: "-", text: "변동 없음" }; + } + }; if (isLoading) { return
로딩 중...
; } - const maxCount = Math.max(...securityData.map((item) => item.count)); + // 가장 높은 감지 횟수 계산 (프로그레스 바용) + const maxCount = + dashboardData.today.sensitiveTypes.length > 0 + ? Math.max( + ...dashboardData.today.sensitiveTypes.map((item) => item.count) + ) + : 1; // 0으로 나누는 것을 방지 + + // 최근 실행일 변화 스타일 + const dailyChangeStyle = getChangeStyle(dashboardData.today.changeRate); + + // 안전 점수 변화 스타일 + const safetyScoreChangeStyle = getChangeStyle(safetyData.scoreDifference); return (
@@ -48,56 +829,154 @@ const Dashboard = () => {

개인 안전 대시보드

+ {error &&
{error}
} +
+ {/* 민감 텍스트 카드 (HTML 내용을 React 컴포넌트로 변환) */}
-

감지된 위험 텍스트

-
523
-

지난 30일 동안

+

오늘 감지된 위험 텍스트

+
+ {dashboardData.today.detectedCount} +
+ {dashboardData.today.lastExecutionDate ? ( +

+ 최근 실행일({dashboardData.today.lastExecutionDate}) 대비{" "} + + {dailyChangeStyle.symbol}{" "} + {Math.abs(dashboardData.today.changeRate)}% + +

+ ) : ( +

오늘 하루 동안

+ )} + + {/* 유형별 목록 */} + {dashboardData.today.sensitiveTypes.length > 0 && ( +
+ {dashboardData.today.sensitiveTypes.map((type, index) => ( +

+ {type.type} ({type.count}건) +

+ ))} +
+ )}
+ {/* 안전 점수 카드 (차트로 표시) */}

안전 점수

-
85/100
-

전월 대비 3% 상승

+
+ +
+

+ 이전 대비{" "} + + {safetyScoreChangeStyle.symbol}{" "} + {Math.abs(safetyData.scoreDifference)}점 + +

+ {/* 체크리스트 카드 */}
-

전세계 평균 대비

-
상위 15%
-

안전한 수준입니다

+

보안 점검 체크리스트

+
+ +
+ + {/* 체크리스트 추가 폼 */} +
+ setChecklistInput(e.target.value)} + /> + +
+ + {/* 체크리스트 목록 */} +
    + {checklistData.map((item, index) => ( +
  • + + +
  • + ))} +
+ {/* 한 달간 감지된 민감 텍스트 (새로 추가된 부분) */} +
+
+

한 달간 감지된 민감 텍스트

+
+
+
+ {monthlyData.totalCount}건 +
+

이번 달 총 감지 건수

+
+
+ +
+
+
+
+ + {/* 두 번째 행: 월간 위험 감지와 유형별 민감 정보 차트 */}
+ {/* 월간 위험 감지 차트 */}
-

민감 정보 유형별 감지 횟수

- - - - - - - - - - {securityData.map((item) => ( - - - - - - ))} - -
유형횟수 - 분포 -
{item.type}{item.count} -
-
-
-
+

월간 위험 감지 추이

+
+ +
+
+ + {/* 유형별 민감 정보 차트 */} +
+

유형별 민감 정보

+
+ +
diff --git a/src/components/HeroPage.jsx b/src/components/HeroPage.jsx index 496ce90..19d5f6e 100644 --- a/src/components/HeroPage.jsx +++ b/src/components/HeroPage.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useRef } from "react"; -import { useNavigate, useLocation } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import styles from "../styles/HeroPage.module.css"; import NavBar from "./NavBar"; @@ -46,7 +46,6 @@ const FeatureCard = ({ icon, title, description, delay }) => { // Main Hero Page Component const HeroPage = () => { const navigate = useNavigate(); - const location = useLocation(); const [scrolled, setScrolled] = useState(false); const [copied, setCopied] = useState(false); const [user, setUser] = useState(null); @@ -124,7 +123,9 @@ const HeroPage = () => { }; // 북마크릿 코드 - 로그인 시에만 실제 코드를 보여줄 것임 - const bookmarkletCode = `일단 비워둠`; + const bookmarkletCode = + `javascript:%28function%28%29%20%7B%0A%0A%20%20%20%20const%20USER_EMAIL%20%3D%20%22%24%7BuserEmail%7D%22%3B%0A%20%20%20%20%0A%20%20%20%20%2F%2F%20%EC%9E%85%EB%A0%A5%EC%B0%BD%20%EC%B0%BE%EA%B8%B0%0A%20%20%20%20let%20inputBox%20%3D%20document.querySelector%28%22div%5Bcontenteditable%3D%27true%27%5D%22%29%3B%0A%20%20%20%20if%20%28%21inputBox%29%20%7B%0A%20%20%20%20%20%20alert%28%22%E2%9D%8C%20%EC%9E%85%EB%A0%A5%EC%B0%BD%EC%9D%84%20%EC%B0%BE%EC%9D%84%20%EC%88%98%20%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4.%20GPT%20%ED%8E%98%EC%9D%B4%EC%A7%80%EC%97%90%EC%84%9C%20%EC%8B%A4%ED%96%89%ED%95%B4%EC%A3%BC%EC%84%B8%EC%9A%94%21%22%29%3B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%0A%20%20%20%20%2F%2F%20%ED%8C%A8%ED%84%B4%EC%9D%84%20%EC%9D%B4%EC%9A%A9%ED%95%9C%20%EA%B8%B0%EB%B3%B8%20%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%EA%B0%90%EC%A7%80%20%28%EC%9D%B4%EB%A6%84%20%EA%B0%90%EC%A7%80%20%EC%B6%94%EA%B0%80%29%0A%20%20%20%20function%20localCheckSensitiveInfo%28text%29%20%7B%0A%20%20%20%20%20%20%20%20const%20patterns%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%9D%B4%EB%A9%94%EC%9D%BC%20%ED%8C%A8%ED%84%B4%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cb%5BA-Za-z0-9._%25%2B-%5D%2B%40%5BA-Za-z0-9.-%5D%2B%5C.%5BA-Z%7Ca-z%5D%7B2%2C%7D%5Cb%2F%2C%20type%3A%20%27EMAIL%27%20%7D%2C%20%20%2F%2F%20%EA%B8%B0%EB%B3%B8%20%EC%9D%B4%EB%A9%94%EC%9D%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Ba-zA-Z0-9_.%2B-%5D%2B%40%5Ba-zA-Z0-9-%5D%2B%5C.%5Ba-zA-Z0-9-.%5D%2B%2F%2C%20type%3A%20%27EMAIL%27%20%7D%2C%20%20%2F%2F%20%EB%8D%94%20%EB%84%93%EC%9D%80%20%EB%B2%94%EC%9C%84%EC%9D%98%20%EC%9D%B4%EB%A9%94%EC%9D%BC%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Ba-zA-Z0-9_.%2B-%5D%2B%28%3F%3A%5Cs%2B%29%3F%40%28%3F%3A%5Cs%2B%29%3F%5Ba-zA-Z0-9-%5D%2B%5C.%2B%5Ba-zA-Z0-9-.%5D%2B%2F%2C%20type%3A%20%27EMAIL%27%20%7D%2C%20%20%2F%2F%20%EC%9D%B4%EB%A9%94%EC%9D%BC%20%EA%B3%B5%EB%B0%B1%20%ED%97%88%EC%9A%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Ba-zA-Z0-9_.%2B-%5D%2B%5C%28at%5C%29%5Ba-zA-Z0-9-%5D%2B%5C.%5Ba-zA-Z0-9-.%5D%2B%2F%2C%20type%3A%20%27EMAIL%27%20%7D%2C%20%20%2F%2F%20at%20%EA%B5%AC%EB%AC%B8%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Ba-zA-Z0-9_.%2B-%5D%2B%28%3F%3A%5B%5C%5B%5C%28%5Dat%5B%5C%5D%5C%29%5D%29%5Ba-zA-Z0-9-%5D%2B%5C.%5Ba-zA-Z0-9-.%5D%2B%2F%2C%20type%3A%20%27EMAIL%27%20%7D%2C%20%20%2F%2F%20%EA%B4%84%ED%98%B8%20%EA%B5%AC%EB%AC%B8%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%A3%BC%EB%AF%BC%EB%93%B1%EB%A1%9D%EB%B2%88%ED%98%B8%20%ED%8C%A8%ED%84%B4%20%28%EC%A0%95%ED%99%95%ED%95%9C%20%EA%B5%AC%EB%B6%84%EC%9D%84%20%EC%9C%84%ED%95%B4%20%EC%88%98%EC%A0%95%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cb%5Cd%7B6%7D-%5Cd%7B7%7D%5Cb%28%3F%21%5B%5Cd%5Cs%5C.%5C-%2F%5D%29%2F%2C%20type%3A%20%27SSN%27%20%7D%2C%20%20%2F%2F%20%EC%A3%BC%EB%AF%BC%EB%93%B1%EB%A1%9D%EB%B2%88%ED%98%B8%20%ED%98%95%EC%8B%9D%20%286%EC%9E%90%EB%A6%AC-7%EC%9E%90%EB%A6%AC%29%20-%20%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8%EB%A1%9C%20%EC%9D%B8%EC%8B%9D%EB%90%98%EC%A7%80%20%EC%95%8A%EB%8F%84%EB%A1%9D%20%EB%B3%B4%EC%A0%95%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%8B%A0%EC%9A%A9%EC%B9%B4%EB%93%9C%20%EB%B2%88%ED%98%B8%20%28%EB%8B%A4%EC%96%91%ED%95%9C%20%EC%B9%B4%EB%93%9C%EC%82%AC%20%ED%98%95%EC%8B%9D%20%EC%A7%80%EC%9B%90%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cb%5Cd%7B4%7D-%5Cd%7B4%7D-%5Cd%7B4%7D-%5Cd%7B4%7D%5Cb%2F%2C%20type%3A%20%27CREDIT_DEBIT_NUMBER%27%20%7D%2C%20%20%2F%2F%20%EA%B8%B0%EB%B3%B8%20%EC%8B%A0%EC%9A%A9%EC%B9%B4%EB%93%9C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cd%7B4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B3%2C4%7D%2F%2C%20type%3A%20%27CREDIT_DEBIT_NUMBER%27%20%7D%2C%20%20%2F%2F%20%EA%B8%B0%EB%B3%B8%20%EC%8B%A0%EC%9A%A9%EC%B9%B4%EB%93%9C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F3%5B47%5D%5Cd%7B2%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B6%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B5%7D%2F%2C%20type%3A%20%27CREDIT_DEBIT_NUMBER%27%20%7D%2C%20%20%2F%2F%20American%20Express%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F6%28%3F%3A011%7C5%5Cd%7B2%7D%29%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%2F%2C%20type%3A%20%27CREDIT_DEBIT_NUMBER%27%20%7D%2C%20%20%2F%2F%20Discover%20%EC%B9%B4%EB%93%9C%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%ED%95%9C%EA%B5%AD%EC%96%B4%20%EC%9D%B4%EB%A6%84%20%ED%8C%A8%ED%84%B4%20%EC%B6%94%EA%B0%80%20%282-4%EC%9E%90%EC%9D%98%20%ED%95%9C%EA%B8%80%20%EC%9D%B4%EB%A6%84%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5E%28%5B%EA%B0%80-%ED%9E%A3%5D%7B2%2C4%7D%29%28%3F%3A%5Cs%3F%28%3F%3A%EB%8B%98%7C%EC%94%A8%7C%EC%9D%B4%7C%EA%B0%80%7C%EA%BB%98%29%3F%29%5Cs%3F%28%3F%3D%5B%EA%B0%80-%ED%9E%A3%7C%5Cs%5D%2A%24%29%2F%2C%20type%3A%20%27NAME%27%20%7D%2C%20%20%2F%2F%20%ED%95%9C%EA%B8%80%20%EC%9D%B4%EB%A6%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%98%81%EB%AC%B8%20%EC%9D%B4%EB%A6%84%20%ED%8C%A8%ED%84%B4%20-%20%EC%84%B1%2B%EC%9D%B4%EB%A6%84%20%ED%98%95%ED%83%9C%EC%9D%98%20%ED%8C%A8%ED%84%B4%20%EC%B6%94%EA%B0%80%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cb%5BA-Z%5D%5Ba-z%5D%2B%5Cs%5BA-Z%5D%5Ba-z%5D%2B%5Cb%2F%2C%20type%3A%20%27NAME%27%20%7D%2C%20%20%2F%2F%20%EC%98%81%EB%AC%B8%20%EC%9D%B4%EB%A6%84%20%28%EC%84%B1%2B%EC%9D%B4%EB%A6%84%29%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%97%AC%EA%B6%8C%20%EB%B2%88%ED%98%B8%20%ED%8C%A8%ED%84%B4%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2FM%5Cd%7B8%7D%7CM%5B0-9A-Z%5D%7B8%7D%2F%2C%20type%3A%20%27PASSPORT_NUMBER%27%20%7D%2C%20%20%2F%2F%20%EC%97%AC%EA%B6%8C%20%EB%B2%88%ED%98%B8%20%28%EA%B8%B0%EA%B3%84%ED%8C%90%EB%8F%85%EC%8B%9D%2C%20%EC%98%81%EB%AC%B8%20%ED%8F%AC%ED%95%A8%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5BA-Z%5D%7B2%7D%5Cd%7B7%7D%2F%2C%20type%3A%20%27PASSPORT_NUMBER%27%20%7D%2C%20%20%2F%2F%20%EC%9D%BC%EB%B0%98%20%EC%97%AC%EA%B6%8C%20%ED%98%95%EC%8B%9D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%28%3F%3ABS%7CUS%7CDG%7CGB%7CGN%7CGJ%7CDJ%7CIC%7CSO%7CSJ%7CSW%7CCH%7CKW%7CTJ%7CHJ%7CHD%7CPS%29%5Cd%7B7%7D%2F%2C%20type%3A%20%27PASSPORT_NUMBER%27%20%7D%2C%20%20%2F%2F%20%EC%A7%80%EC%97%AD%20%EC%BD%94%EB%93%9C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5BA-Z%5D%7B1%2C2%7D%5Cd%7B7%2C8%7D%2F%2C%20type%3A%20%27PASSPORT_NUMBER%27%20%7D%2C%20%20%2F%2F%20%EA%B8%B0%ED%83%80%20%EA%B5%AD%EA%B0%80%20%EC%97%AC%EA%B6%8C%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8%20%ED%8C%A8%ED%84%B4%20%28%EC%A3%BC%EB%AF%BC%EB%93%B1%EB%A1%9D%EB%B2%88%ED%98%B8%EC%99%80%EC%9D%98%20%ED%98%BC%EB%8F%99%EC%9D%84%20%EB%A7%89%EA%B8%B0%20%EC%9C%84%ED%95%B4%20%EA%B5%AC%EC%B2%B4%ED%99%94%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F01%5B0-9%5D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B3%2C4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%2F%2C%20type%3A%20%27PHONE%27%20%7D%2C%20%20%2F%2F%20%EA%B5%AD%EB%82%B4%20%ED%9C%B4%EB%8C%80%ED%8F%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5C%2B82%5B%5Cs%5C-%5C.%5D%3F1%5B0-9%5D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B3%2C4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%2F%2C%20type%3A%20%27PHONE%27%20%7D%2C%20%20%2F%2F%20%EA%B5%AD%EC%A0%9C%20%ED%98%95%EC%8B%9D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F0%5Cd%7B1%2C2%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B3%2C4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%2F%2C%20type%3A%20%27PHONE%27%20%7D%2C%20%20%2F%2F%20%EC%A7%80%EC%97%AD%EB%B2%88%ED%98%B8%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F01%5B0-9%5D%5B%5Cs%5D%2A%5Cd%7B4%7D%5B%5Cs%5D%2A%5Cd%7B4%7D%2F%2C%20type%3A%20%27PHONE%27%20%7D%2C%20%20%2F%2F%20%EA%B3%B5%EB%B0%B1%20%EA%B5%AC%EB%B6%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F050%5Cd%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B3%2C4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%2F%2C%20type%3A%20%27PHONE%27%20%7D%2C%20%20%2F%2F%20%EC%9D%B8%ED%84%B0%EB%84%B7%EC%A0%84%ED%99%94%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F070%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%5B%5Cs%5C-%5C.%2F%5D%2A%5Cd%7B4%7D%2F%2C%20type%3A%20%27PHONE%27%20%7D%2C%20%20%2F%2F%20%EC%9D%B8%ED%84%B0%EB%84%B7%EC%A0%84%ED%99%94%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%A3%BC%EC%86%8C%20%ED%8C%A8%ED%84%B4%20%28%EB%8D%94%20%EB%A7%8E%EC%9D%80%20%ED%98%95%EC%8B%9D%20%EC%B6%94%EA%B0%80%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%28%3F%3A%5B%EA%B0%80-%ED%9E%A3%5D%2B%28%3F%3A%EC%8B%9C%7C%EB%8F%84%29%5Cs%2A%29%3F%5B%EA%B0%80-%ED%9E%A3%5D%2B%28%3F%3A%EC%8B%9C%7C%EA%B5%B0%7C%EA%B5%AC%29%5Cs%2A%5B%EA%B0%80-%ED%9E%A3%5D%2B%28%3F%3A%EC%9D%8D%7C%EB%A9%B4%7C%EB%8F%99%7C%EA%B0%80%7C%EB%A1%9C%7C%EA%B8%B8%29%5Cs%2A%28%3F%3A%5Cd%2B%28%3F%3A-%5Cd%2B%29%3F%29%3F%2F%2C%20type%3A%20%27ADDRESS%27%20%7D%2C%20%20%2F%2F%20%EB%B3%B5%ED%95%A9%20%EC%A3%BC%EC%86%8C%20%ED%98%95%EC%8B%9D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5B%EA%B0%80-%ED%9E%A3%5D%2B%28%3F%3A%EC%95%84%ED%8C%8C%ED%8A%B8%7C%EB%B9%8C%EB%9D%BC%7C%EC%98%A4%ED%94%BC%EC%8A%A4%ED%85%94%29%5Cs%2A%5Cd%2B%EB%8F%99%5Cs%2A%5Cd%2B%ED%98%B8%2F%2C%20type%3A%20%27ADDRESS%27%20%7D%2C%20%20%2F%2F%20%EC%95%84%ED%8C%8C%ED%8A%B8%20%EC%A3%BC%EC%86%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5B%EA%B0%80-%ED%9E%A3%5D%2B%28%3F%3A%EB%A1%9C%7C%EA%B8%B8%29%5Cs%2A%5Cd%2B%28%3F%3A-%5Cd%2B%29%3F%2F%2C%20type%3A%20%27ADDRESS%27%20%7D%2C%20%20%2F%2F%20%EB%8F%84%EB%A1%9C%EB%AA%85%20%EC%A3%BC%EC%86%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%27%5B%EA%B0%80-%ED%9E%A3%5D%2B%28%3F%3A%EC%95%84%ED%8C%8C%ED%8A%B8%7C%EB%B9%8C%EB%9D%BC%7C%EC%98%A4%ED%94%BC%EC%8A%A4%ED%85%94%7C%ED%83%80%EC%9B%8C%7C%EB%A7%A8%EC%85%98%29%5Cs%2A%5Cd%2B%EB%8F%99%5Cs%2A%5Cd%2B%ED%98%B8%27%2C%20type%3A%20%27ADDRESS%27%20%7D%2C%20%20%2F%2F%20%EC%B6%94%EA%B0%80%20%EC%A3%BC%EC%86%8C%20%ED%8C%A8%ED%84%B4%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%28%5B%EA%B0%80-%ED%9E%A3%5D%2B%28%EC%8B%9C%7C%EB%8F%84%7C%EA%B5%B0%7C%EA%B5%AC%7C%EC%9D%8D%7C%EB%A9%B4%7C%EB%8F%99%29%29%5Cs%28%5B%EA%B0%80-%ED%9E%A3%5D%2B%28%EA%B5%AC%7C%EB%A1%9C%7C%EA%B8%B8%7C%EB%8F%99%29%29%5Cs%3F%28%5Cd%2B%28%3F%3A-%5Cd%2B%29%3F%29%3F%2F%2C%20type%3A%20%27ADDRESS%27%20%7D%2C%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%9A%B4%EC%A0%84%EB%A9%B4%ED%97%88%EB%B2%88%ED%98%B8%20%ED%8C%A8%ED%84%B4%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cd%7B2%7D%5B-%5Cs%5D%3F%5Cd%7B2%7D%5B-%5Cs%5D%3F%5Cd%7B6%7D%5B-%5Cs%5D%3F%5Cd%7B2%7D%2F%2C%20type%3A%20%27DRIVER_LICENSE%27%20%7D%2C%20%20%2F%2F%20%EC%9A%B4%EC%A0%84%EB%A9%B4%ED%97%88%EB%B2%88%ED%98%B8%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cd%7B12%7D%2F%2C%20type%3A%20%27DRIVER_LICENSE%27%20%7D%2C%20%20%2F%2F%2012%EC%9E%90%EB%A6%AC%20%EC%9A%B4%EC%A0%84%EB%A9%B4%ED%97%88%EB%B2%88%ED%98%B8%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%EC%84%9C%EC%9A%B8%7C%EB%B6%80%EC%82%B0%7C%EB%8C%80%EA%B5%AC%7C%EC%9D%B8%EC%B2%9C%7C%EA%B4%91%EC%A3%BC%7C%EB%8C%80%EC%A0%84%7C%EC%9A%B8%EC%82%B0%7C%EC%84%B8%EC%A2%85%7C%EA%B2%BD%EA%B8%B0%7C%EA%B0%95%EC%9B%90%7C%EC%B6%A9%EB%B6%81%7C%EC%B6%A9%EB%82%A8%7C%EC%A0%84%EB%B6%81%7C%EC%A0%84%EB%82%A8%7C%EA%B2%BD%EB%B6%81%7C%EA%B2%BD%EB%82%A8%7C%EC%A0%9C%EC%A3%BC%7C%EA%B0%95%EC%9B%90%5B-%5Cs%5D%3F%5Cd%7B8%7D%2F%2C%20type%3A%20%27DRIVER_LICENSE%27%20%7D%2C%20%20%2F%2F%20%EC%A7%80%EC%97%AD%EB%AA%85%20%2B%208%EC%9E%90%EB%A6%AC%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%99%B8%EA%B5%AD%EC%9D%B8%EB%93%B1%EB%A1%9D%EB%B2%88%ED%98%B8%20%ED%8C%A8%ED%84%B4%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cd%7B6%7D%5B%5Cs%5C-%5C.%2F%5D%2B%5B5-8%5D%5Cd%7B6%7D%2F%2C%20type%3A%20%27FOREIGN_REGISTRATION%27%20%7D%2C%20%20%2F%2F%20%EC%99%B8%EA%B5%AD%EC%9D%B8%20%EB%93%B1%EB%A1%9D%EB%B2%88%ED%98%B8%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5BA-Z%5D%5Cd%7B5%7D%5B0-9A-Z%5D%7B2%7D%2F%2C%20type%3A%20%27FOREIGN_REGISTRATION%27%20%7D%2C%20%20%2F%2F%20%EC%99%B8%EA%B5%AD%EC%9D%B8%20%EB%93%B1%EB%A1%9D%EB%B2%88%ED%98%B8%0A%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EA%B1%B4%EA%B0%95%EB%B3%B4%ED%97%98%EB%B2%88%ED%98%B8%20%ED%8C%A8%ED%84%B4%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cd%7B2%7D%5B-%5Cs%5D%3F%5Cd%7B8%7D%5B-%5Cs%5D%3F%5Cd%7B2%7D%2F%2C%20type%3A%20%27HEALTH_INSURANCE%27%20%7D%2C%20%20%2F%2F%20%EA%B1%B4%EA%B0%95%EB%B3%B4%ED%97%98%EB%B2%88%ED%98%B8%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%20pattern%3A%20%2F%5Cd%7B2%7D%5B-%5Cs%5D%3F%5Cd%7B10%7D%2F%2C%20type%3A%20%27HEALTH_INSURANCE%27%20%7D%20%20%2F%2F%20%EA%B1%B4%EA%B0%95%EB%B3%B4%ED%97%98%EB%B2%88%ED%98%B8%0A%20%20%20%20%20%20%20%20%5D%3B%0A%20%20%20%20%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20const%20matches%20%3D%20%5B%5D%3B%0A%20%20%20%20%20%20for%20%28let%20item%20of%20patterns%29%20%7B%0A%20%20%20%20%20%20%20%20const%20regex%20%3D%20new%20RegExp%28item.pattern%2C%20%27g%27%29%3B%0A%20%20%20%20%20%20%20%20let%20match%3B%0A%20%20%20%20%20%20%20%20while%20%28%28match%20%3D%20regex.exec%28text%29%29%20%21%3D%3D%20null%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%9D%B4%EB%A6%84%20%ED%8C%A8%ED%84%B4%EC%9D%98%20%EA%B2%BD%EC%9A%B0%20%EA%B7%B8%EB%A3%B9%201%EB%A7%8C%20%EC%B6%94%EC%B6%9C%20%28%EC%9D%B4%EB%A6%84%20%EB%B6%80%EB%B6%84%EB%A7%8C%29%0A%20%20%20%20%20%20%20%20%20%20const%20value%20%3D%20item.type%20%3D%3D%3D%20%27NAME%27%20%26%26%20match%5B1%5D%20%3F%20match%5B1%5D%20%3A%20match%5B0%5D%3B%0A%20%20%20%20%20%20%20%20%20%20matches.push%28%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20type%3A%20item.type%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20value%3A%20value%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20index%3A%20match.index%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20length%3A%20value.length%0A%20%20%20%20%20%20%20%20%20%20%7D%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EC%A4%91%EB%B3%B5%20%EC%A0%9C%EA%B1%B0%20%28%EA%B0%99%EC%9D%80%20%EA%B0%92%EA%B3%BC%20%ED%83%80%EC%9E%85%EC%9D%B4%20%EC%97%AC%EB%9F%AC%EB%B2%88%20%EA%B0%90%EC%A7%80%EB%90%9C%20%EA%B2%BD%EC%9A%B0%29%0A%20%20%20%20%20%20const%20uniqueMatches%20%3D%20%5B%5D%3B%0A%20%20%20%20%20%20const%20seen%20%3D%20new%20Set%28%29%3B%0A%20%20%20%20%20%20for%20%28const%20match%20of%20matches%29%20%7B%0A%20%20%20%20%20%20%20%20const%20key%20%3D%20%60%24%7Bmatch.type%7D%3A%24%7Bmatch.value%7D%60%3B%0A%20%20%20%20%20%20%20%20if%20%28%21seen.has%28key%29%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20seen.add%28key%29%3B%0A%20%20%20%20%20%20%20%20%20%20uniqueMatches.push%28match%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20return%20uniqueMatches%3B%0A%20%20%20%20%7D%0A%20%20%20%20%2F%2F%20%ED%85%8D%EC%8A%A4%ED%8A%B8%20%EC%9D%B5%EB%AA%85%ED%99%94%20%ED%95%A8%EC%88%98%0A%20%20%20%20function%20anonymizeText%28text%2C%20type%29%20%7B%0A%20%20%20%20%20%20%20%20switch%28type%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20case%20%27PHONE%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20text.replace%28%2F%5Cd%2Fg%2C%20%27%2A%27%29.replace%28%2F%5C%2A%7B3%2C4%7D-%5C%2A%7B3%2C4%7D-%5C%2A%7B4%7D%2F%2C%20%27%2A%2A%2A-%2A%2A%2A%2A-%2A%2A%2A%2A%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27EMAIL%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20%5Busername%2C%20domain%5D%20%3D%20text.split%28%27%40%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20username.substring%280%2C%202%29%20%2B%20%27%2A%2A%2A%2A%40%27%20%2B%20domain%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27SSN%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20text.substr%280%2C%208%29%20%2B%20%27%2A%2A%2A%2A%2A%2A%27%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27CREDIT_DEBIT_NUMBER%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20text.replace%28%2F%5Cd%7B4%7D-%5Cd%7B4%7D-%5Cd%7B4%7D-%5Cd%7B4%7D%2F%2C%20%27%2A%2A%2A%2A-%2A%2A%2A%2A-%2A%2A%2A%2A-%2A%2A%2A%2A%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27NAME%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20%28text.length%20%3C%3D%202%29%20return%20%27%2A%2A%27%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20text.charAt%280%29%20%2B%20%27%2A%27.repeat%28text.length%20-%201%29%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27ADDRESS%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20parts%20%3D%20text.split%28%27%20%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20parts.map%28%28part%2C%20index%29%20%3D%3E%20index%20%3D%3D%3D%200%20%3F%20part%20%3A%20%27%2A%2A%2A%27%29.join%28%27%20%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27PASSPORT_NUMBER%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20text.replace%28%2F%5BA-Za-z0-9%5D%2Fg%2C%20%27%2A%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27DRIVER_LICENSE%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20text.replace%28%2F%5Cd%2Fg%2C%20%27%2A%27%29.replace%28%2F.%7B4%2C%7D%2F%2C%20%27%2A%2A%2A%2A-%2A%2A%2A%2A-%2A%2A%2A%2A%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27FOREIGN_REGISTRATION%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20text.replace%28%2F%5Cd%2Fg%2C%20%27%2A%27%29.replace%28%2F.%7B5%2C%7D%2F%2C%20%27%2A%2A%2A%2A%2A%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20case%20%27HEALTH_INSURANCE%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20text.replace%28%2F%5Cd%2Fg%2C%20%27%2A%27%29.replace%28%2F.%7B10%2C%7D%2F%2C%20%27%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20default%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%27%2A%2A%2A%2A%2A%2A%27%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%0A%20%20%20%20%2F%2F%20%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%EC%B2%98%EB%A6%AC%EB%A5%BC%20%EC%9C%84%ED%95%9C%20%EC%84%B8%EB%A0%A8%EB%90%9C%20%EB%AA%A8%EB%8B%AC%20%0A%20%20%20%20function%20createSimpleModal%28matches%29%20%7B%0A%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%EC%9D%B4%20%EC%9D%B4%EB%AF%B8%20%EC%A1%B4%EC%9E%AC%ED%95%98%EB%A9%B4%20%EC%A0%9C%EA%B1%B0%0A%20%20%20%20%20%20let%20existingModal%20%3D%20document.querySelector%28%27.pii-simple-modal%27%29%3B%0A%20%20%20%20%20%20if%20%28existingModal%29%20existingModal.remove%28%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%20%EC%8A%A4%ED%83%80%EC%9D%BC%20%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0%0A%20%20%20%20%20%20const%20alertStyles%20%3D%20window.getComputedStyle%28%0A%20%20%20%20%20%20%20%20document.querySelector%28%27.text-base%27%29%20%7C%7C%20%0A%20%20%20%20%20%20%20%20document.querySelector%28%27.flex.items-center%27%29%20%7C%7C%20%0A%20%20%20%20%20%20%20%20document.body%0A%20%20%20%20%20%20%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%20%EB%B0%B0%EA%B2%BD%0A%20%20%20%20%20%20const%20modalBackground%20%3D%20document.createElement%28%27div%27%29%3B%0A%20%20%20%20%20%20modalBackground.className%20%3D%20%27pii-modal-background%27%3B%0A%20%20%20%20%20%20modalBackground.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20position%3A%20fixed%3B%0A%20%20%20%20%20%20%20%20top%3A%200%3B%0A%20%20%20%20%20%20%20%20left%3A%200%3B%0A%20%20%20%20%20%20%20%20width%3A%20100%25%3B%0A%20%20%20%20%20%20%20%20height%3A%20100%25%3B%0A%20%20%20%20%20%20%20%20z-index%3A%209999%3B%0A%20%20%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20%20%20justify-content%3A%20center%3B%0A%20%20%20%20%20%20%20%20align-items%3A%20flex-start%3B%0A%20%20%20%20%20%20%20%20padding-top%3A%20100px%3B%0A%20%20%20%20%20%20%20%20background-color%3A%20rgba%280%2C%200%2C%200%2C%200.0%29%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%20%EC%83%9D%EC%84%B1%0A%20%20%20%20%20%20const%20modal%20%3D%20document.createElement%28%27div%27%29%3B%0A%20%20%20%20%20%20modal.className%20%3D%20%27pii-simple-modal%27%3B%0A%20%20%20%20%20%20modal.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20background-color%3A%20rgb%28255%2C%20255%2C%20255%29%3B%0A%20%20%20%20%20%20%20%20color%3A%20%24%7BalertStyles.color%20%7C%7C%20%27black%27%7D%3B%0A%20%20%20%20%20%20%20%20padding%3A%2024px%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%2012px%3B%0A%20%20%20%20%20%20%20%20border%20%3A%202px%20solid%20%234285F4%3B%0A%20%20%20%20%20%20%20%20box-shadow%3A%200%204px%2020px%20rgba%280%2C0%2C0%2C0.15%29%3B%0A%20%20%20%20%20%20%20%20width%3A%20420px%3B%0A%20%20%20%20%20%20%20%20max-width%3A%2090%25%3B%0A%20%20%20%20%20%20%20%20font-family%3A%20%24%7BalertStyles.fontFamily%20%7C%7C%20%27Arial%2C%20sans-serif%27%7D%3B%0A%20%20%20%20%20%20%20%20animation%3A%20modal-slide-down%200.3s%20ease-out%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%20%EC%8A%A4%ED%83%80%EC%9D%BC%20%EC%B6%94%EA%B0%80%0A%20%20%20%20%20%20const%20styleElement%20%3D%20document.createElement%28%27style%27%29%3B%0A%20%20%20%20%20%20styleElement.textContent%20%3D%20%60%0A%20%20%20%20%20%20%20%20%40keyframes%20modal-slide-down%20%7B%0A%20%20%20%20%20%20%20%20%20%20from%20%7B%20transform%3A%20translateY%28-50px%29%3B%20opacity%3A%200%3B%20%7D%0A%20%20%20%20%20%20%20%20%20%20to%20%7B%20transform%3A%20translateY%280%29%3B%20opacity%3A%201%3B%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20.pii-simple-modal%20ul%20%7B%0A%20%20%20%20%20%20%20%20%20%20max-height%3A%20200px%3B%0A%20%20%20%20%20%20%20%20%20%20overflow-y%3A%20auto%3B%0A%20%20%20%20%20%20%20%20%20%20padding-left%3A%2020px%3B%0A%20%20%20%20%20%20%20%20%20%20margin%3A%2015px%200%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20.pii-simple-modal%20li%20%7B%0A%20%20%20%20%20%20%20%20%20%20margin-bottom%3A%208px%3B%0A%20%20%20%20%20%20%20%20%20%20line-height%3A%201.5%3B%0A%20%20%20%20%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20%20%20%20%20justify-content%3A%20space-between%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20.pii-simple-modal%20li%20span.type%20%7B%0A%20%20%20%20%20%20%20%20%20%20background-color%3A%20%23f0f0f0%3B%0A%20%20%20%20%20%20%20%20%20%20border-radius%3A%204px%3B%0A%20%20%20%20%20%20%20%20%20%20padding%3A%202px%206px%3B%0A%20%20%20%20%20%20%20%20%20%20font-size%3A%200.85em%3B%0A%20%20%20%20%20%20%20%20%20%20margin-right%3A%208px%3B%0A%20%20%20%20%20%20%20%20%20%20color%3A%20%23666%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20.pii-simple-modal%20button%3Ahover%20%7B%0A%20%20%20%20%20%20%20%20%20%20opacity%3A%200.9%3B%0A%20%20%20%20%20%20%20%20%20%20transform%3A%20translateY%28-1px%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20.pii-simple-modal%20button%3Aactive%20%7B%0A%20%20%20%20%20%20%20%20%20%20transform%3A%20translateY%281px%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20document.head.appendChild%28styleElement%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%20%ED%97%A4%EB%8D%94%0A%20%20%20%20%20%20const%20header%20%3D%20document.createElement%28%27div%27%29%3B%0A%20%20%20%20%20%20header.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20%20%20align-items%3A%20center%3B%0A%20%20%20%20%20%20%20%20margin-bottom%3A%2016px%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EA%B2%BD%EA%B3%A0%20%EC%95%84%EC%9D%B4%EC%BD%98%0A%20%20%20%20%20%20const%20warningIcon%20%3D%20document.createElement%28%27div%27%29%3B%0A%20%20%20%20%20%20warningIcon.innerHTML%20%3D%20%60%0A%20%20%20%20%20%20%20%20%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012C2%206.477%206.477%202%2012%202C17.523%202%2022%206.477%2022%2012C22%2017.523%2017.523%2022%2012%2022ZM12%2020C16.418%2020%2020%2016.418%2020%2012C20%207.582%2016.418%204%2012%204C7.582%204%204%207.582%204%2012C4%2016.418%207.582%2020%2012%2020ZM11%2015H13V17H11V15ZM11%207H13V13H11V7Z%22%20fill%3D%22%23FF9800%22%2F%3E%0A%20%20%20%20%20%20%20%20%3C%2Fsvg%3E%0A%20%20%20%20%20%20%60%3B%0A%0A%20%20%20%20%20%20const%20title%20%3D%20document.createElement%28%27h3%27%29%3B%0A%20%20%20%20%20%20title.textContent%20%3D%20%27%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%EA%B0%90%EC%A7%80%EB%90%A8%27%3B%0A%20%20%20%20%20%20title.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20margin%3A%200%200%200%2010px%3B%0A%20%20%20%20%20%20%20%20font-size%3A%2018px%3B%0A%20%20%20%20%20%20%20%20font-weight%3A%20600%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20header.appendChild%28warningIcon%29%3B%0A%20%20%20%20%20%20header.appendChild%28title%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EC%84%A4%EB%AA%85%20%ED%85%8D%EC%8A%A4%ED%8A%B8%0A%20%20%20%20%20%20const%20description%20%3D%20document.createElement%28%27p%27%29%3B%0A%20%20%20%20%20%20description.textContent%20%3D%20%27%EB%8B%A4%EC%9D%8C%20%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EA%B0%80%20%EC%9E%85%EB%A0%A5%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4.%20%EC%9D%B5%EB%AA%85%ED%99%94%ED%95%98%EC%8B%9C%EA%B2%A0%EC%8A%B5%EB%8B%88%EA%B9%8C%3F%27%3B%0A%20%20%20%20%20%20description.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20margin%3A%200%200%2015px%200%3B%0A%20%20%20%20%20%20%20%20font-size%3A%2014px%3B%0A%20%20%20%20%20%20%20%20color%3A%20%24%7BalertStyles.color%20%7C%7C%20%27%23666%27%7D%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EA%B0%90%EC%A7%80%EB%90%9C%20%ED%95%AD%EB%AA%A9%20%EB%A6%AC%EC%8A%A4%ED%8A%B8%0A%20%20%20%20%20%20const%20list%20%3D%20document.createElement%28%27ul%27%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20matches.forEach%28%28match%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20item%20%3D%20document.createElement%28%27li%27%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%ED%83%80%EC%9E%85%20%ED%91%9C%EC%8B%9C%0A%20%20%20%20%20%20%20%20const%20typeSpan%20%3D%20document.createElement%28%27span%27%29%3B%0A%20%20%20%20%20%20%20%20typeSpan.className%20%3D%20%27type%27%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20let%20typeText%20%3D%20%27%27%3B%0A%20%20%20%20%20%20%20%20let%20sensitivityLevel%20%3D%20%27%EB%82%AE%EC%9D%8C%27%3B%20%2F%2F%20%EA%B8%B0%EB%B3%B8%EA%B0%92%20%EC%84%A4%EC%A0%95%0A%0A%20%20%20%20%20%20%20%20switch%28match.type%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27PHONE%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EC%A4%91%EA%B0%84%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27EMAIL%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%9D%B4%EB%A9%94%EC%9D%BC%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EC%A4%91%EA%B0%84%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27SSN%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%A3%BC%EB%AF%BC%EB%B2%88%ED%98%B8%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EB%86%92%EC%9D%8C%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27CREDIT_DEBIT_NUMBER%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%B9%B4%EB%93%9C%EB%B2%88%ED%98%B8%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EB%86%92%EC%9D%8C%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27NAME%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%9D%B4%EB%A6%84%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EB%82%AE%EC%9D%8C%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27ADDRESS%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%A3%BC%EC%86%8C%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EC%A4%91%EA%B0%84%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27PASSPORT_NUMBER%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%97%AC%EA%B6%8C%EB%B2%88%ED%98%B8%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EB%86%92%EC%9D%8C%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27DRIVER_LICENSE%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%9A%B4%EC%A0%84%EB%A9%B4%ED%97%88%EB%B2%88%ED%98%B8%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EC%A4%91%EA%B0%84%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27FOREIGN_REGISTRATION%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EC%99%B8%EA%B5%AD%EC%9D%B8%EB%93%B1%EB%A1%9D%EB%B2%88%ED%98%B8%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EC%A4%91%EA%B0%84%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20case%20%27HEALTH_INSURANCE%27%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EA%B1%B4%EA%B0%95%EB%B3%B4%ED%97%98%EB%B2%88%ED%98%B8%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EC%A4%91%EA%B0%84%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20default%3A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20typeText%20%3D%20%22%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%22%3B%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sensitivityLevel%20%3D%20%22%EB%82%AE%EC%9D%8C%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%A4%80%EB%B9%84%0A%20%20%20%20%20%20%20%20let%20logData%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20email%3A%20email%2C%20%20%2F%2F%20%EC%9D%B4%EB%A9%94%EC%9D%BC%20%EA%B0%92%EC%9D%80%20%EB%AF%B8%EB%A6%AC%20%EC%9E%85%EB%A0%A5%EB%B0%9B%EC%95%98%EB%8B%A4%EA%B3%A0%20%EA%B0%80%EC%A0%95%0A%20%20%20%20%20%20%20%20%20%20%20%20content_type%3A%20typeText%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20sensitivity_level%3A%20sensitivityLevel%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20input_date%3A%20new%20Date%28%29.toISOString%28%29.split%28%27T%27%29%5B0%5D%20%20%2F%2F%20%EC%98%A4%EB%8A%98%20%EB%82%A0%EC%A7%9C%20%28YYYY-MM-DD%29%0A%20%20%20%20%20%20%20%20%7D%3B%0A%0A%20%20%20%20%2F%2F%20%EC%B5%9C%EC%A2%85%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%B6%9C%EB%A0%A5%20%28%EC%84%9C%EB%B2%84%EB%A1%9C%20%EB%B3%B4%EB%82%B4%EA%B8%B0%20%EC%A0%84%EC%97%90%20%ED%99%95%EC%9D%B8%EC%9A%A9%29%0A%20%20%20%20%20%20%20%20console.log%28logData%29%3B%0A%0A%20%20%20%20%20%20%20%20typeSpan.textContent%20%3D%20typeText%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%EA%B0%92%20%ED%91%9C%EC%8B%9C%0A%20%20%20%20%20%20%20%20const%20valueSpan%20%3D%20document.createElement%28%27span%27%29%3B%0A%20%20%20%20%20%20%20%20valueSpan.className%20%3D%20%27value%27%3B%0A%20%20%20%20%20%20%20%20valueSpan.textContent%20%3D%20match.value%3B%0A%20%20%20%20%20%20%20%20valueSpan.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20flex-grow%3A%201%3B%0A%20%20%20%20%20%20%20%20text-overflow%3A%20ellipsis%3B%0A%20%20%20%20%20%20%20%20overflow%3A%20hidden%3B%0A%20%20%20%20%20%20%20%20white-space%3A%20nowrap%3B%0A%20%20%20%20%20%20%20%20margin-left%3A%208px%3B%0A%20%20%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20item.appendChild%28typeSpan%29%3B%0A%20%20%20%20%20%20%20%20item.appendChild%28valueSpan%29%3B%0A%20%20%20%20%20%20%20%20list.appendChild%28item%29%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2Furl%EB%A1%9C%20%EC%A0%95%EB%B3%B4%20%EB%B3%B4%EB%82%B4%EA%B8%B0%0A%0A%20%20%20%20%20%20%20%20logData%20%3D%20sendDataToService%28logData%29%3B%0A%20%20%20%20%20%20%20%20%7D%29%3B%0A%0A%20%20%20%20%20%20%20%20function%20sendDataToService%28logData%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20params%20%3D%20new%20URLSearchParams%28logData%29.toString%28%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20console.log%28params%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%84%9C%EB%B9%84%EC%8A%A4%20URL%20%28%EC%84%9C%EB%B2%84%20URL%EC%97%90%20%EB%A7%9E%EA%B2%8C%20%EC%88%98%EC%A0%95%29%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20serviceUrl%20%3D%20%27http%3A%2F%2Flocalhost%3A5000%2Fapi%2Fdashboard%2Fgetdata%3F%27%20%2B%20params%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20console.log%28%22%F0%9F%94%97%20%EC%9A%94%EC%B2%AD%20URL%3A%22%2C%20serviceUrl%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%83%88%20%EC%B0%BD%EC%97%90%EC%84%9C%20%EC%84%9C%EB%B9%84%EC%8A%A4%20%ED%8E%98%EC%9D%B4%EC%A7%80%20%EC%97%B4%EA%B8%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20window.open%28serviceUrl%2C%20%27_blank%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20console.log%28%27%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%A0%84%EC%86%A1%20%EC%99%84%EB%A3%8C%21%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%5D%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20%EC%9D%BC%EC%A0%95%20%EA%B0%84%EA%B2%A9%EC%9C%BC%EB%A1%9C%20%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC%20%EC%88%98%EC%A7%91%ED%95%98%EA%B3%A0%20%EC%A0%84%EC%86%A1%ED%95%98%EB%8A%94%20%EA%B8%B0%EB%8A%A5%0A%20%20%20%20%20%20%20%20function%20startDataCollection%28%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20intervalMinutes%20%3D%205%3B%20%20%2F%2F%205%EB%B6%84%20%EA%B0%84%EA%B2%A9%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20maxRepetitions%20%3D%2012%3B%20%20%2F%2F%20%EC%B5%9C%EB%8C%80%2012%ED%9A%8C%20%281%EC%8B%9C%EA%B0%84%20%EB%8F%99%EC%95%88%20%EB%B0%98%EB%B3%B5%29%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20let%20currentRepetition%20%3D%201%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%9E%90%EB%8F%99%20%EC%88%98%EC%A7%91%20%EC%83%81%ED%83%9C%20%ED%91%9C%EC%8B%9C%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20statusDiv%20%3D%20document.createElement%28%27div%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20statusDiv.id%20%3D%20%27data-collection-status%27%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20statusDiv.style%20%3D%20%27position%3Afixed%3Btop%3A10px%3Bright%3A10px%3Bbackground%3Argba%280%2C0%2C0%2C0.7%29%3Bcolor%3Awhite%3Bpadding%3A10px%3Bz-index%3A9999%3Bborder-radius%3A5px%3Bfont-family%3AArial%3B%27%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20statusDiv.innerHTML%20%3D%20%60%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%ED%99%9C%EC%84%B1%ED%99%94%20%28%24%7BintervalMinutes%7D%EB%B6%84%20%EA%B0%84%EA%B2%A9%29%3Cbr%3E%EC%A7%84%ED%96%89%3A%201%2F%24%7BmaxRepetitions%7D%60%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20document.body.appendChild%28statusDiv%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%A3%BC%EA%B8%B0%EC%A0%81%EC%9C%BC%EB%A1%9C%20%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%88%98%EC%A7%91%20%EB%B0%8F%20%EC%A0%84%EC%86%A1%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20intervalId%20%3D%20setInterval%28%28%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20currentRepetition%2B%2B%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20%28currentRepetition%20%3C%3D%20maxRepetitions%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20createSimpleModal%28%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20statusDiv.innerHTML%20%3D%20%60%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%9E%90%EB%8F%99%20%EC%88%98%EC%A7%91%20%ED%99%9C%EC%84%B1%ED%99%94%20%28%24%7BintervalMinutes%7D%EB%B6%84%20%EA%B0%84%EA%B2%A9%29%3Cbr%3E%EC%A7%84%ED%96%89%3A%20%24%7BcurrentRepetition%7D%2F%24%7BmaxRepetitions%7D%60%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%A7%80%EC%A0%95%EB%90%9C%20%EB%B0%98%EB%B3%B5%20%ED%9A%9F%EC%88%98%20%EC%99%84%EB%A3%8C%20%ED%9B%84%20%EC%A4%91%EC%A7%80%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20clearInterval%28intervalId%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20statusDiv.innerHTML%20%3D%20%27%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%88%98%EC%A7%91%20%EC%99%84%EB%A3%8C%21%27%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20setTimeout%28%28%29%20%3D%3E%20statusDiv.remove%28%29%2C%203000%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%20intervalMinutes%20%2A%2060%20%2A%201000%29%3B%20%20%2F%2F%205%EB%B6%84%20%EA%B0%84%EA%B2%A9%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%A4%91%EC%A7%80%20%EB%B2%84%ED%8A%BC%20%EC%B6%94%EA%B0%80%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20stopButton%20%3D%20document.createElement%28%27button%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20stopButton.textContent%20%3D%20%27%EC%A4%91%EC%A7%80%27%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20stopButton.style%20%3D%20%27margin-top%3A5px%3Bpadding%3A3px%208px%3B%27%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20stopButton.onclick%20%3D%20function%28%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20clearInterval%28intervalId%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20statusDiv.innerHTML%20%3D%20%27%EB%8D%B0%EC%9D%B4%ED%84%B0%20%EC%88%98%EC%A7%91%EC%9D%B4%20%EC%A4%91%EC%A7%80%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4.%27%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20setTimeout%28%28%29%20%3D%3E%20statusDiv.remove%28%29%2C%203000%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20statusDiv.appendChild%28document.createElement%28%27br%27%29%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20statusDiv.appendChild%28stopButton%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20startDataCollection%28%29%3B%0A%20%20%20%20%20%20%2F%2F%20%EB%B2%84%ED%8A%BC%20%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%0A%20%20%20%20%20%20const%20buttonContainer%20%3D%20document.createElement%28%27div%27%29%3B%0A%20%20%20%20%20%20buttonContainer.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20%20%20justify-content%3A%20space-between%3B%0A%20%20%20%20%20%20%20%20margin-top%3A%2020px%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EC%9D%B5%EB%AA%85%ED%99%94%20%EB%B2%84%ED%8A%BC%0A%20%20%20%20%20%20const%20anonymizeAllBtn%20%3D%20document.createElement%28%27button%27%29%3B%0A%20%20%20%20%20%20anonymizeAllBtn.textContent%20%3D%20%27%EB%AA%A8%EB%91%90%20%EC%9D%B5%EB%AA%85%ED%99%94%27%3B%0A%20%20%20%20%20%20anonymizeAllBtn.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20padding%3A%2010px%2016px%3B%0A%20%20%20%20%20%20%20%20background-color%3A%20%234285F4%3B%0A%20%20%20%20%20%20%20%20color%3A%20white%3B%0A%20%20%20%20%20%20%20%20border%3A%20none%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%206px%3B%0A%20%20%20%20%20%20%20%20cursor%3A%20pointer%3B%0A%20%20%20%20%20%20%20%20font-weight%3A%20500%3B%0A%20%20%20%20%20%20%20%20font-size%3A%2014px%3B%0A%20%20%20%20%20%20%20%20transition%3A%20all%200.2s%3B%0A%20%20%20%20%20%20%20%20flex-grow%3A%201%3B%0A%20%20%20%20%20%20%20%20margin-right%3A%2010px%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20anonymizeAllBtn.onclick%20%3D%20%28%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20let%20text%20%3D%20inputBox.innerText%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%93%A0%20%EB%A7%A4%EC%B9%98%20%ED%95%AD%EB%AA%A9%20%EC%9D%B5%EB%AA%85%ED%99%94%0A%20%20%20%20%20%20%20%20matches.forEach%28match%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20const%20anonymized%20%3D%20anonymizeText%28match.value%2C%20match.type%29%3B%0A%20%20%20%20%20%20%20%20%20%20%2F%2F%20%EC%A0%95%ED%99%95%ED%95%9C%20%EB%8C%80%EC%B2%B4%EB%A5%BC%20%EC%9C%84%ED%95%B4%20%EC%A0%95%EA%B7%9C%EC%8B%9D%20%EC%82%AC%EC%9A%A9%0A%20%20%20%20%20%20%20%20%20%20const%20regex%20%3D%20new%20RegExp%28escapeRegExp%28match.value%29%2C%20%27g%27%29%3B%0A%20%20%20%20%20%20%20%20%20%20text%20%3D%20text.replace%28regex%2C%20anonymized%29%3B%0A%20%20%20%20%20%20%20%20%7D%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20%EB%B3%80%EA%B2%BD%EB%90%9C%20%ED%85%8D%EC%8A%A4%ED%8A%B8%20%EC%A0%81%EC%9A%A9%0A%20%20%20%20%20%20%20%20inputBox.innerText%20%3D%20text%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20modalBackground.remove%28%29%3B%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%2F%2F%20%ED%86%A0%EC%8A%A4%ED%8A%B8%20%EB%A9%94%EC%8B%9C%EC%A7%80%20%ED%91%9C%EC%8B%9C%0A%20%20%20%20%20%20%20%20showToast%28%27%E2%9C%85%20%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EA%B0%80%20%EC%9D%B5%EB%AA%85%ED%99%94%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4%27%29%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AC%B4%EC%8B%9C%20%EB%B2%84%ED%8A%BC%0A%20%20%20%20%20%20const%20ignoreBtn%20%3D%20document.createElement%28%27button%27%29%3B%0A%20%20%20%20%20%20ignoreBtn.textContent%20%3D%20%27%EB%AC%B4%EC%8B%9C%ED%95%98%EA%B8%B0%27%3B%0A%20%20%20%20%20%20ignoreBtn.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20padding%3A%2010px%2016px%3B%0A%20%20%20%20%20%20%20%20background-color%3A%20transparent%3B%0A%20%20%20%20%20%20%20%20color%3A%20%23666%3B%0A%20%20%20%20%20%20%20%20border%3A%201px%20solid%20%23ddd%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%206px%3B%0A%20%20%20%20%20%20%20%20cursor%3A%20pointer%3B%0A%20%20%20%20%20%20%20%20font-weight%3A%20500%3B%0A%20%20%20%20%20%20%20%20font-size%3A%2014px%3B%0A%20%20%20%20%20%20%20%20transition%3A%20all%200.2s%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20ignoreBtn.onclick%20%3D%20%28%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20modalBackground.remove%28%29%3B%0A%20%20%20%20%20%20%20%20showToast%28%27%E2%9A%A0%EF%B8%8F%20%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%EC%B2%98%EB%A6%AC%EB%A5%BC%20%EB%AC%B4%EC%8B%9C%ED%96%88%EC%8A%B5%EB%8B%88%EB%8B%A4%27%29%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%B2%84%ED%8A%BC%20%EC%B6%94%EA%B0%80%0A%20%20%20%20%20%20buttonContainer.appendChild%28anonymizeAllBtn%29%3B%0A%20%20%20%20%20%20buttonContainer.appendChild%28ignoreBtn%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%20%EC%A1%B0%EB%A6%BD%0A%20%20%20%20%20%20modal.appendChild%28header%29%3B%0A%20%20%20%20%20%20modal.appendChild%28description%29%3B%0A%20%20%20%20%20%20modal.appendChild%28list%29%3B%0A%20%20%20%20%20%20modal.appendChild%28buttonContainer%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%20%EB%B0%B0%EA%B2%BD%EC%97%90%20%EB%AA%A8%EB%8B%AC%20%EC%B6%94%EA%B0%80%0A%20%20%20%20%20%20modalBackground.appendChild%28modal%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%20%EB%B0%B0%EA%B2%BD%20%ED%81%B4%EB%A6%AD%20%EC%8B%9C%20%EB%8B%AB%EA%B8%B0%20%28%EB%AA%A8%EB%8B%AC%20%EC%9E%90%EC%B2%B4%EB%8A%94%20%ED%81%B4%EB%A6%AD%20%EC%9D%B4%EB%B2%A4%ED%8A%B8%20%EC%A0%84%ED%8C%8C%20%EC%A4%91%EC%A7%80%29%0A%20%20%20%20%20%20modalBackground.addEventListener%28%27click%27%2C%20%28e%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20if%20%28e.target%20%3D%3D%3D%20modalBackground%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20modalBackground.remove%28%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20modal.addEventListener%28%27click%27%2C%20%28e%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20e.stopPropagation%28%29%3B%0A%20%20%20%20%20%20%7D%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EB%AC%B8%EC%84%9C%EC%97%90%20%EB%AA%A8%EB%8B%AC%20%EC%B6%94%EA%B0%80%0A%20%20%20%20%20%20document.body.appendChild%28modalBackground%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20ESC%20%ED%82%A4%EB%A1%9C%20%EB%AA%A8%EB%8B%AC%20%EB%8B%AB%EA%B8%B0%0A%20%20%20%20%20%20function%20handleKeyDown%28e%29%20%7B%0A%20%20%20%20%20%20%20%20if%20%28e.key%20%3D%3D%3D%20%27Escape%27%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20modalBackground.remove%28%29%3B%0A%20%20%20%20%20%20%20%20%20%20document.removeEventListener%28%27keydown%27%2C%20handleKeyDown%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20document.addEventListener%28%27keydown%27%2C%20handleKeyDown%29%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20%2F%2F%20%EC%A0%95%EA%B7%9C%EC%8B%9D%20%ED%8A%B9%EC%88%98%EB%AC%B8%EC%9E%90%20%EC%9D%B4%EC%8A%A4%EC%BC%80%EC%9D%B4%ED%94%84%20%ED%95%A8%EC%88%98%0A%20%20%20%20function%20escapeRegExp%28string%29%20%7B%0A%20%20%20%20%20%20return%20string.replace%28%2F%5B.%2A%2B%3F%5E%24%7B%7D%28%29%7C%5B%5C%5D%5C%5C%5D%2Fg%2C%20%27%5C%5C%24%26%27%29%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20%2F%2F%20%ED%86%A0%EC%8A%A4%ED%8A%B8%20%EB%A9%94%EC%8B%9C%EC%A7%80%20%ED%91%9C%EC%8B%9C%20%ED%95%A8%EC%88%98%0A%20%20%20%20function%20showToast%28message%29%20%7B%0A%20%20%20%20%20%20%2F%2F%20%EA%B8%B0%EC%A1%B4%20%ED%86%A0%EC%8A%A4%ED%8A%B8%20%EC%A0%9C%EA%B1%B0%0A%20%20%20%20%20%20const%20existingToast%20%3D%20document.querySelector%28%27.pii-toast%27%29%3B%0A%20%20%20%20%20%20if%20%28existingToast%29%20existingToast.remove%28%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%ED%86%A0%EC%8A%A4%ED%8A%B8%20%EC%83%9D%EC%84%B1%0A%20%20%20%20%20%20const%20toast%20%3D%20document.createElement%28%27div%27%29%3B%0A%20%20%20%20%20%20toast.className%20%3D%20%27pii-toast%27%3B%0A%20%20%20%20%20%20toast.textContent%20%3D%20message%3B%0A%20%20%20%20%20%20toast.style.cssText%20%3D%20%60%0A%20%20%20%20%20%20%20%20position%3A%20fixed%3B%0A%20%20%20%20%20%20%20%20bottom%3A%2030px%3B%0A%20%20%20%20%20%20%20%20left%3A%2050%25%3B%0A%20%20%20%20%20%20%20%20transform%3A%20translateX%28-50%25%29%3B%0A%20%20%20%20%20%20%20%20background-color%3A%20rgba%280%2C0%2C0%2C0.8%29%3B%0A%20%20%20%20%20%20%20%20color%3A%20white%3B%0A%20%20%20%20%20%20%20%20padding%3A%2012px%2020px%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%208px%3B%0A%20%20%20%20%20%20%20%20font-size%3A%2014px%3B%0A%20%20%20%20%20%20%20%20font-weight%3A%20500%3B%0A%20%20%20%20%20%20%20%20z-index%3A%2010000%3B%0A%20%20%20%20%20%20%20%20animation%3A%20toast-fade-in%200.3s%2C%20toast-fade-out%200.3s%202.5s%3B%0A%20%20%20%20%20%20%20%20animation-fill-mode%3A%20forwards%3B%0A%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%20%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%20%EC%8A%A4%ED%83%80%EC%9D%BC%20%EC%B6%94%EA%B0%80%0A%20%20%20%20%20%20if%20%28%21document.querySelector%28%27style.toast-style%27%29%29%20%7B%0A%20%20%20%20%20%20%20%20const%20style%20%3D%20document.createElement%28%27style%27%29%3B%0A%20%20%20%20%20%20%20%20style.className%20%3D%20%27toast-style%27%3B%0A%20%20%20%20%20%20%20%20style.textContent%20%3D%20%60%0A%20%20%20%20%20%20%20%20%20%20%40keyframes%20toast-fade-in%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20from%20%7B%20opacity%3A%200%3B%20transform%3A%20translate%28-50%25%2C%2020px%29%3B%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20to%20%7B%20opacity%3A%201%3B%20transform%3A%20translate%28-50%25%2C%200%29%3B%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%40keyframes%20toast-fade-out%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20from%20%7B%20opacity%3A%201%3B%20transform%3A%20translate%28-50%25%2C%200%29%3B%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20to%20%7B%20opacity%3A%200%3B%20transform%3A%20translate%28-50%25%2C%2020px%29%3B%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%60%3B%0A%20%20%20%20%20%20%20%20document.head.appendChild%28style%29%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20document.body.appendChild%28toast%29%3B%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%2F%2F%203%EC%B4%88%20%ED%9B%84%20%ED%86%A0%EC%8A%A4%ED%8A%B8%20%EC%A0%9C%EA%B1%B0%0A%20%20%20%20%20%20setTimeout%28%28%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20toast.remove%28%29%3B%0A%20%20%20%20%20%20%7D%2C%203000%29%3B%0A%20%20%20%20%7D%0A%0A%2F%2F%20%EC%9E%85%EB%A0%A5%EC%B0%BD%EC%9D%98%20%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%A5%BC%20%EC%B2%98%EB%A6%AC%0Aasync%20function%20processText%28%29%20%7B%0A%20%20%20%20const%20text%20%3D%20inputBox.innerText%3B%0A%0A%20%20%20%20%2F%2F%20%ED%85%8D%EC%8A%A4%ED%8A%B8%EA%B0%80%20%EB%B9%84%EC%96%B4%EC%9E%88%EC%9C%BC%EB%A9%B4%20%EC%B2%98%EB%A6%AC%ED%95%98%EC%A7%80%20%EC%95%8A%EC%9D%8C%0A%20%20%20%20if%20%28%21text.trim%28%29%29%20return%3B%0A%0A%20%20%20%20%2F%2F%20%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C%201%EC%B0%A8%20%EA%B2%80%EC%82%AC%0A%20%20%20%20const%20localMatches%20%3D%20localCheckSensitiveInfo%28text%29%3B%0A%0A%20%20%20%20if%20%28localMatches.length%20%3E%200%29%20%7B%0A%20%20%20%20%20%20%20%20%2F%2F%20%EB%AA%A8%EB%8B%AC%EB%A1%9C%20%EC%95%8C%EB%A6%BC%0A%20%20%20%20%20%20%20%20createSimpleModal%28localMatches%29%3B%0A%20%20%20%20%7D%0A%0A%7D%0A%20%20%20%20%2F%2F%20%EB%94%94%EB%B0%94%EC%9A%B4%EC%8A%A4%20%ED%95%A8%EC%88%98%0A%20%20%20%20function%20debounce%28func%2C%20wait%29%20%7B%0A%20%20%20%20%20%20%20%20let%20timeout%3B%0A%20%20%20%20%20%20%20%20return%20function%28...args%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20clearTimeout%28timeout%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20timeout%20%3D%20setTimeout%28%28%29%20%3D%3E%20func.apply%28this%2C%20args%29%2C%20wait%29%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F%2F%20%EC%9D%B4%EB%B2%A4%ED%8A%B8%20%EB%A6%AC%EC%8A%A4%EB%84%88%20%EC%84%A4%EC%A0%95%0A%20%20%20%20const%20debouncedProcess%20%3D%20debounce%28processText%2C%20500%29%3B%0A%20%20%20%20inputBox.addEventListener%28%22input%22%2C%20debouncedProcess%29%3B%0A%0A%20%20%20%20%2F%2F%20%EC%B4%88%EA%B8%B0%ED%99%94%20%EC%99%84%EB%A3%8C%20%EB%A9%94%EC%8B%9C%EC%A7%80%0A%20%20%20%20alert%28%22%E2%9C%85%20%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%EA%B0%90%EC%A7%80%20%EB%B0%8F%20%EC%9D%B5%EB%AA%85%ED%99%94%20%EA%B8%B0%EB%8A%A5%EC%9D%B4%20%ED%99%9C%EC%84%B1%ED%99%94%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4%21%22%29%3B%0A%0A%20%20%20%20%2F%2F%20%EC%9D%B4%EB%A9%94%EC%9D%BC%20%EC%9E%85%EB%A0%A5%EC%9D%84%20%EB%B0%9B%EB%8A%94%20%EC%B0%BD%20%EB%9D%84%EC%9A%B0%EA%B8%B0%0A%20%20%20%20var%20email%20%3D%20prompt%28%22privaShield%EC%97%90%20%EA%B0%80%EC%9E%85%ED%95%9C%20%EC%9D%B4%EB%A9%94%EC%9D%BC%EC%9D%84%20%EC%9E%85%EB%A0%A5%ED%95%B4%EC%A3%BC%EC%84%B8%EC%9A%94%3A%22%29%3B%0A%0A%20%20%20%20%2F%2F%20%EC%9D%B4%EB%A9%94%EC%9D%BC%EC%9D%B4%20%EC%9E%85%EB%A0%A5%EB%90%98%EC%97%88%EB%8A%94%EC%A7%80%20%ED%99%95%EC%9D%B8%0A%20%20%20%20if%20%28email%29%20%7B%0A%20%20%20%20%20%20%20%20console.log%28%22%EC%9E%85%EB%A0%A5%EB%90%9C%20%EC%9D%B4%EB%A9%94%EC%9D%BC%3A%20%22%20%2B%20email%29%3B%0A%20%20%20%20%20%20%20%20%2F%2F%20%EC%97%AC%EA%B8%B0%EC%97%90%20%EC%9D%B4%EB%A9%94%EC%9D%BC%EC%9D%84%20%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94%20%EB%A1%9C%EC%A7%81%EC%9D%84%20%EC%B6%94%EA%B0%80%ED%95%A0%20%EC%88%98%20%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4.%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20console.log%28%22%EC%9D%B4%EB%A9%94%EC%9D%BC%EC%9D%B4%20%EC%9E%85%EB%A0%A5%EB%90%98%EC%A7%80%20%EC%95%8A%EC%95%98%EC%8A%B5%EB%8B%88%EB%8B%A4.%22%29%3B%0A%20%20%20%20%7D%0A%7D%29%28%29%3B + `; return ( <> @@ -135,15 +136,15 @@ const HeroPage = () => {
개인정보 보호 - AI 기반 + 패턴분석 기반 실시간 감지

- AI 기반 민감정보 보호 + 패턴분석 기반 민감정보 보호
안전한 데이터 관리

- PrivaShield의 강력한 AI 엔진으로 중요한 데이터를 안전하게 + PrivaShield의 강력한 패턴분석으로 중요한 데이터를 안전하게 보호하세요. 실시간 모니터링과 자동화된 보안 시스템으로 당신의 정보를 지켜드립니다.

@@ -154,7 +155,7 @@ const HeroPage = () => {

- 100% 안심 : PrivaShield는 분석한 개인 민감 정보를 절대 저장하지 않습니다. + 100% 안심 : PrivaShield는 분석한 개인 민감 정보를 절대 저장하지 않습니다. 대시보드 통계를 위한 카테고리 정보만을 수집합니다.

+ - {/* 비밀번호 찾기 버튼 추가 */} - @@ -78,20 +98,23 @@ const LoginPage = () => { 또는 - - {/* 분리된 비밀번호 찾기 모달 컴포넌트 사용 */} - setIsModalOpen(false)} + setIsModalOpen(false)} loginEmail={email} /> ); }; -export default LoginPage; \ No newline at end of file +export default LoginPage; diff --git a/src/components/MyPage.jsx b/src/components/MyPage.jsx index a290119..e8ad12d 100644 --- a/src/components/MyPage.jsx +++ b/src/components/MyPage.jsx @@ -3,8 +3,7 @@ import { useNavigate } from "react-router-dom"; import { useUser } from "../contexts/UserContext"; // 전역 상태 사용 import styles from "../styles/MyPage.module.css"; import NavBar from "./NavBar"; -import ForgotPasswordModal from "./ForgotPasswordModal"; - +import ForgotPasswordModal from "./ForgotPasswordModal"; const ProfileEditPage = () => { const navigate = useNavigate(); @@ -19,7 +18,6 @@ const ProfileEditPage = () => { const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(""); const fileInputRef = useRef(null); - const [email, setEmail] = useState(""); const [isModalOpen, setIsModalOpen] = useState(false); useEffect(() => { @@ -54,47 +52,33 @@ const ProfileEditPage = () => { setError(null); const formData = new FormData(); formData.append("email", profileData.email); - formData.append("profileImage", file); // 서버에서 기대하는 필드명 + formData.append("profileImage", file); - // 올바른 API URL - 언더스코어(_) 사용 const response = await fetch( - "http://localhost:5000/api/users/profile_image", + "http://localhost:5000/api/users/update-profile-image", { method: "POST", body: formData, } ); - // 응답 상태 확인 - console.log("서버 응답 상태:", response.status); - if (!response.ok) { - if ( - response.headers.get("content-type")?.includes("application/json") - ) { - const errorData = await response.json(); - throw new Error( - errorData.message || "프로필 이미지 업로드 중 오류가 발생했습니다." - ); - } else { - const text = await response.text(); - console.log("에러 응답 텍스트:", text); - throw new Error("프로필 이미지 업로드 중 오류가 발생했습니다."); - } + const errorData = await response.json(); + throw new Error( + errorData.message || "프로필 이미지 업로드 중 오류가 발생했습니다." + ); } const data = await response.json(); - console.log("받은 데이터:", data); if (data.success) { - // 서버 응답 형식에 맞게 업데이트 updateUser({ - profile_image: data.data.profileImage || data.data.profile_image, + profile_image: data.data.profileImage, }); setProfileData((prev) => ({ ...prev, - profileImage: data.data.profileImage || data.data.profile_image, + profileImage: data.data.profileImage, })); setSuccessMessage("프로필 이미지가 업데이트되었습니다."); @@ -109,7 +93,6 @@ const ProfileEditPage = () => { navigate("/dashboard"); }; - // 회원 탈퇴 처리 const handleDeleteAccount = async () => { if ( window.confirm( @@ -118,16 +101,13 @@ const ProfileEditPage = () => { ) { try { setError(null); - const response = await fetch( - "http://localhost:5000/api/users/delete", // URL 수정 (서버 라우트에 맞게) - { - method: "POST", // DELETE에서 POST로 변경 (서버 컨트롤러에 맞게) - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ email: profileData.email }), - } - ); + const response = await fetch("http://localhost:5000/api/users/delete", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email: profileData.email }), + }); if (!response.ok) { const errorData = await response.json(); @@ -140,9 +120,8 @@ const ProfileEditPage = () => { if (data.success) { alert("회원 탈퇴가 완료되었습니다."); - // 로컬 스토리지 정리 및 로그인 페이지로 이동 localStorage.removeItem("userEmail"); - window.location.href = "/"; // 전체 페이지 새로고침과 함께 홈으로 이동 + window.location.href = "/"; } else { throw new Error( data.message || "회원 탈퇴 처리 중 오류가 발생했습니다." @@ -155,7 +134,6 @@ const ProfileEditPage = () => { } }; - // 비밀번호 변경 함수 추가 const handlePasswordChange = async () => { if (!profileData.password) { setError("새 비밀번호를 입력해주세요."); @@ -187,7 +165,6 @@ const ProfileEditPage = () => { if (data.success) { setSuccessMessage("비밀번호가 성공적으로 변경되었습니다."); - // 비밀번호 필드 초기화 setProfileData((prev) => ({ ...prev, password: "" })); } } catch (error) { @@ -200,11 +177,10 @@ const ProfileEditPage = () => { e.preventDefault(); try { setError(null); - // 프로필 정보 업데이트 const response = await fetch( "http://localhost:5000/api/users/update-profile", { - method: "POST", // PATCH에서 POST로 변경 (서버 컨트롤러에 맞게) + method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: profileData.email, @@ -226,7 +202,6 @@ const ProfileEditPage = () => { updateUser({ user_name: profileData.name }); setSuccessMessage("프로필 정보가 업데이트되었습니다."); - // 비밀번호가 입력되었으면 비밀번호도 변경 if (profileData.password) { await handlePasswordChange(); } @@ -237,7 +212,6 @@ const ProfileEditPage = () => { } }; - // 오류 메시지 타임아웃 처리 useEffect(() => { if (error || successMessage) { const timer = setTimeout(() => { @@ -315,7 +289,7 @@ const ProfileEditPage = () => { onChange={handleInputChange} className={styles.formInput} required - disabled // 이메일은 변경 불가 + disabled />

이메일은 변경할 수 없습니다

@@ -334,7 +308,6 @@ const ProfileEditPage = () => {

-
- - {/* 분리된 비밀번호 찾기 모달 컴포넌트 사용 */} - setIsModalOpen(false)} + setIsModalOpen(false)} loginEmail={profileData.email} - skipVerification={true} // 인증 과정 건너뛰기 + skipVerification={true} /> - ); }; -export default ProfileEditPage; \ No newline at end of file +export default ProfileEditPage; diff --git a/src/components/NavBar.jsx b/src/components/NavBar.jsx index 454954d..7ba1688 100644 --- a/src/components/NavBar.jsx +++ b/src/components/NavBar.jsx @@ -1,30 +1,32 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import { useNavigate, useLocation } from "react-router-dom"; +import { useUser } from "../contexts/UserContext"; // UserContext 가져오기 import styles from "../styles/NavBar.module.css"; const NavBar = ({ scrolled }) => { const navigate = useNavigate(); const location = useLocation(); - const [user, setUser] = useState(null); + const { user, logout } = useUser(); // 전역 사용자 컨텍스트 사용 - // 컴포넌트 마운트 시 로그인 상태 확인 - useEffect(() => { - const storedUser = localStorage.getItem("user"); - if (storedUser) { - setUser(JSON.parse(storedUser)); + // 로고 클릭 시 페이지 최상단 이동 + const handleLogoClick = () => { + if (location.pathname !== "/") { + navigate("/#topOfPage"); + setTimeout(() => { + const element = document.getElementById("topOfPage"); + if (element) { + element.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }, 200); + } else { + const element = document.getElementById("topOfPage"); + if (element) { + element.scrollIntoView({ behavior: "smooth", block: "start" }); + } } - }, []); - - // 로그아웃 처리 - const handleLogout = () => { - localStorage.removeItem("token"); - localStorage.removeItem("user"); - setUser(null); - alert("로그아웃 되었습니다."); - window.location.href = "/"; // 전체 페이지 새로고침과 함께 홈으로 이동 }; - // 특정 섹션으로 이동하는 함수 + // 특정 섹션으로 이동 const handleNavigateToSection = (id) => { if (location.pathname !== "/") { navigate(`/#${id}`); @@ -42,15 +44,28 @@ const NavBar = ({ scrolled }) => { } }; + const handleLoginClick = () => navigate("/login"); + const handleMyPageClick = () => navigate("/mypage"); + const handleDashboardClick = () => navigate("/dashboard"); + + const handleLogout = () => { + logout(); + alert("로그아웃 되었습니다."); + window.location.href = "/"; + }; + return (