688 lines
25 KiB
HTML
688 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Profil — GeoSosial Pontianak</title>
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
<meta http-equiv="Pragma" content="no-cache">
|
|
<meta http-equiv="Expires" content="0">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
:root {
|
|
--bg: #f0f4fb;
|
|
--blue: #1a5fff;
|
|
--blue2: #0f3cc7;
|
|
--teal: #0891b2;
|
|
--grn: #10b981;
|
|
--red: #f03e5a;
|
|
--purple:#7c3aed;
|
|
--ink: #0b1120;
|
|
--ink2: #3d4b66;
|
|
--ink3: #8a97ab;
|
|
--card: #ffffff;
|
|
--card-h:#f8faff;
|
|
--bdr: rgba(11,17,32,.09);
|
|
--sh: 0 4px 24px rgba(11,17,32,.08);
|
|
--shm: 0 8px 40px rgba(11,17,32,.12);
|
|
}
|
|
|
|
html { scroll-behavior: smooth; }
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background: var(--bg);
|
|
min-height: 100vh;
|
|
color: var(--ink);
|
|
position: relative;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* ── SUBTLE DOT BG ── */
|
|
body::before {
|
|
content: '';
|
|
position: fixed; inset: 0; z-index: 0;
|
|
background-image: radial-gradient(rgba(26,95,255,.06) 1px, transparent 1px);
|
|
background-size: 28px 28px;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* ── TOPBAR ── */
|
|
.topbar {
|
|
position: sticky; top: 0; z-index: 500;
|
|
height: 54px;
|
|
background: rgba(255,255,255,.92);
|
|
backdrop-filter: blur(20px) saturate(1.8);
|
|
border-bottom: 1px solid var(--bdr);
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0 28px;
|
|
box-shadow: 0 1px 12px rgba(11,17,32,.06);
|
|
}
|
|
.brand {
|
|
display: flex; align-items: center; gap: 9px;
|
|
text-decoration: none;
|
|
}
|
|
.brand-logo {
|
|
width: 28px; height: 28px; border-radius: 7px;
|
|
background: linear-gradient(135deg, var(--blue), var(--teal));
|
|
display: flex; align-items: center; justify-content: center; font-size: 14px;
|
|
}
|
|
.brand-name {
|
|
font-family: 'Space Grotesk', sans-serif;
|
|
font-size: 15px; font-weight: 700; color: var(--ink);
|
|
}
|
|
.topbar-right { display: flex; gap: 8px; }
|
|
.tbtn {
|
|
display: flex; align-items: center; gap: 5px;
|
|
padding: 6px 14px; border-radius: 99px;
|
|
font-size: 11px; font-weight: 600; font-family: inherit;
|
|
cursor: pointer; border: none; text-decoration: none;
|
|
transition: all .18s;
|
|
}
|
|
.tbtn.ghost {
|
|
background: var(--bg); border: 1px solid var(--bdr); color: var(--ink2);
|
|
}
|
|
.tbtn.ghost:hover { background: #e8efff; color: var(--blue); border-color: #c0d0ff; }
|
|
.tbtn.danger {
|
|
background: #fde8ec; border: 1px solid #f9b8c4; color: var(--red);
|
|
}
|
|
.tbtn.danger:hover { background: var(--red); color: #fff; border-color: var(--red); }
|
|
|
|
/* ── PAGE WRAP ── */
|
|
.page { position: relative; z-index: 1; padding: 40px 20px 60px; }
|
|
|
|
/* ── PROFILE HEADER ── */
|
|
.phead {
|
|
max-width: 700px; margin: 0 auto 28px;
|
|
display: flex; align-items: center; gap: 24px;
|
|
padding: 28px 32px;
|
|
background: var(--card);
|
|
border: 1px solid var(--bdr);
|
|
border-radius: 24px;
|
|
box-shadow: var(--shm);
|
|
position: relative; overflow: hidden;
|
|
}
|
|
/* Colored top strip */
|
|
.phead::before {
|
|
content: '';
|
|
position: absolute; top: 0; left: 0; right: 0; height: 4px;
|
|
background: linear-gradient(90deg, var(--blue), var(--teal), var(--purple));
|
|
}
|
|
/* Avatar */
|
|
.av-wrap { position: relative; flex-shrink: 0; }
|
|
.av {
|
|
width: 80px; height: 80px; border-radius: 50%;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-family: 'Space Grotesk', sans-serif;
|
|
font-size: 28px; font-weight: 700; color: #fff;
|
|
position: relative;
|
|
}
|
|
.av::before {
|
|
content: '';
|
|
position: absolute; inset: -3px; border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--blue), var(--teal));
|
|
z-index: -1;
|
|
}
|
|
.av::after {
|
|
content: '';
|
|
position: absolute; inset: -7px; border-radius: 50%;
|
|
background: linear-gradient(135deg, rgba(26,95,255,.18), rgba(8,145,178,.18));
|
|
z-index: -2;
|
|
animation: spin-ring 8s linear infinite;
|
|
}
|
|
@keyframes spin-ring {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
.av-pulse {
|
|
position: absolute; bottom: 2px; right: 2px;
|
|
width: 16px; height: 16px; border-radius: 50%;
|
|
background: var(--grn);
|
|
border: 2.5px solid #fff;
|
|
}
|
|
.av-pulse::after {
|
|
content: '';
|
|
position: absolute; inset: -3px; border-radius: 50%;
|
|
background: rgba(16,185,129,.3);
|
|
animation: pulse 2s infinite;
|
|
}
|
|
@keyframes pulse { 0%{transform:scale(1);opacity:1} 100%{transform:scale(2.2);opacity:0} }
|
|
|
|
.phead-meta { flex: 1; }
|
|
.phead-name {
|
|
font-family: 'Space Grotesk', sans-serif;
|
|
font-size: 22px; font-weight: 700; color: var(--ink);
|
|
letter-spacing: -.4px;
|
|
}
|
|
.phead-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
|
|
.chip {
|
|
display: inline-flex; align-items: center; gap: 4px;
|
|
padding: 3px 10px; border-radius: 99px;
|
|
font-size: 11px; font-weight: 600;
|
|
border: 1px solid transparent;
|
|
}
|
|
.chip.role-admin { background: #fde8ec; color: var(--red); border-color: #f9b8c4; }
|
|
.chip.role-petugas { background: #e8efff; color: var(--blue); border-color: #c0d0ff; }
|
|
.chip.role-wali { background: #f0ebff; color: var(--purple); border-color: #d4bfff; }
|
|
.chip.role-viewer { background: var(--bg); color: var(--ink3); border-color: var(--bdr); }
|
|
.chip.online { background: #e5f8f1; color: var(--grn); border-color: #a7f3d0; }
|
|
.phead-uname { font-size: 12px; color: var(--ink3); margin-top: 5px; }
|
|
|
|
/* ── MAIN LAYOUT ── */
|
|
.main {
|
|
max-width: 700px; margin: 0 auto;
|
|
display: flex; flex-direction: column; gap: 16px;
|
|
}
|
|
|
|
/* ── GLASS CARD → LIGHT CARD ── */
|
|
.gcard {
|
|
background: var(--card);
|
|
border: 1px solid var(--bdr);
|
|
border-radius: 20px;
|
|
overflow: hidden;
|
|
box-shadow: var(--sh);
|
|
transition: box-shadow .2s;
|
|
}
|
|
.gcard:hover { box-shadow: var(--shm); }
|
|
|
|
/* ── SECTION HEADER ── */
|
|
.sec-head {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 18px 22px;
|
|
border-bottom: 1px solid var(--bdr);
|
|
background: var(--card);
|
|
}
|
|
.sec-left { display: flex; align-items: center; gap: 12px; }
|
|
.sec-ic {
|
|
width: 36px; height: 36px; border-radius: 10px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 17px;
|
|
}
|
|
.sec-ic.c { background: #e8efff; }
|
|
.sec-ic.p { background: #fef3c7; }
|
|
.sec-ic.s { background: #f0ebff; }
|
|
.sec-title { font-size: 14px; font-weight: 700; color: var(--ink); }
|
|
.sec-sub { font-size: 11px; color: var(--ink3); margin-top: 1px; }
|
|
|
|
/* Edit button */
|
|
.ebtn {
|
|
display: flex; align-items: center; gap: 5px;
|
|
padding: 6px 14px; border-radius: 8px;
|
|
font-size: 11px; font-weight: 700; font-family: inherit;
|
|
background: var(--bg); color: var(--ink2);
|
|
border: 1px solid var(--bdr); cursor: pointer; transition: all .15s;
|
|
}
|
|
.ebtn:hover { background: #e8efff; color: var(--blue); border-color: #c0d0ff; }
|
|
.ebtn.on { background: #e8efff; color: var(--blue); border-color: #c0d0ff; }
|
|
|
|
/* ── INFO ROWS ── */
|
|
.irows { padding: 12px 16px; display: flex; flex-direction: column; gap: 12px; }
|
|
.irow {
|
|
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
|
|
}
|
|
.icell {
|
|
padding: 14px 18px;
|
|
background: var(--bg);
|
|
border: 1px solid var(--bdr);
|
|
border-radius: 12px;
|
|
}
|
|
.ilbl {
|
|
font-size: 9px; font-weight: 700; text-transform: uppercase;
|
|
letter-spacing: .1em; color: var(--ink3); margin-bottom: 7px;
|
|
}
|
|
.ival {
|
|
font-size: 13px; font-weight: 600; color: var(--ink);
|
|
display: flex; align-items: center; gap: 6px;
|
|
}
|
|
.ival.mono { font-family: 'Space Grotesk', monospace; }
|
|
.ival .tag {
|
|
display: inline-flex; align-items: center;
|
|
padding: 3px 9px; border-radius: 99px;
|
|
font-size: 11px; font-weight: 700;
|
|
}
|
|
|
|
/* inputs */
|
|
.gi {
|
|
width: 100%; padding: 9px 12px;
|
|
background: #fff;
|
|
border: 1.5px solid var(--bdr);
|
|
border-radius: 9px; font-size: 13px;
|
|
font-family: inherit; color: var(--ink); outline: none;
|
|
transition: border-color .18s;
|
|
}
|
|
.gi:focus { border-color: var(--blue); }
|
|
|
|
/* Save bar */
|
|
.save-bar {
|
|
display: none; justify-content: flex-end; gap: 10px;
|
|
padding: 14px 22px; border-top: 1px solid var(--bdr);
|
|
background: var(--bg);
|
|
}
|
|
.save-bar.show { display: flex; }
|
|
.sbtn-c {
|
|
padding: 9px 18px; border-radius: 9px; font-size: 12px;
|
|
font-weight: 600; font-family: inherit; cursor: pointer;
|
|
background: #fff; border: 1px solid var(--bdr); color: var(--ink2);
|
|
transition: all .15s;
|
|
}
|
|
.sbtn-c:hover { background: var(--bg); }
|
|
.sbtn-s {
|
|
padding: 9px 20px; border-radius: 9px; font-size: 12px;
|
|
font-weight: 700; font-family: inherit; cursor: pointer;
|
|
background: linear-gradient(135deg, var(--blue), var(--teal)); border: none; color: #fff;
|
|
transition: opacity .15s;
|
|
}
|
|
.sbtn-s:hover { opacity: .85; }
|
|
|
|
/* ── PASSWORD ── */
|
|
.pfields { padding: 20px 22px; display: flex; flex-direction: column; gap: 16px; }
|
|
.pf > label {
|
|
display: block; font-size: 9px; font-weight: 700;
|
|
text-transform: uppercase; letter-spacing: .1em;
|
|
color: var(--ink3); margin-bottom: 7px;
|
|
}
|
|
.pf > label span { font-size: 9px; font-weight: 400; text-transform: none; letter-spacing: 0; }
|
|
.pwrap { position: relative; }
|
|
.pwrap input {
|
|
width: 100%; padding: 11px 40px 11px 40px;
|
|
background: var(--bg);
|
|
border: 1.5px solid var(--bdr);
|
|
border-radius: 10px; font-size: 13px;
|
|
font-family: inherit; color: var(--ink); outline: none;
|
|
transition: border-color .18s, background .18s;
|
|
}
|
|
.pwrap input:focus { border-color: var(--blue); background: #fff; }
|
|
.pwrap input::placeholder { color: var(--ink3); }
|
|
.ppfx {
|
|
position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
|
|
font-size: 14px; pointer-events: none; user-select: none;
|
|
}
|
|
.ptgl {
|
|
position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
|
|
background: none; border: none; cursor: pointer; color: var(--ink3);
|
|
font-size: 14px; padding: 2px; line-height: 1; transition: color .15s;
|
|
}
|
|
.ptgl:hover { color: var(--blue); }
|
|
.sbar { height: 3px; border-radius: 99px; background: var(--bdr); margin-top: 7px; overflow: hidden; }
|
|
.sfill { height: 100%; width: 0; border-radius: 99px; transition: width .3s, background .3s; }
|
|
.shint { font-size: 10px; margin-top: 4px; color: var(--ink3); }
|
|
.pact { display: flex; justify-content: flex-end; padding-top: 4px; }
|
|
.pbtn {
|
|
display: flex; align-items: center; gap: 8px;
|
|
padding: 11px 28px; border-radius: 10px; border: none;
|
|
background: linear-gradient(135deg, var(--blue) 0%, var(--teal) 100%);
|
|
color: #fff; font-size: 12px; font-weight: 700;
|
|
font-family: inherit; cursor: pointer;
|
|
box-shadow: 0 4px 20px rgba(26,95,255,.22);
|
|
transition: opacity .18s, transform .15s;
|
|
}
|
|
.pbtn:hover { opacity: .88; transform: translateY(-1px); }
|
|
.pbtn:disabled { opacity: .45; cursor: not-allowed; transform: none; }
|
|
|
|
/* ── STATS ROW ── */
|
|
.stats {
|
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
|
|
}
|
|
.stat {
|
|
background: var(--card);
|
|
border: 1px solid var(--bdr);
|
|
border-radius: 16px; padding: 16px;
|
|
text-align: center;
|
|
box-shadow: var(--sh);
|
|
transition: box-shadow .2s, transform .2s;
|
|
}
|
|
.stat:hover { box-shadow: var(--shm); transform: translateY(-2px); }
|
|
.stat-ic { font-size: 22px; margin-bottom: 8px; }
|
|
.stat-n { font-size: 11px; color: var(--ink3); margin-bottom: 3px; }
|
|
.stat-v { font-size: 13px; font-weight: 700; color: var(--ink); }
|
|
|
|
/* ── TOAST ── */
|
|
#toast {
|
|
position: fixed; bottom: 28px; left: 50%;
|
|
transform: translateX(-50%) translateY(90px);
|
|
padding: 12px 22px; border-radius: 99px;
|
|
background: var(--ink);
|
|
color: #fff; font-size: 12px; font-weight: 700;
|
|
box-shadow: 0 8px 40px rgba(0,0,0,.2);
|
|
transition: transform .35s cubic-bezier(.34,1.56,.64,1);
|
|
z-index: 9999; white-space: nowrap;
|
|
}
|
|
#toast.show { transform: translateX(-50%) translateY(0); }
|
|
|
|
@media (max-width: 600px) {
|
|
.phead { flex-direction: column; text-align: center; align-items: center; }
|
|
.phead-row { justify-content: center; }
|
|
.irow { grid-template-columns: 1fr; }
|
|
.icell + .icell { border-left: none; border-top: 1px solid var(--bdr); }
|
|
.stats { grid-template-columns: 1fr; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- TOPBAR -->
|
|
<nav class="topbar">
|
|
<a class="brand" href="sig1.html">
|
|
<div class="brand-logo">📍</div>
|
|
<span class="brand-name">GeoSosial</span>
|
|
</a>
|
|
<div class="topbar-right">
|
|
<a class="tbtn ghost" href="sig1.html">← Peta</a>
|
|
<button class="tbtn danger" onclick="doLogout()">❤ Keluar</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="page">
|
|
|
|
<!-- PROFILE HEADER -->
|
|
<div class="phead">
|
|
<div class="av-wrap">
|
|
<div class="av" id="avEl" style="background:#0d2340">
|
|
<span id="avIni">AD</span>
|
|
</div>
|
|
<div class="av-pulse"></div>
|
|
</div>
|
|
<div class="phead-meta">
|
|
<div class="phead-name" id="phName">—</div>
|
|
<div class="phead-row">
|
|
<span class="chip" id="phRole">—</span>
|
|
<span class="chip online">● Online</span>
|
|
</div>
|
|
<div class="phead-uname" id="phUser">—</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- STATS ROW -->
|
|
<div class="stats main" style="padding: 0; margin-bottom: 0; display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; max-width: 700px;">
|
|
<div class="stat">
|
|
<div class="stat-ic">🔐</div>
|
|
<div class="stat-n">Hak Akses</div>
|
|
<div class="stat-v" id="stAccess">—</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-ic">🗓</div>
|
|
<div class="stat-n">Bergabung</div>
|
|
<div class="stat-v" id="stJoin">—</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-ic">⏱</div>
|
|
<div class="stat-n">Login Terakhir</div>
|
|
<div class="stat-v" id="stLogin">—</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MAIN CARDS -->
|
|
<div class="main">
|
|
|
|
<!-- Informasi Akun -->
|
|
<div class="gcard" id="secInfo">
|
|
<div class="sec-head">
|
|
<div class="sec-left">
|
|
<div class="sec-ic c">🪪</div>
|
|
<div>
|
|
<div class="sec-title">Informasi Akun</div>
|
|
<div class="sec-sub">Data profil dan pengaturan akun</div>
|
|
</div>
|
|
</div>
|
|
<button class="ebtn" id="editBtn" onclick="toggleEdit()">✏️ Edit</button>
|
|
</div>
|
|
<div class="irows">
|
|
<div class="irow">
|
|
<div class="icell">
|
|
<div class="ilbl">Username</div>
|
|
<div class="ival mono">@ <span id="dUser">—</span></div>
|
|
</div>
|
|
<div class="icell">
|
|
<div class="ilbl">Nama Lengkap / Jabatan</div>
|
|
<div class="ival" id="dJabW"><span id="dJab">—</span></div>
|
|
<input class="gi" id="eJab" style="display:none" placeholder="cth. Kadis Sosial">
|
|
</div>
|
|
</div>
|
|
<div class="irow">
|
|
<div class="icell">
|
|
<div class="ilbl">Role</div>
|
|
<div class="ival" id="dRoleW"><span class="tag" id="dRole">—</span></div>
|
|
<select class="gi" id="eRole" style="display:none">
|
|
<option value="admin">🛡 Admin</option>
|
|
<option value="petugas">👮 Petugas</option>
|
|
<option value="walikota">🏛️ Walikota</option>
|
|
<option value="viewer">👁️ Viewer</option>
|
|
</select>
|
|
</div>
|
|
<div class="icell">
|
|
<div class="ilbl">Status Akun</div>
|
|
<div class="ival"><span id="dStatus" style="color:var(--grn);font-weight:700">● Aktif</span></div>
|
|
</div>
|
|
</div>
|
|
<div class="irow">
|
|
<div class="icell">
|
|
<div class="ilbl">Sesi Aktif</div>
|
|
<div class="ival" style="color:var(--grn);font-weight:700">● Online</div>
|
|
</div>
|
|
<div class="icell">
|
|
<div class="ilbl">Login Terakhir</div>
|
|
<div class="ival" id="dLogin">—</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="save-bar" id="saveBar">
|
|
<button class="sbtn-c" onclick="cancelEdit()">Batal</button>
|
|
<button class="sbtn-s" onclick="saveInfo()">💾 Simpan</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ganti Password -->
|
|
<div class="gcard" id="secPw">
|
|
<div class="sec-head">
|
|
<div class="sec-left">
|
|
<div class="sec-ic p">🔑</div>
|
|
<div>
|
|
<div class="sec-title">Ganti Password</div>
|
|
<div class="sec-sub">Perbarui kata sandi akun Anda</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="pfields">
|
|
<div class="pf">
|
|
<label>Password Lama</label>
|
|
<div class="pwrap">
|
|
<span class="ppfx">🔒</span>
|
|
<input type="password" id="pwOld" placeholder="Masukkan password lama">
|
|
<button class="ptgl" onclick="togglePw('pwOld',this)" tabindex="-1">👁</button>
|
|
</div>
|
|
</div>
|
|
<div class="pf">
|
|
<label>Password Baru <span>(min. 6 karakter)</span></label>
|
|
<div class="pwrap">
|
|
<span class="ppfx">🔑</span>
|
|
<input type="password" id="pwNew" placeholder="Masukkan password baru" oninput="checkStr(this.value)">
|
|
<button class="ptgl" onclick="togglePw('pwNew',this)" tabindex="-1">👁</button>
|
|
</div>
|
|
<div class="sbar"><div class="sfill" id="sfill"></div></div>
|
|
<div class="shint" id="shint">—</div>
|
|
</div>
|
|
<div class="pf">
|
|
<label>Konfirmasi Password</label>
|
|
<div class="pwrap">
|
|
<span class="ppfx">✅</span>
|
|
<input type="password" id="pwCfm" placeholder="Ulangi password baru">
|
|
<button class="ptgl" onclick="togglePw('pwCfm',this)" tabindex="-1">👁</button>
|
|
</div>
|
|
</div>
|
|
<div class="pact">
|
|
<button class="pbtn" id="btnPw" onclick="savePw()">🔒 Simpan Password</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="toast"></div>
|
|
|
|
<script>
|
|
let PD = null, editing = false, ttimer;
|
|
|
|
const RM = {
|
|
admin: { cls:'role-admin', lbl:'🛡 Admin', bg:'rgba(248,113,113,.12)', fg:'#fca5a5' },
|
|
petugas: { cls:'role-petugas', lbl:'👮 Petugas', bg:'rgba(34,211,238,.1)', fg:'#22d3ee' },
|
|
walikota: { cls:'role-wali', lbl:'🏛️ Walikota', bg:'rgba(167,139,250,.12)', fg:'#c4b5fd' },
|
|
viewer: { cls:'role-viewer', lbl:'👁️ Viewer', bg:'rgba(148,163,184,.1)', fg:'#94a3b8' },
|
|
};
|
|
const AVC = ['#1a5fff','#7c3aed','#0891b2','#059669','#dc2626','#b45309'];
|
|
|
|
async function loadProfile() {
|
|
let base = null;
|
|
try {
|
|
const r = await fetch('api/auth.php?action=check');
|
|
const j = await r.json();
|
|
if (!j.success) { location.href = 'sig1.html'; return; }
|
|
base = j.data;
|
|
} catch { location.href = 'sig1.html'; return; }
|
|
|
|
let full = { ...base };
|
|
try {
|
|
const r2 = await fetch('api/users.php');
|
|
const j2 = await r2.json();
|
|
if (j2.success) {
|
|
const found = j2.data.find(x => x.id == base.id);
|
|
if (found) full = { ...base, ...found };
|
|
}
|
|
} catch {}
|
|
|
|
PD = full;
|
|
try { render(full); } catch(e) { console.error(e); }
|
|
}
|
|
|
|
function $(id) { return document.getElementById(id); }
|
|
|
|
function render(u) {
|
|
const rm = RM[u.role] || RM.viewer;
|
|
const ini = (u.username || 'U').substring(0, 2).toUpperCase();
|
|
const color = AVC[(u.id || 0) % AVC.length];
|
|
const now = new Date().toLocaleString('id-ID', {day:'2-digit',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});
|
|
const joined = u.created_at ? new Date(u.created_at).toLocaleDateString('id-ID',{day:'2-digit',month:'short',year:'numeric'}) : '—';
|
|
|
|
// Avatar
|
|
const av = $('avEl'); if (av) av.style.background = color;
|
|
const ai = $('avIni'); if (ai) ai.textContent = ini;
|
|
|
|
// Header
|
|
if ($('phName')) $('phName').textContent = u.jabatan || u.username;
|
|
const pr = $('phRole');
|
|
if (pr) { pr.textContent = rm.lbl; pr.className = 'chip ' + rm.cls; }
|
|
if ($('phUser')) $('phUser').textContent = '@ ' + u.username;
|
|
|
|
// Stats
|
|
if ($('stAccess')) $('stAccess').textContent = u.can_edit ? 'Baca & Tulis' : 'Baca Saja';
|
|
if ($('stJoin')) $('stJoin').textContent = joined;
|
|
if ($('stLogin')) $('stLogin').textContent = now;
|
|
|
|
// Info card
|
|
if ($('dUser')) $('dUser').textContent = u.username;
|
|
if ($('dJab')) $('dJab').textContent = u.jabatan || '—';
|
|
if ($('eJab')) $('eJab').value = u.jabatan || '';
|
|
if ($('eRole')) $('eRole').value = u.role;
|
|
const dr = $('dRole');
|
|
if (dr) { dr.textContent = rm.lbl; dr.style.background = rm.bg; dr.style.color = rm.fg; }
|
|
if ($('dStatus')) {
|
|
$('dStatus').textContent = u.aktif ? '● Aktif' : '○ Nonaktif';
|
|
$('dStatus').style.color = u.aktif ? 'var(--grn)' : 'var(--ink3)';
|
|
}
|
|
if ($('dLogin')) $('dLogin').textContent = now;
|
|
if ($('eRole')) $('eRole').disabled = !u.is_admin;
|
|
}
|
|
|
|
function toggleEdit() {
|
|
editing = !editing;
|
|
$('editBtn').textContent = editing ? '✕ Batal' : '✏️ Edit';
|
|
$('editBtn').className = 'ebtn' + (editing ? ' on' : '');
|
|
$('dJabW').style.display = editing ? 'none' : '';
|
|
$('eJab').style.display = editing ? '' : 'none';
|
|
$('dRoleW').style.display = editing ? 'none' : '';
|
|
$('eRole').style.display = editing ? '' : 'none';
|
|
$('saveBar').classList.toggle('show', editing);
|
|
if (editing) $('eJab').focus();
|
|
}
|
|
|
|
function cancelEdit() {
|
|
editing = false;
|
|
$('editBtn').textContent = '✏️ Edit'; $('editBtn').className = 'ebtn';
|
|
$('eJab').value = PD?.jabatan || '';
|
|
$('eRole').value = PD?.role || 'viewer';
|
|
$('dJabW').style.display = ''; $('eJab').style.display = 'none';
|
|
$('dRoleW').style.display = ''; $('eRole').style.display = 'none';
|
|
$('saveBar').classList.remove('show');
|
|
}
|
|
|
|
async function saveInfo() {
|
|
const jabatan = $('eJab').value.trim(), role = $('eRole').value;
|
|
try {
|
|
const r = await fetch(`api/users.php?id=${PD.id}`, {
|
|
method:'PUT', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({jabatan, role, can_edit: PD.can_edit, aktif: PD.aktif})
|
|
});
|
|
const j = await r.json();
|
|
if (j.success) { PD.jabatan=jabatan; PD.role=role; render(PD); cancelEdit(); toast('✓ Perubahan disimpan'); }
|
|
else alert('Gagal: ' + j.message);
|
|
} catch { alert('Koneksi server gagal.'); }
|
|
}
|
|
|
|
function togglePw(id, btn) {
|
|
const inp = $(id);
|
|
inp.type = inp.type === 'password' ? 'text' : 'password';
|
|
btn.textContent = inp.type === 'password' ? '👁' : '🙈';
|
|
}
|
|
|
|
function checkStr(v) {
|
|
const bar = $('sfill'), lbl = $('shint');
|
|
if (!v) { bar.style.width='0'; lbl.textContent='—'; lbl.style.color='var(--ink3)'; return; }
|
|
let s=0;
|
|
if(v.length>=6)s++; if(v.length>=10)s++;
|
|
if(/[A-Z]/.test(v))s++; if(/[0-9]/.test(v))s++; if(/[^A-Za-z0-9]/.test(v))s++;
|
|
const ls=[{w:'20%',c:'#f87171',t:'Sangat Lemah'},{w:'40%',c:'#fb923c',t:'Lemah'},{w:'60%',c:'#fbbf24',t:'Cukup'},{w:'80%',c:'#34d399',t:'Kuat'},{w:'100%',c:'#10b981',t:'Sangat Kuat'}];
|
|
const lv=ls[Math.min(s,4)];
|
|
bar.style.width=lv.w; bar.style.background=lv.c;
|
|
lbl.textContent=lv.t; lbl.style.color=lv.c;
|
|
}
|
|
|
|
async function savePw() {
|
|
const old=$('pwOld').value, nw=$('pwNew').value, cf=$('pwCfm').value;
|
|
if(!old){alert('Password lama wajib diisi.');return;}
|
|
if(nw.length<6){alert('Password baru minimal 6 karakter.');return;}
|
|
if(nw!==cf){alert('Konfirmasi password tidak cocok.');return;}
|
|
const btn=$('btnPw'); btn.disabled=true; btn.textContent='⏳ Menyimpan...';
|
|
try {
|
|
const r=await fetch(`api/users.php?id=${PD.id}`,{
|
|
method:'PUT',headers:{'Content-Type':'application/json'},
|
|
body:JSON.stringify({jabatan:PD.jabatan,role:PD.role,can_edit:PD.can_edit,aktif:PD.aktif,password:nw})
|
|
});
|
|
const j=await r.json();
|
|
if(j.success){
|
|
$('pwOld').value=$('pwNew').value=$('pwCfm').value='';
|
|
$('sfill').style.width='0'; $('shint').textContent='—';
|
|
toast('🔒 Password berhasil diperbarui');
|
|
} else alert('Gagal: '+j.message);
|
|
} catch { alert('Koneksi server gagal.'); }
|
|
btn.disabled=false; btn.textContent='🔒 Simpan Password';
|
|
}
|
|
|
|
async function doLogout() {
|
|
try { await fetch('api/auth.php?action=logout',{method:'POST'}); } catch{}
|
|
location.href='sig1.html';
|
|
}
|
|
|
|
function toast(msg) {
|
|
$('toast').textContent = msg;
|
|
$('toast').classList.add('show');
|
|
clearTimeout(ttimer);
|
|
ttimer = setTimeout(() => $('toast').classList.remove('show'), 2800);
|
|
}
|
|
|
|
loadProfile();
|
|
</script>
|
|
</body>
|
|
</html>
|