From cd1477855f2492c7b1df9cb46d16d8dac34efb07 Mon Sep 17 00:00:00 2001 From: "MOCH. PASHA ARDYAN PUTRA" Date: Mon, 7 Jul 2025 04:40:49 +0000 Subject: [PATCH] ok --- src/App.js | 5 + src/Dashboard.js | 3 - src/FileListComponent.js | 4 +- src/ProfileTab.js | 218 ++++++++++++++++ src/ProfileTab.module.css | 528 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 753 insertions(+), 5 deletions(-) create mode 100644 src/ProfileTab.js create mode 100644 src/ProfileTab.module.css diff --git a/src/App.js b/src/App.js index 2da4865..b04db33 100644 --- a/src/App.js +++ b/src/App.js @@ -5,6 +5,7 @@ import ShowImage from "./ShowImage"; import Dashboard from "./Dashboard"; import Login from "./Login"; import CameraKtp from "./KTPScanner"; +import Profile from "./ProfileTab"; // Komponen untuk melindungi route dengan token const ProtectedRoute = ({ element }) => { @@ -42,6 +43,10 @@ function App() { path="/dashboard" element={} />} /> + } />} + /> } /> } /> diff --git a/src/Dashboard.js b/src/Dashboard.js index 3e64b83..23fae80 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.js @@ -131,9 +131,6 @@ const Dashboard = () => {
- - {user.username || "Guest"} - - {files.length} file tersedia + {files.length} anggota
@@ -339,7 +339,7 @@ const FileListComponent = ({ /> )} -

🪪 Detail Data KTP

+

🪪 Detail Data Anggota

diff --git a/src/ProfileTab.js b/src/ProfileTab.js new file mode 100644 index 0000000..70581c4 --- /dev/null +++ b/src/ProfileTab.js @@ -0,0 +1,218 @@ +import React, { useState, useRef, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import styles from "./ProfileTab.module.css"; + +const ProfileTab = () => { + const menuRef = useRef(null); + const navigate = useNavigate(); + const [isEditing, setIsEditing] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [profile, setProfile] = useState({}); + const [profileTemp, setProfileTemp] = useState({}); + + useEffect(() => { + const handleClickOutside = (event) => { + if (menuRef.current && !menuRef.current.contains(event.target)) { + setIsMenuOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const handleLogout = () => { + localStorage.removeItem("token"); + localStorage.removeItem("user"); + window.location.reload(); + }; + + useEffect(() => { + const dummyProfile = { + username: "admin", + }; + + setProfile(dummyProfile); + setProfileTemp(dummyProfile); + }, []); + + const handleChange = (e) => { + const { name, value } = e.target; + setProfile((prev) => ({ ...prev, [name]: value })); + }; + + const handleSave = async () => { + try { + if (!profile.oldPassword || !profile.newPassword) { + alert("Password lama dan baru tidak boleh kosong."); + return; + } + + const payload = { + username: profile.username, + oldPassword: profile.oldPassword, + newPassword: profile.newPassword, + }; + + const response = await fetch( + "https://bot.kediritechnopark.com/webhook/reset-password/psi", + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(payload), + } + ); + + if (!response.ok) throw new Error("Gagal menyimpan profil"); + + alert("Berhasil mengubah password"); + setIsEditing(false); + } catch (error) { + console.error("Error saat menyimpan profil:", error); + alert("Terjadi kesalahan saat menyimpan profil."); + } + }; + + const handleCancel = () => { + setIsEditing(false); + setProfile(profileTemp); + }; + + return ( +
+
+
+ Profile Avatar +

Kawal PSI Profile

+
+ +
+ + + {isMenuOpen && ( +
+ + +
+ )} +
+
+ +
+
+
+
+

Account

+ {!isEditing ? ( + + ) : ( +
+ + +
+ )} +
+ +
+ {!isEditing && ( +
+ + +
+ )} + + {isEditing && ( + <> +
+ + +
+
+ + +
+ + )} +
+
+
+
+ +
+ © 2025 Kediri Technopark • Dermalounge AI Admin +
+
+ ); +}; + +export default ProfileTab; diff --git a/src/ProfileTab.module.css b/src/ProfileTab.module.css new file mode 100644 index 0000000..bd27acf --- /dev/null +++ b/src/ProfileTab.module.css @@ -0,0 +1,528 @@ +/* ProfileTab.module.css - Modern Design */ + +/* Modern Color Palette */ +:root { + --primary-blue: #3b82f6; + --secondary-blue: #60a5fa; + --dark-blue: #1e40af; + --neutral-50: #fafafa; + --neutral-100: #f5f5f5; + --neutral-200: #e5e5e5; + --neutral-300: #d4d4d4; + --neutral-500: #737373; + --neutral-700: #404040; + --neutral-800: #262626; + --neutral-900: #171717; + --white: #ffffff; + --success-green: #10b981; + --warning-amber: #f59e0b; + --error-red: #ef4444; + --text-primary: #0f172a; + --text-secondary: #64748b; + --text-light: #ffffff; + --border-light: #e2e8f0; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); +} + +/* Base Styles */ +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; + line-height: 1.5; + color: var(--text-primary); + background-color: var(--neutral-50); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.dashboardContainer { + background-color: var(--neutral-50); + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* Header */ +.dashboardHeader { + background-color: var(--white); + color: var(--text-primary); + padding: 1rem 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: var(--shadow-sm); + border-bottom: 3px solid #ef4444; + position: sticky; + top: 0; + z-index: 50; + backdrop-filter: blur(8px); +} + +.logoAndTitle { + display: flex; + align-items: center; + flex-shrink: 0; +} + +.logoAndTitle img { + width: 2.5rem; + height: 2.5rem; + border-radius: 0.75rem; + margin-right: 0.75rem; + object-fit: cover; +} + +.dashboardHeader .h1 { + margin: 0; + font-size: 1.5rem; + font-weight: 700; + color: #ed4344; + letter-spacing: -0.025em; +} + +/* Dropdown Menu */ +.dropdownContainer { + position: relative; + display: flex; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; +} + +.dropdownToggle { + background-color: var(--neutral-100); + color: var(--text-primary); + border: 1px solid var(--border-light); + padding: 0.5rem; + border-radius: 0.5rem; + cursor: pointer; + font-size: 1rem; + transition: all 0.2s ease; + min-width: 2.5rem; + height: 2.5rem; + display: flex; + align-items: center; + justify-content: center; +} + +.dropdownToggle:hover { + background-color: var(--neutral-200); + border-color: var(--neutral-300); +} + +.dropdownMenu { + position: absolute; + top: calc(100% + 0.5rem); + right: 0; + background-color: var(--white); + border-radius: 0.75rem; + box-shadow: var(--shadow-lg); + border: 1px solid var(--border-light); + z-index: 10; + display: flex; + flex-direction: column; + min-width: 10rem; + overflow: hidden; + padding: 0.5rem; +} + +.dropdownItem { + background: none; + border: none; + padding: 0.75rem 1rem; + text-align: left; + cursor: pointer; + color: var(--text-primary); + transition: background-color 0.2s ease; + font-size: 0.875rem; + font-weight: 500; + border-radius: 0.5rem; + margin-bottom: 0.125rem; +} + +.dropdownItem:hover { + background-color: var(--neutral-100); +} + +.dropdownItem:last-child { + margin-bottom: 0; +} + +/* Main Content */ +.mainContent { + flex-grow: 1; + padding: 2rem 1.5rem; + display: flex; + flex-direction: column; + gap: 2rem; + max-width: 1200px; + margin: 0 auto; + width: 100%; +} + +/* Profile Section */ +.profileSection { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.profileCard { + background-color: var(--white); + padding: 2rem; + border-radius: 1rem; + border: 1px solid var(--border-light); + box-shadow: var(--shadow-sm); + transition: all 0.2s ease; +} + +.profileCard:hover { + box-shadow: var(--shadow-md); +} + +.profileHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--border-light); +} + +.profileHeader h2 { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + color: var(--text-primary); + letter-spacing: -0.025em; +} + +.editButton { + background-color: #ef4444; + color: var(--text-light); + border: none; + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + cursor: pointer; + font-size: 0.875rem; + font-weight: 600; + transition: all 0.2s ease; + letter-spacing: 0.025em; +} + +.editButton:hover { + background-color: var(--dark-blue); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.actionButtons { + display: flex; + gap: 0.75rem; +} + +.cancelButton { + background-color: var(--neutral-200); + color: var(--text-primary); + border: 1px solid var(--border-light); + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + cursor: pointer; + font-size: 0.875rem; + font-weight: 600; + transition: all 0.2s ease; + letter-spacing: 0.025em; +} + +.cancelButton:hover { + background-color: var(--neutral-300); + transform: translateY(-1px); +} + +.saveButton { + background-color: var(--success-green); + color: var(--text-light); + border: none; + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + cursor: pointer; + font-size: 0.875rem; + font-weight: 600; + transition: all 0.2s ease; + letter-spacing: 0.025em; +} + +.saveButton:hover { + background-color: #059669; + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +/* Profile Form */ +.profileForm { + display: grid; + grid-template-columns: 1fr; + gap: 1.5rem; +} + +.inputGroup { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.inputLabel { + font-size: 0.875rem; + font-weight: 500; + color: var(--text-primary); + letter-spacing: 0.025em; +} + +.input { + padding: 0.75rem 1rem; + border: 1px solid var(--border-light); + border-radius: 0.5rem; + font-size: 0.875rem; + transition: all 0.2s ease; + background-color: var(--white); + color: var(--text-primary); +} + +.input:focus { + border-color: var(--primary-blue); + box-shadow: 0 0 0 3px rgb(59 130 246 / 0.1); + outline: none; +} + +.readOnly { + background-color: var(--neutral-50); + border-color: var(--neutral-200); + pointer-events: none; + color: var(--text-secondary); +} + +/* License Section */ +.licenseSection { + margin-top: 1rem; +} + +.licenseSection h2 { + margin: 0 0 1.5rem 0; + font-size: 1.5rem; + font-weight: 600; + color: var(--text-primary); + letter-spacing: -0.025em; +} + +.licenseCards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; +} + +.licenseCard { + background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); + color: var(--text-light); + padding: 1.5rem; + border-radius: 1rem; + box-shadow: var(--shadow-md); + transition: all 0.2s ease; + position: relative; + overflow: hidden; +} + +.licenseCard::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.1) 0%, + rgba(255, 255, 255, 0.05) 100% + ); + pointer-events: none; +} + +.licenseCard:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.licenseType, +.licenseNumber, +.licenseValidity { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); +} + +.licenseType:last-child, +.licenseNumber:last-child, +.licenseValidity:last-child { + margin-bottom: 0; + border-bottom: none; + padding-bottom: 0; +} + +.licenseLabel { + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.1em; + opacity: 0.8; +} + +.licenseValue { + font-size: 0.875rem; + font-weight: 600; + text-align: right; +} + +.licenseStatus { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid rgba(255, 255, 255, 0.2); +} + +.statusBadge { + display: inline-block; + background-color: rgba(255, 255, 255, 0.2); + color: var(--text-light); + padding: 0.25rem 0.75rem; + border-radius: 1rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + backdrop-filter: blur(8px); +} + +/* Footer */ +.footer { + background-color: var(--white); + color: var(--text-secondary); + text-align: center; + padding: 1rem; + margin-top: auto; + font-size: 0.75rem; + border-top: 1px solid var(--border-light); +} + +/* Responsive Design */ +@media (min-width: 768px) { + .dashboardHeader { + padding: 1rem 2rem; + } + + .logoAndTitle img { + width: 3rem; + height: 3rem; + } + + .dashboardHeader .h1 { + font-size: 1.75rem; + } + + .mainContent { + padding: 2.5rem 2rem; + gap: 2.5rem; + } + + .profileCard { + padding: 2.5rem; + } + + .profileForm { + grid-template-columns: repeat(2, 1fr); + } + + .profileHeader { + flex-direction: row; + align-items: center; + } + + .actionButtons { + flex-direction: row; + } +} + +@media (min-width: 1024px) { + .dashboardHeader { + padding: 1.25rem 3rem; + } + + .logoAndTitle img { + width: 3.5rem; + height: 3.5rem; + } + + .dashboardHeader .h1 { + font-size: 2rem; + } + + .mainContent { + padding: 3rem 2.5rem; + gap: 3rem; + } + + .profileCard { + padding: 3rem; + } + + .licenseCards { + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + } +} + +@media (max-width: 767px) { + .dashboardHeader { + padding: 1rem; + } + + .logoAndTitle img { + width: 2rem; + height: 2rem; + } + + .dashboardHeader .h1 { + font-size: 1.25rem; + } + + .mainContent { + padding: 1.5rem 1rem; + gap: 1.5rem; + } + + .profileCard { + padding: 1.5rem; + } + + .profileHeader { + flex-direction: column; + align-items: stretch; + gap: 1rem; + } + + .actionButtons { + flex-direction: column; + gap: 0.5rem; + } + + .licenseCards { + grid-template-columns: 1fr; + } +}