From f8fcc8c961d509cf8ca7479a1946db9fdc6ba327 Mon Sep 17 00:00:00 2001 From: 0xC0FFE2 Date: Thu, 23 Jan 2025 20:02:56 +0900 Subject: [PATCH] =?UTF-8?q?chore=20:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=8D=BC=EB=B8=94=EB=A6=AC=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/default_profile.png | Bin 0 -> 35247 bytes src/App.tsx | 4 + src/components/home/HomeLayout.tsx | 6 +- src/components/home/Sidebar.tsx | 67 ++++++----- .../mypage/PasswordChangeDialog.tsx | 74 ++++++++++++ src/components/mypage/PinChangeDialog.tsx | 58 ++++++++++ src/components/mypage/ProfileImageUpload.tsx | 43 +++++++ src/components/token/TokenHistoryItem.tsx | 108 ++++++++++++++++++ src/components/token/TokenHistoryList.tsx | 26 +++++ src/pages/HomePage.tsx | 12 +- src/pages/LoginPage.tsx | 2 +- src/pages/MyPage.tsx | 107 +++++++++++++++++ src/pages/TokenInfoPage.tsx | 68 +++++++++++ src/services/IpLocationService.ts | 14 +++ src/types/Token.ts | 8 ++ 15 files changed, 562 insertions(+), 35 deletions(-) create mode 100644 public/default_profile.png create mode 100644 src/components/mypage/PasswordChangeDialog.tsx create mode 100644 src/components/mypage/PinChangeDialog.tsx create mode 100644 src/components/mypage/ProfileImageUpload.tsx create mode 100644 src/components/token/TokenHistoryItem.tsx create mode 100644 src/components/token/TokenHistoryList.tsx create mode 100644 src/pages/MyPage.tsx create mode 100644 src/pages/TokenInfoPage.tsx create mode 100644 src/services/IpLocationService.ts create mode 100644 src/types/Token.ts diff --git a/public/default_profile.png b/public/default_profile.png new file mode 100644 index 0000000000000000000000000000000000000000..c15ae5ff987145b134015162e40ce9469cb91df2 GIT binary patch literal 35247 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 literal 0 HcmV?d00001 diff --git a/src/App.tsx b/src/App.tsx index 75b1419..669ff0b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,8 @@ import MobileRegisterPage from "./pages/mobile/MobileRegisterPage"; import ProtectedRoute from "./components/ProtectedRoute"; import MobileProtectedRoute from "./components/MobileProtectedRoute"; import HomePage from "./pages/HomePage"; +import TokenInfoPage from "./pages/TokenInfoPage"; +import MyPage from "./pages/MyPage"; const App = () => { return ( @@ -39,6 +41,8 @@ const App = () => { } /> } /> } /> + } /> + } /> diff --git a/src/components/home/HomeLayout.tsx b/src/components/home/HomeLayout.tsx index a0675ab..496df7d 100644 --- a/src/components/home/HomeLayout.tsx +++ b/src/components/home/HomeLayout.tsx @@ -3,11 +3,13 @@ import Sidebar from "./Sidebar"; interface LayoutProps { children: ReactNode; + username: string; + email: string; } -const Layout: React.FC = ({ children }) => ( +const Layout: React.FC = ({ children , username , email} ) => (
- +
{children}
); diff --git a/src/components/home/Sidebar.tsx b/src/components/home/Sidebar.tsx index b1d0fff..736c11f 100644 --- a/src/components/home/Sidebar.tsx +++ b/src/components/home/Sidebar.tsx @@ -1,41 +1,54 @@ import React from "react"; +import { useLocation } from "react-router-dom"; import SidebarLink from "./SidebarLink"; interface SidebarProps { name: string; email: string; + profileUrl?: string; } -const Sidebar: React.FC = ({ name, email }) => ( - + ); +}; export default Sidebar; diff --git a/src/components/mypage/PasswordChangeDialog.tsx b/src/components/mypage/PasswordChangeDialog.tsx new file mode 100644 index 0000000..82b3f25 --- /dev/null +++ b/src/components/mypage/PasswordChangeDialog.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { Lock } from 'lucide-react'; + +const PasswordChangeDialog: React.FC = () => { + const [currentPassword, setCurrentPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [isOpen, setIsOpen] = useState(false); + + const handlePasswordChange = () => { + // Validation and password change logic + console.log('Password change initiated'); + setIsOpen(false); // Close modal after submission + }; + + return ( +
+ {/* Trigger button */} + + + {/* Modal */} + {isOpen && ( +
+
+

비밀번호 변경

+
+ setCurrentPassword(e.target.value)} + className="w-full border rounded-lg p-2" + /> + setNewPassword(e.target.value)} + className="w-full border rounded-lg p-2" + /> + setConfirmPassword(e.target.value)} + className="w-full border rounded-lg p-2" + /> + + +
+
+
+ )} +
+ ); +}; + +export default PasswordChangeDialog; diff --git a/src/components/mypage/PinChangeDialog.tsx b/src/components/mypage/PinChangeDialog.tsx new file mode 100644 index 0000000..6fe6149 --- /dev/null +++ b/src/components/mypage/PinChangeDialog.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { Shield } from 'lucide-react'; + +const PinChangeDialog: React.FC = () => { + const [pin, setPin] = useState(''); + const [isOpen, setIsOpen] = useState(false); + + const handlePinChange = () => { + // PIN change logic + console.log('PIN change initiated'); + setIsOpen(false); // Close modal after submission + }; + + return ( +
+ {/* Trigger button */} + + + {/* Modal */} + {isOpen && ( +
+
+

PIN 변경

+
+ setPin(e.target.value)} + className="w-full border rounded-lg p-2" + /> + + +
+
+
+ )} +
+ ); +}; + +export default PinChangeDialog; diff --git a/src/components/mypage/ProfileImageUpload.tsx b/src/components/mypage/ProfileImageUpload.tsx new file mode 100644 index 0000000..f7611d5 --- /dev/null +++ b/src/components/mypage/ProfileImageUpload.tsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; +import { Upload } from 'lucide-react'; + +interface ProfileImageUploadProps { + profileImage: string; + userName: string; +} +const ProfileImageUpload: React.FC = ({ profileImage, userName }) => { + const [selectedFile, setSelectedFile] = useState(null); + + const handleFileChange = (event: React.ChangeEvent) => { + if (event.target.files) { + setSelectedFile(event.target.files[0]); + } + }; + + return ( +
+ {/* Avatar 부분을 간단한 img 태그로 대체 */} +
+ Profile +
+
+ + +
+
+ ); +}; + +export default ProfileImageUpload; diff --git a/src/components/token/TokenHistoryItem.tsx b/src/components/token/TokenHistoryItem.tsx new file mode 100644 index 0000000..e9513ea --- /dev/null +++ b/src/components/token/TokenHistoryItem.tsx @@ -0,0 +1,108 @@ +import React, { useState } from 'react'; +import { Shield, Trash2 } from 'lucide-react'; + +interface TokenHistoryItemProps { + token: { + id: string; + service: string; + date: string; + device: string; + ip: string; + location: string; + }; + onDelete: (id: string) => void; + onBlockIP: (ip: string) => void; +} + +const TokenHistoryItem: React.FC = ({ token, onDelete, onBlockIP }) => { + const [showModal, setShowModal] = useState(false); + const [actionType, setActionType] = useState<'delete' | 'block'>('delete'); + + const handleAction = () => { + if (actionType === 'delete') { + onDelete(token.id); + } else if (actionType === 'block') { + onBlockIP(token.ip); + } + setShowModal(false); + }; + + return ( +
+
+
+
+ {token.service} + + {token.device} + +
+
+ +
+
+

날짜

+

{token.date}

+
+
+

IP 주소

+

{token.ip}

+
+
+

위치

+

{token.location}

+
+
+ + {/* 모바일 최적화된 액션 버튼 */} +
+ + +
+
+ + {/* 경고 모달 */} + {showModal && ( +
+
+

+ {actionType === 'delete' ? '정말로 이 토큰을 삭제하시겠습니까?' : 'IP를 차단하고 토큰을 삭제하시겠습니까?'} +

+

+ {actionType === 'delete' ? `서비스: ${token.service}, ${token.device} 에서 발급된 토큰을 더이상 사용할 수 없습니다!` : + `기존 토큰을 삭제하며 ${token.ip} / ${token.location} 에서 더이상 로그인 할 수 없습니다!`} +

+
+ + +
+
+
+ )} +
+ ); +}; + +export default TokenHistoryItem; \ No newline at end of file diff --git a/src/components/token/TokenHistoryList.tsx b/src/components/token/TokenHistoryList.tsx new file mode 100644 index 0000000..02fb5f9 --- /dev/null +++ b/src/components/token/TokenHistoryList.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import TokenHistoryItem from './TokenHistoryItem'; +import Token from '../../types/Token'; + +interface TokenHistoryListProps { + tokens: Token[]; + onDelete: (id: string) => void; + onBlockIP: (ip: string) => void; +} + +const TokenHistoryList: React.FC = ({ tokens, onDelete, onBlockIP }) => { + return ( +
+ {tokens.map((token) => ( + + ))} +
+ ); +}; + +export default TokenHistoryList; \ No newline at end of file diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 04ad2cb..d5e08a4 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -4,13 +4,14 @@ import LoginHistoryItem from "../components/home/LoginHistoryItem"; import Layout from "../components/home/HomeLayout"; const HomePage: React.FC = () => ( - +
-

반가워요

-

이동현 님

+

반갑습니다

+

Lee Donghyun 님의 나누아이디 사용을 환영합니다

+

추천 설정

( subtitle="이메일이 인증되지 않음" />
+

최근 활동

로그인 기록

diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 6352a5e..2258b4c 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -18,7 +18,7 @@ const LoginPage = () => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - const [captchaToken, setCaptchaToken] = useState(null); // 리캡챠 토큰 상태 추가 + const [captchaToken, setCaptchaToken] = useState(null); const [isPinModalOpen, setIsPinModalOpen] = useState(false); const navigate = useNavigate(); diff --git a/src/pages/MyPage.tsx b/src/pages/MyPage.tsx new file mode 100644 index 0000000..44833d6 --- /dev/null +++ b/src/pages/MyPage.tsx @@ -0,0 +1,107 @@ +import React, { useState } from "react"; +import { Edit, Phone, Mail, User } from "lucide-react"; +import PasswordChangeDialog from "../components/mypage/PasswordChangeDialog"; +import PinChangeDialog from "../components/mypage/PinChangeDialog"; +import ProfileImageUpload from "../components/mypage/ProfileImageUpload"; +import Layout from "../components/home/HomeLayout"; + +const mockUserData = { + name: "Lee Donghyun", + email: "m*****n@gmail.com", + phone: "010-****-7890", + profileImage: "/default_profile.png", + connectedDevices: [ + { + loginDate: "2024.01.23 09:00", + deviceName: "SM-P580 Android" + }, + ], +}; + +const MyPage: React.FC = () => { + const [userName, setUserName] = useState(mockUserData.name); + const [isEditingName, setIsEditingName] = useState(false); + + return ( + +
+
+

마이 페이지

+

+ 개인 정보를 확인하고 수정할 수 있습니다 +

+
+ +
+
+
+ +
+ {isEditingName ? ( +
+ setUserName(e.target.value)} + className="border rounded-lg px-2 py-1" + /> + +
+ ) : ( +
+

{userName}

+ +
+ )} +

{mockUserData.email}

+
+
+
+ +
+

보안 설정

+
+ + +
+
+
+ +
+
+

로그인된 모바일 기기

+
+ + {mockUserData.connectedDevices[0]?.deviceName === "NONE" ? ( +
+

로그인된 기기가 없습니다.

+
+ ) : ( +
+
+

+ {mockUserData.connectedDevices[0]?.deviceName} +

+
+

+ {mockUserData.connectedDevices[0]?.loginDate} +

+
+ )} +
+
+
+ ); +}; + +export default MyPage; diff --git a/src/pages/TokenInfoPage.tsx b/src/pages/TokenInfoPage.tsx new file mode 100644 index 0000000..42a3e24 --- /dev/null +++ b/src/pages/TokenInfoPage.tsx @@ -0,0 +1,68 @@ +import { useState, useEffect } from "react"; +import TokenHistoryList from "../components/token/TokenHistoryList"; +import Layout from "../components/home/HomeLayout"; +import { getLocationByIP } from "../services/IpLocationService"; +const mockTokens = [ + { + id: '1', + date: '2024.1.23 AM 9:00 KST', + service: 'DASHBOARD(NANUID)', + device: 'Android Web', + ip: '103.21.244.31', + location: '' // 초기값은 빈 문자열 + }, + { + id: '2', + date: '2024.1.21 AM 2:00 KST', + service: 'VocaVault Service', + device: 'Windows Web', + ip: '103.21.244.91', + location: '' // 초기값은 빈 문자열 + } +]; + +const TokenInfoPage: React.FC = () => { + const [tokens, setTokens] = useState(mockTokens); + + const fetchLocationForTokens = async () => { + const updatedTokens = await Promise.all( + tokens.map(async (token) => { + const location = await getLocationByIP(token.ip); + return { ...token, location }; + }) + ); + setTokens(updatedTokens); // 위치 정보 업데이트 + }; + + useEffect(() => { + fetchLocationForTokens(); // 컴포넌트가 마운트될 때 위치 정보 가져오기 + }, []); // 최초 렌더링 시 한 번만 실행 + + const handleDeleteToken = (id: string) => { + setTokens(tokens.filter(token => token.id !== id)); + console.log(`Token ${id} deleted`); + }; + + const handleBlockIP = (ip: string) => { + console.log(`IP ${ip} blocked`); + }; + + return ( + +
+
+

토큰 내역

+

로그인된 토큰 기록을 관리합니다

+
+ + +
+
+ ); +}; + +export default TokenInfoPage; diff --git a/src/services/IpLocationService.ts b/src/services/IpLocationService.ts new file mode 100644 index 0000000..1459a3a --- /dev/null +++ b/src/services/IpLocationService.ts @@ -0,0 +1,14 @@ +export const getLocationByIP = async (ip: string): Promise => { + try { + const response = await fetch(`http://ip-api.com/json/${ip}`); + const data = await response.json(); + if (data.status === "success") { + return `${data.city}, ${data.regionName}, ${data.country}`; + } else { + return "Unknown"; + } + } catch (error) { + console.error("Error fetching location:", error); + return "Unknown"; + } +}; diff --git a/src/types/Token.ts b/src/types/Token.ts new file mode 100644 index 0000000..b9ceee9 --- /dev/null +++ b/src/types/Token.ts @@ -0,0 +1,8 @@ +export default interface Token { + id: string; + service: string; + date: string; + device: string; + ip: string; + location: string; +}