TKK_E32230273/profile.php

800 lines
40 KiB
PHP

<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
include 'koneksi.php';
$stmt = $pdo->prepare("
SELECT a.*, p.id_profile, p.email AS p_email, p.moto, p.provinsi, p.kabupaten, p.kecamatan, p.desa, p.kode_pos,
p.facebook, p.instagram, p.linkedin, p.whatsapp
FROM account a
LEFT JOIN profile p ON a.id_account = p.id_account
WHERE a.id_account = ?
");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) {
header("Location: login.php");
exit;
}
$fotoSrc = (!empty($user['foto']) && file_exists($user['foto'])) ? $user['foto'] : 'images/faces/user.jpg';
$hasProfile = !empty($user['id_profile']);
$hasFb = !empty($user['facebook']);
$hasIg = !empty($user['instagram']);
$hasLi = !empty($user['linkedin']);
$hasWa = !empty($user['whatsapp']);
include 'header.php';
?>
<div class="main-panel">
<div class="content-wrapper">
<div class="row">
<div class="col-md-12 grid-margin">
<div class="row">
<div class="col-12 col-xl-8 mb-4 mb-xl-0">
<h3 class="font-weight-bold">Profile</h3>
<h6 class="font-weight-normal mb-0">Your personal and contact information, <span class="text-primary">Complete your profile now!</span></h6>
</div>
<div class="col-12 col-xl-4">
<div class="justify-content-end d-flex flex-wrap" style="gap:15px; align-items:center;">
<div class="justify-content-end d-flex">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== MAIN CARD ==================== -->
<div class="row">
<div class="col-md-12 grid-margin stretch-card">
<div class="card" style="border:1px solid #e5e9f0;box-shadow:none;border-radius:12px;">
<div class="card-body" style="padding:28px;">
<div class="d-flex justify-content-between align-items-center mb-0">
<p class="card-title mb-0">Profile Overview</p>
<?php if (!empty($user['finger_id'])): ?>
<div style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;padding:8px 12px;display:inline-flex;align-items:center;gap:10px;text-align:left;">
<div style="width:28px;height:28px;border-radius:50%;background:#1d4ed8;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
<i class="mdi mdi-fingerprint" style="color:#e0ecff;font-size:16px;line-height:1;"></i>
</div>
<div style="flex:1;">
<div style="font-size:11px;font-weight:600;color:#000000;line-height:1.2;">Fingerprint Registered via :</div>
<div style="font-size:11px;color:#000000;font-weight:400;line-height:1.2;margin-top:2px;"><?= !empty($user['finger_device']) ? htmlspecialchars($user['finger_device']) : 'Unknown Device' ?></div>
</div>
</div>
<?php endif; ?>
</div>
<!-- ── Sub-Card 1: Profile Header ── -->
<div style="border:1px solid #e5e9f0;border-radius:10px;padding:24px 28px;margin-top:20px;">
<div class="d-flex flex-wrap align-items-center justify-content-between">
<div class="d-flex align-items-center">
<div class="pf-avatar mr-3">
<img src="<?= htmlspecialchars($fotoSrc) ?>" alt="Profile">
</div>
<div>
<h5 class="font-weight-bold mb-1" style="color:#1a1a1a; display:inline-flex; align-items:center;"><?= htmlspecialchars($user['name']) ?>
<?php if ($user['status'] === 'approved'): ?>
<i class="mdi mdi-check-decagram" style="color: #1da1f2; font-size: 18px; margin-left: 4px;" title="Verified Approved"></i>
<?php endif; ?>
</h5>
<div class="d-flex align-items-center flex-wrap" style="gap:6px;">
<span style="color:#888;font-size:13px;">@<?= htmlspecialchars($user['username']) ?></span>
<?php if ($hasProfile && !empty($user['provinsi'])): ?>
<span style="color:#ccc;">|</span>
<span style="color:#888;font-size:13px;"><?= htmlspecialchars($user['kabupaten'] ?? '') ?>, <?= htmlspecialchars($user['provinsi'] ?? '') ?></span>
<?php endif; ?>
</div>
</div>
</div>
<div class="d-flex align-items-center mt-3 mt-md-0" style="gap:8px;">
<!-- Social Media Icon Buttons: enabled/disabled -->
<?php if ($hasFb): ?>
<a href="https://facebook.com/<?= htmlspecialchars($user['facebook']) ?>" target="_blank" class="pf-soc-icon" title="Facebook"><i class="fab fa-facebook-f text-primary"></i></a>
<?php else: ?>
<span class="pf-soc-icon disabled" title="Facebook not set"><i class="fab fa-facebook-f"></i></span>
<?php endif; ?>
<?php if ($hasIg): ?>
<a href="https://instagram.com/<?= htmlspecialchars($user['instagram']) ?>" target="_blank" class="pf-soc-icon" title="Instagram"><i class="fab fa-instagram" style="background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;"></i></a>
<?php else: ?>
<span class="pf-soc-icon disabled" title="Instagram not set"><i class="fab fa-instagram"></i></span>
<?php endif; ?>
<?php if ($hasLi): ?>
<a href="https://linkedin.com/in/<?= htmlspecialchars($user['linkedin']) ?>" target="_blank" class="pf-soc-icon" title="LinkedIn"><i class="fab fa-linkedin-in text-info"></i></a>
<?php else: ?>
<span class="pf-soc-icon disabled" title="LinkedIn not set"><i class="fab fa-linkedin-in"></i></span>
<?php endif; ?>
<?php if ($hasWa): ?>
<a href="https://wa.me/<?= htmlspecialchars(preg_replace('/[^0-9]/','',$user['whatsapp'])) ?>" target="_blank" class="pf-soc-icon" title="WhatsApp"><i class="fab fa-whatsapp text-success"></i></a>
<?php else: ?>
<span class="pf-soc-icon disabled" title="WhatsApp not set"><i class="fab fa-whatsapp"></i></span>
<?php endif; ?>
<button class="pf-edit-btn ml-2" onclick="$('#modalEditSocial').modal('show')"><i class="mdi mdi-pencil mr-1"></i>Edit</button>
</div>
</div>
</div>
<!-- ── Sub-Card 2: Personal Information ── -->
<div style="border:1px solid #e5e9f0;border-radius:10px;padding:24px 28px;margin-top:20px;">
<div class="d-flex justify-content-between align-items-center mb-3">
<p class="card-title mb-0" style="font-size:15px;"><strong>Personal Information</strong></p>
<button class="pf-edit-btn" onclick="$('#modalEditPersonal').modal('show')"><i class="mdi mdi-pencil mr-1"></i>Edit</button>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<span class="pf-lbl">Full Name</span>
<p class="pf-val"><?= htmlspecialchars($user['name']) ?></p>
</div>
<div class="col-md-6 mb-3">
<span class="pf-lbl">Username</span>
<p class="pf-val">@<?= htmlspecialchars($user['username']) ?></p>
</div>
<div class="col-md-6 mb-3">
<span class="pf-lbl">Email address</span>
<p class="pf-val"><?= $hasProfile && !empty($user['p_email']) ? htmlspecialchars($user['p_email']) : '<span class="text-muted" style="font-style:italic;">Not provided</span>' ?></p>
</div>
<div class="col-md-6 mb-3">
<span class="pf-lbl">Phone</span>
<p class="pf-val"><?= $hasProfile && !empty($user['whatsapp']) ? htmlspecialchars($user['whatsapp']) : '<span class="text-muted" style="font-style:italic;">Not provided</span>' ?></p>
</div>
<div class="col-md-12 mb-0">
<span class="pf-lbl">Bio</span>
<p class="pf-val mb-0"><?= $hasProfile && !empty($user['moto']) ? htmlspecialchars($user['moto']) : '<span class="text-muted" style="font-style:italic;">Not provided</span>' ?></p>
</div>
</div>
</div>
<!-- ── Sub-Card 3: Address ── -->
<div style="border:1px solid #e5e9f0;border-radius:10px;padding:24px 28px;margin-top:20px;">
<div class="d-flex justify-content-between align-items-center mb-3">
<p class="card-title mb-0" style="font-size:15px;"><strong>Address</strong></p>
<button class="pf-edit-btn" onclick="openEditAddressModal()"><i class="mdi mdi-pencil mr-1"></i>Edit</button>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<span class="pf-lbl">Province</span>
<p class="pf-val"><?= $hasProfile && !empty($user['provinsi']) ? htmlspecialchars($user['provinsi']) : '<span class="text-muted" style="font-style:italic;">Not provided</span>' ?></p>
</div>
<div class="col-md-6 mb-3">
<span class="pf-lbl">Regency / City</span>
<p class="pf-val"><?= $hasProfile && !empty($user['kabupaten']) ? htmlspecialchars($user['kabupaten']) : '<span class="text-muted" style="font-style:italic;">Not provided</span>' ?></p>
</div>
<div class="col-md-6 mb-3">
<span class="pf-lbl">District</span>
<p class="pf-val"><?= $hasProfile && !empty($user['kecamatan']) ? htmlspecialchars($user['kecamatan']) : '<span class="text-muted" style="font-style:italic;">Not provided</span>' ?></p>
</div>
<div class="col-md-6 mb-3">
<span class="pf-lbl">Village</span>
<p class="pf-val"><?= $hasProfile && !empty($user['desa']) ? htmlspecialchars($user['desa']) : '<span class="text-muted" style="font-style:italic;">Not provided</span>' ?></p>
</div>
<div class="col-md-6 mb-0">
<span class="pf-lbl">Postal Code</span>
<p class="pf-val mb-0"><?= $hasProfile && !empty($user['kode_pos']) ? htmlspecialchars($user['kode_pos']) : '<span class="text-muted" style="font-style:italic;">Not provided</span>' ?></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ====================================================
MODAL 1: EDIT SOCIAL MEDIA (dari card profile header)
==================================================== -->
<div class="modal fade" id="modalEditSocial" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content mdl-card">
<form id="formEditSocial">
<div class="modal-header">
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-share-variant mr-1 text-primary"></i> Edit Social Media</h6>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body py-3">
<input type="hidden" name="form_type" value="social">
<div class="form-group mb-3">
<label class="pf-flbl"><i class="fab fa-facebook mr-1 text-primary"></i>Facebook <span class="text-muted" style="font-size:11px;"></span></label>
<div class="input-group">
<div class="input-group-prepend"><span class="input-group-text" style="background:#f8fafc;border-color:#e5e9f0;font-size:12px;color:#999;">facebook.com/</span></div>
<input type="text" name="facebook" id="soc_facebook" class="form-control" placeholder="username" value="<?= htmlspecialchars($user['facebook'] ?? '') ?>" style="border-color:#e5e9f0;">
</div>
<div id="socFbFeedback" class="pf-fb"></div>
</div>
<div class="form-group mb-3">
<label class="pf-flbl"><i class="fab fa-instagram mr-1" style="color:#E4405F;"></i>Instagram <span class="text-muted" style="font-size:11px;"></span></label>
<div class="input-group">
<div class="input-group-prepend"><span class="input-group-text" style="background:#f8fafc;border-color:#e5e9f0;font-size:12px;color:#999;">@</span></div>
<input type="text" name="instagram" id="soc_instagram" class="form-control" placeholder="username" value="<?= htmlspecialchars($user['instagram'] ?? '') ?>" style="border-color:#e5e9f0;">
</div>
<div id="socIgFeedback" class="pf-fb"></div>
</div>
<div class="form-group mb-3">
<label class="pf-flbl"><i class="fab fa-linkedin mr-1" style="color:#0A66C2;"></i>LinkedIn <span class="text-muted" style="font-size:11px;"></span></label>
<div class="input-group">
<div class="input-group-prepend"><span class="input-group-text" style="background:#f8fafc;border-color:#e5e9f0;font-size:12px;color:#999;">linkedin.com/in/</span></div>
<input type="text" name="linkedin" id="soc_linkedin" class="form-control" placeholder="username" value="<?= htmlspecialchars($user['linkedin'] ?? '') ?>" style="border-color:#e5e9f0;">
</div>
</div>
<div class="form-group mb-0">
<label class="pf-flbl"><i class="fab fa-whatsapp mr-1" style="color:#25D366;"></i>WhatsApp</label>
<div class="input-group">
<div class="input-group-prepend"><span class="input-group-text" style="background:#f8fafc;border-color:#e5e9f0;"><i class="fab fa-whatsapp" style="color:#25D366;"></i></span></div>
<input type="text" name="whatsapp" id="soc_whatsapp" class="form-control" placeholder="08xxxxxxxxxx" required value="<?= htmlspecialchars($user['whatsapp'] ?? '') ?>" style="border-color:#e5e9f0;">
</div>
<div id="socWaFeedback" class="pf-fb"></div>
</div>
<!-- Hidden: carry over other fields -->
<input type="hidden" name="email" value="<?= htmlspecialchars($user['p_email'] ?? '') ?>">
<input type="hidden" name="moto" value="<?= htmlspecialchars($user['moto'] ?? '') ?>">
<input type="hidden" name="provinsi" value="<?= htmlspecialchars($user['provinsi'] ?? '') ?>">
<input type="hidden" name="kabupaten" value="<?= htmlspecialchars($user['kabupaten'] ?? '') ?>">
<input type="hidden" name="kecamatan" value="<?= htmlspecialchars($user['kecamatan'] ?? '') ?>">
<input type="hidden" name="desa" value="<?= htmlspecialchars($user['desa'] ?? '') ?>">
<input type="hidden" name="kode_pos" value="<?= htmlspecialchars($user['kode_pos'] ?? '') ?>">
</div>
<div class="modal-footer py-2">
<button type="button" class="btn btn-light btn-sm" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm" id="btnSaveSocial"><i class="mdi mdi-content-save mr-1"></i>Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- ====================================================
MODAL 2: EDIT PERSONAL INFO (Email, WhatsApp, Bio)
==================================================== -->
<div class="modal fade" id="modalEditPersonal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content mdl-card">
<form id="formEditPersonal">
<div class="modal-header">
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-account-edit mr-1 text-primary"></i> Edit Personal Information</h6>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body py-3">
<input type="hidden" name="form_type" value="personal">
<div class="form-group mb-3">
<label class="pf-flbl">Email</label>
<div class="input-group">
<div class="input-group-prepend"><span class="input-group-text" style="background:#f8fafc;border-color:#e5e9f0;"><i class="mdi mdi-email-outline" style="color:#888;"></i></span></div>
<input type="email" name="email" id="edit_email" class="form-control" placeholder="name@email.com" required value="<?= htmlspecialchars($user['p_email'] ?? '') ?>" style="border-color:#e5e9f0;">
</div>
<div id="emailFeedback" class="pf-fb"></div>
</div>
<div class="form-group mb-0">
<label class="pf-flbl">Moto / Bio <span class="text-muted" style="font-size:11px;"></span></label>
<textarea name="moto" id="edit_moto" class="form-control" rows="3" placeholder="Write a short motto or bio..." style="border-color:#e5e9f0;resize:none;"><?= htmlspecialchars($user['moto'] ?? '') ?></textarea>
</div>
<!-- Hidden: carry over other fields -->
<input type="hidden" name="whatsapp" value="<?= htmlspecialchars($user['whatsapp'] ?? '') ?>">
<input type="hidden" name="facebook" value="<?= htmlspecialchars($user['facebook'] ?? '') ?>">
<input type="hidden" name="instagram" value="<?= htmlspecialchars($user['instagram'] ?? '') ?>">
<input type="hidden" name="linkedin" value="<?= htmlspecialchars($user['linkedin'] ?? '') ?>">
<input type="hidden" name="provinsi" value="<?= htmlspecialchars($user['provinsi'] ?? '') ?>">
<input type="hidden" name="kabupaten" value="<?= htmlspecialchars($user['kabupaten'] ?? '') ?>">
<input type="hidden" name="kecamatan" value="<?= htmlspecialchars($user['kecamatan'] ?? '') ?>">
<input type="hidden" name="desa" value="<?= htmlspecialchars($user['desa'] ?? '') ?>">
<input type="hidden" name="kode_pos" value="<?= htmlspecialchars($user['kode_pos'] ?? '') ?>">
</div>
<div class="modal-footer py-2">
<button type="button" class="btn btn-light btn-sm" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm" id="btnSavePersonal"><i class="mdi mdi-content-save mr-1"></i>Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- ====================================================
MODAL 3: EDIT ADDRESS ONLY (all selects, vertical)
==================================================== -->
<div class="modal fade" id="modalEditAddress" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content mdl-card">
<form id="formEditAddress">
<div class="modal-header">
<h6 class="modal-title font-weight-bold"><i class="mdi mdi-map-marker mr-1 text-primary"></i> Edit Address</h6>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body py-3">
<input type="hidden" name="form_type" value="address">
<input type="hidden" name="provinsi" id="hid_provinsi" value="<?= htmlspecialchars($user['provinsi'] ?? '') ?>">
<input type="hidden" name="kabupaten" id="hid_kabupaten" value="<?= htmlspecialchars($user['kabupaten'] ?? '') ?>">
<input type="hidden" name="kecamatan" id="hid_kecamatan" value="<?= htmlspecialchars($user['kecamatan'] ?? '') ?>">
<input type="hidden" name="desa" id="hid_desa" value="<?= htmlspecialchars($user['desa'] ?? '') ?>">
<input type="hidden" name="kode_pos" id="hid_kode_pos" value="<?= htmlspecialchars($user['kode_pos'] ?? '') ?>">
<div class="form-group mb-3">
<label class="pf-flbl">Province</label>
<div class="ss-wrap" id="ssProvinsi">
<input type="text" class="form-control ss-input" placeholder="Type province then select..." autocomplete="off" value="<?= htmlspecialchars($user['provinsi'] ?? '') ?>" style="border-color:#e5e9f0;">
<div class="ss-dropdown"></div>
</div>
</div>
<div class="form-group mb-3">
<label class="pf-flbl">Regency / City</label>
<div class="ss-wrap" id="ssKabupaten">
<input type="text" class="form-control ss-input" placeholder="Type regency/city then select..." autocomplete="off" value="<?= htmlspecialchars($user['kabupaten'] ?? '') ?>" disabled style="border-color:#e5e9f0;">
<div class="ss-dropdown"></div>
</div>
</div>
<div class="form-group mb-3">
<label class="pf-flbl">District</label>
<div class="ss-wrap" id="ssKecamatan">
<input type="text" class="form-control ss-input" placeholder="Type district then select..." autocomplete="off" value="<?= htmlspecialchars($user['kecamatan'] ?? '') ?>" disabled style="border-color:#e5e9f0;">
<div class="ss-dropdown"></div>
</div>
</div>
<div class="form-group mb-3">
<label class="pf-flbl">Village</label>
<div class="ss-wrap" id="ssDesa">
<input type="text" class="form-control ss-input" placeholder="Type village then select..." autocomplete="off" value="<?= htmlspecialchars($user['desa'] ?? '') ?>" disabled style="border-color:#e5e9f0;">
<div class="ss-dropdown"></div>
</div>
</div>
<div class="form-group mb-0">
<label class="pf-flbl">Postal Code <span class="text-muted" style="font-size:11px;"></span></label>
<div class="ss-wrap" id="ssKodePos">
<input type="text" class="form-control ss-input" placeholder="Select a village to load postal code..." autocomplete="off" value="<?= htmlspecialchars($user['kode_pos'] ?? '') ?>" disabled style="border-color:#e5e9f0;">
<div class="ss-dropdown"></div>
</div>
</div>
<!-- Hidden: carry over other fields -->
<input type="hidden" name="email" value="<?= htmlspecialchars($user['p_email'] ?? '') ?>">
<input type="hidden" name="moto" value="<?= htmlspecialchars($user['moto'] ?? '') ?>">
<input type="hidden" name="facebook" value="<?= htmlspecialchars($user['facebook'] ?? '') ?>">
<input type="hidden" name="instagram" value="<?= htmlspecialchars($user['instagram'] ?? '') ?>">
<input type="hidden" name="linkedin" value="<?= htmlspecialchars($user['linkedin'] ?? '') ?>">
<input type="hidden" name="whatsapp" value="<?= htmlspecialchars($user['whatsapp'] ?? '') ?>">
</div>
<div class="modal-footer py-2">
<button type="button" class="btn btn-light btn-sm" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm" id="btnSaveAddress"><i class="mdi mdi-content-save mr-1"></i>Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- ==================== CSS ==================== -->
<style>
/* ── MODALS ── */
.mdl-card { border: none; border-radius: 6px; box-shadow: 0 8px 30px rgba(0,0,0,0.1); }
.pf-avatar {
width: 64px; height: 64px; border-radius: 50%; overflow: hidden;
border: 2px solid #e5e9f0; flex-shrink: 0;
}
.pf-avatar img { width: 100%; height: 100%; object-fit: cover; }
.pf-soc-icon {
width: 34px; height: 34px; border-radius: 50%; border: 1px solid #d4d4d8;
display: inline-flex; align-items: center; justify-content: center;
color: #333; font-size: 14px; text-decoration: none; transition: all .2s;
background: #fff;
}
.pf-soc-icon:hover:not(.disabled) {
background: #e9ecef; transform: translateY(-2px); text-decoration: none;
}
.pf-soc-icon.disabled {
background: #f5f5f5; border-color: #d4d4d8; color: #333;
cursor: not-allowed; pointer-events: none; opacity: 0.8;
}
.pf-edit-btn {
font-size: 13px; font-weight: 500; color: #333; background: #fff;
border: 1px solid #d4d4d8; border-radius: 6px; padding: 6px 16px;
cursor: pointer; transition: all .15s; display: inline-flex; align-items: center;
}
.pf-edit-btn:hover { background: #f5f5f5; border-color: #bbb; }
.pf-lbl {
display: block; font-size: 12px; font-weight: 500; color: #4B49AC;
margin-bottom: 4px;
}
.pf-val {
font-size: 14px; font-weight: 400; color: #1a1a1a; margin-bottom: 0;
}
.pf-flbl {
font-size: 12px; font-weight: 600; color: #555; margin-bottom: 3px; display: block;
}
.pf-fb {
font-size: 11px; margin-top: 3px; display: none; font-weight: 500;
}
.pf-fb.valid { display: block; color: #10b981; }
.pf-fb.invalid { display: block; color: #ef4444; }
/* Searchable Select */
.ss-wrap { position: relative; }
.ss-input { cursor: text; }
.ss-input:disabled { background: #f8fafc; color: #aaa; cursor: not-allowed; }
.ss-dropdown {
display: none; position: absolute; top: 100%; left: 0; right: 0; z-index: 1060;
background: #fff; border: 1px solid #e5e9f0; border-top: none; border-radius: 0 0 6px 6px;
max-height: 200px; overflow-y: auto; box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.ss-dropdown.show { display: block; }
.ss-item {
padding: 8px 14px; font-size: 13px; color: #333; cursor: pointer;
border-bottom: 1px solid #f5f5f5; transition: background .1s;
}
.ss-item:last-child { border-bottom: none; }
.ss-item:hover, .ss-item.active { background: #f0f4ff; color: #4B49AC; }
.ss-empty { padding: 12px 14px; font-size: 12px; color: #aaa; text-align: center; }
.ss-loading { padding: 12px 14px; font-size: 12px; color: #888; text-align: center; }
.ss-loading::before {
content: ''; display: inline-block; width: 14px; height: 14px;
border: 2px solid #e5e9f0; border-top-color: #4B49AC; border-radius: 50%;
animation: pfSpin .5s linear infinite; vertical-align: middle; margin-right: 6px;
}
@keyframes pfSpin { to { transform: rotate(360deg); } }
</style>
<?php include 'footer.php'; ?>
<!-- ==================== JAVASCRIPT ==================== -->
<script>
const API_BASE = 'https://www.emsifa.com/api-wilayah-indonesia/api';
const KODEPOS_API = 'https://kodepos.vercel.app';
const saved = {
provinsi: <?= json_encode($user['provinsi'] ?? '') ?>,
kabupaten: <?= json_encode($user['kabupaten'] ?? '') ?>,
kecamatan: <?= json_encode($user['kecamatan'] ?? '') ?>,
desa: <?= json_encode($user['desa'] ?? '') ?>,
kode_pos: <?= json_encode($user['kode_pos'] ?? '') ?>
};
// ──────────────────────────────────────────
// Searchable Select Component
// ──────────────────────────────────────────
class SearchableSelect {
constructor(wrapperId, hiddenId, options = {}) {
this.wrap = document.getElementById(wrapperId);
this.input = this.wrap.querySelector('.ss-input');
this.dropdown = this.wrap.querySelector('.ss-dropdown');
this.hidden = document.getElementById(hiddenId);
this.items = []; // {id, name}
this.onSelect = options.onSelect || null;
this.useIdAsValue = options.useIdAsValue || false;
this.selectedId = null;
this._bound = false;
this._bindEvents();
}
_bindEvents() {
if (this._bound) return;
this._bound = true;
this.input.addEventListener('focus', () => {
if (this.items.length > 0) {
this._render(this.input.value);
this.dropdown.classList.add('show');
}
});
this.input.addEventListener('input', () => {
this.hidden.value = '';
this.selectedId = null;
this._render(this.input.value);
this.dropdown.classList.add('show');
});
document.addEventListener('click', (e) => {
if (!this.wrap.contains(e.target)) {
this.dropdown.classList.remove('show');
}
});
}
setItems(items) {
this.items = items;
}
showLoading() {
this.dropdown.innerHTML = '<div class="ss-loading">Loading data...</div>';
this.dropdown.classList.add('show');
}
enable() { this.input.disabled = false; }
disable() { this.input.disabled = true; this.clear(); }
clear() {
this.input.value = '';
this.hidden.value = '';
this.selectedId = null;
this.items = [];
this.dropdown.innerHTML = '';
this.dropdown.classList.remove('show');
}
setValue(name, id) {
this.input.value = name;
this.hidden.value = (this.useIdAsValue && id) ? id : name;
this.selectedId = id || null;
this.dropdown.classList.remove('show');
}
_render(query) {
const q = (query || '').toLowerCase();
const filtered = q ? this.items.filter(i => i.name.toLowerCase().includes(q)) : this.items;
if (filtered.length === 0) {
this.dropdown.innerHTML = '<div class="ss-empty">No results found</div>';
return;
}
this.dropdown.innerHTML = '';
filtered.forEach(item => {
const div = document.createElement('div');
div.className = 'ss-item';
div.textContent = item.name;
div.addEventListener('click', () => {
this.input.value = item.name;
this.hidden.value = (this.useIdAsValue && item.id) ? item.id : item.name;
this.selectedId = item.id;
this.dropdown.classList.remove('show');
if (this.onSelect) this.onSelect(item);
});
this.dropdown.appendChild(div);
});
}
}
// ── Init Searchable Selects ──
let ssProvinsi, ssKabupaten, ssKecamatan, ssDesa, ssKodePos;
let addressInitialized = false;
function openEditAddressModal() {
$('#modalEditAddress').modal('show');
initAddressSelects();
}
function initAddressSelects() {
if (!ssProvinsi) {
ssProvinsi = new SearchableSelect('ssProvinsi', 'hid_provinsi', {
onSelect: (item) => {
// User picked a new province -> clear saved cascade values
saved.kabupaten = '';
saved.kecamatan = '';
saved.desa = '';
saved.kode_pos = '';
loadAddrKabupaten(item.id);
}
});
ssKabupaten = new SearchableSelect('ssKabupaten', 'hid_kabupaten', {
onSelect: (item) => {
// User picked a new kabupaten -> clear saved cascade values
saved.kecamatan = '';
saved.desa = '';
saved.kode_pos = '';
loadAddrKecamatan(item.id);
}
});
ssKecamatan = new SearchableSelect('ssKecamatan', 'hid_kecamatan', {
onSelect: (item) => {
// User picked a new kecamatan -> clear saved cascade values
saved.desa = '';
saved.kode_pos = '';
loadAddrDesa(item.id);
}
});
ssDesa = new SearchableSelect('ssDesa', 'hid_desa', {
onSelect: (item) => {
saved.kode_pos = '';
loadAddrKodePos(item.name);
}
});
ssKodePos = new SearchableSelect('ssKodePos', 'hid_kode_pos', {
useIdAsValue: true
});
}
if (!addressInitialized) {
addressInitialized = true;
loadAddrProvinsi();
}
}
function loadAddrProvinsi() {
ssProvinsi.showLoading();
fetch(API_BASE + '/provinces.json').then(r => r.json()).then(data => {
ssProvinsi.setItems(data.map(p => ({id: p.id, name: p.name})));
ssProvinsi.dropdown.classList.remove('show');
// Pre-populate if saved
if (saved.provinsi) {
const found = data.find(p => p.name === saved.provinsi);
if (found) {
ssProvinsi.setValue(found.name, found.id);
loadAddrKabupaten(found.id);
}
}
}).catch(() => { ssProvinsi.dropdown.classList.remove('show'); });
}
function loadAddrKabupaten(provId) {
ssKabupaten.clear(); ssKecamatan.clear(); ssDesa.clear(); ssKodePos.clear();
ssKabupaten.disable(); ssKecamatan.disable(); ssDesa.disable(); ssKodePos.disable();
ssKabupaten.enable();
ssKabupaten.showLoading();
fetch(API_BASE + '/regencies/' + provId + '.json').then(r => r.json()).then(data => {
ssKabupaten.setItems(data.map(k => ({id: k.id, name: k.name})));
ssKabupaten.dropdown.classList.remove('show');
if (saved.kabupaten) {
const found = data.find(k => k.name === saved.kabupaten);
if (found) {
ssKabupaten.setValue(found.name, found.id);
loadAddrKecamatan(found.id);
}
}
}).catch(() => { ssKabupaten.dropdown.classList.remove('show'); });
}
function loadAddrKecamatan(kabId) {
ssKecamatan.clear(); ssDesa.clear(); ssKodePos.clear();
ssKecamatan.disable(); ssDesa.disable(); ssKodePos.disable();
ssKecamatan.enable();
ssKecamatan.showLoading();
fetch(API_BASE + '/districts/' + kabId + '.json').then(r => r.json()).then(data => {
ssKecamatan.setItems(data.map(k => ({id: k.id, name: k.name})));
ssKecamatan.dropdown.classList.remove('show');
if (saved.kecamatan) {
const found = data.find(k => k.name === saved.kecamatan);
if (found) {
ssKecamatan.setValue(found.name, found.id);
loadAddrDesa(found.id);
}
}
}).catch(() => { ssKecamatan.dropdown.classList.remove('show'); });
}
function loadAddrDesa(kecId) {
ssDesa.clear(); ssKodePos.clear();
ssDesa.disable(); ssKodePos.disable();
ssDesa.enable();
ssDesa.showLoading();
fetch(API_BASE + '/villages/' + kecId + '.json').then(r => r.json()).then(data => {
ssDesa.setItems(data.map(d => ({id: d.id, name: d.name})));
ssDesa.dropdown.classList.remove('show');
if (saved.desa) {
const found = data.find(d => d.name === saved.desa);
if (found) {
ssDesa.setValue(found.name, found.id);
loadAddrKodePos(found.name);
}
}
}).catch(() => { ssDesa.dropdown.classList.remove('show'); });
}
function loadAddrKodePos(desaName) {
ssKodePos.clear();
ssKodePos.disable();
ssKodePos.enable();
ssKodePos.showLoading();
const q = encodeURIComponent(desaName);
fetch(KODEPOS_API + '/search/?q=' + q).then(r => r.json()).then(res => {
const results = res.data || res || [];
const items = [];
const seen = new Set();
results.forEach(r => {
const kp = r.postalcode || r.kodepos || r.postal_code || '';
if (kp && !seen.has(kp)) {
seen.add(kp);
const label = kp + ' — ' + (r.village || r.urban || r.kelurahan || r.desa || desaName);
items.push({id: kp, name: label});
}
});
ssKodePos.setItems(items);
ssKodePos.dropdown.classList.remove('show');
if (saved.kode_pos) {
const found = items.find(i => i.id === saved.kode_pos);
if (found) ssKodePos.setValue(found.name, found.id);
}
// If no results from API, allow manual input
if (items.length === 0) {
ssKodePos.input.placeholder = 'Enter postal code manually...';
ssKodePos.input.addEventListener('input', function() {
ssKodePos.hidden.value = this.value.trim();
});
ssKodePos.input.addEventListener('blur', function() {
if (this.value.trim()) ssKodePos.hidden.value = this.value.trim();
});
}
}).catch(() => {
ssKodePos.dropdown.classList.remove('show');
ssKodePos.input.placeholder = 'Enter postal code manually...';
ssKodePos.input.addEventListener('input', function() {
ssKodePos.hidden.value = this.value.trim();
});
ssKodePos.input.addEventListener('blur', function() {
if (this.value.trim()) ssKodePos.hidden.value = this.value.trim();
});
});
}
// ── Validasi ──
document.getElementById('edit_email').addEventListener('blur', function() {
const fb = document.getElementById('emailFeedback');
const v = this.value.trim();
if (!v) { fb.className = 'pf-fb'; return; }
fb.innerHTML = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? '<i class="mdi mdi-check-circle mr-1"></i>Valid email format' : '<i class="mdi mdi-alert-circle mr-1"></i>Invalid email format';
fb.className = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? 'pf-fb valid' : 'pf-fb invalid';
});
document.getElementById('soc_instagram').addEventListener('blur', function() {
const fb = document.getElementById('socIgFeedback');
const v = this.value.trim();
if (!v) { fb.className = 'pf-fb'; return; }
if (/^[a-zA-Z0-9._]{1,30}$/.test(v)) {
fb.innerHTML = '<i class="mdi mdi-check-circle mr-1"></i>Valid — <a href="https://instagram.com/' + v + '" target="_blank" style="color:#4B49AC;">Check profile</a>';
fb.className = 'pf-fb valid';
} else { fb.innerHTML = '<i class="mdi mdi-alert-circle mr-1"></i>Only letters, numbers, dots, and underscores'; fb.className = 'pf-fb invalid'; }
});
document.getElementById('soc_facebook').addEventListener('blur', function() {
const fb = document.getElementById('socFbFeedback');
const v = this.value.trim();
if (!v) { fb.className = 'pf-fb'; return; }
if (/^[a-zA-Z0-9.]{1,50}$/.test(v)) {
fb.innerHTML = '<i class="mdi mdi-check-circle mr-1"></i>Valid — <a href="https://facebook.com/' + v + '" target="_blank" style="color:#4B49AC;">Check profile</a>';
fb.className = 'pf-fb valid';
} else { fb.innerHTML = '<i class="mdi mdi-alert-circle mr-1"></i>Only letters, numbers, and dots'; fb.className = 'pf-fb invalid'; }
});
document.getElementById('soc_whatsapp').addEventListener('blur', function() {
const fb = document.getElementById('socWaFeedback');
const v = this.value.trim().replace(/[^0-9]/g, '');
if (!v) { fb.className = 'pf-fb'; return; }
fb.innerHTML = (v.length >= 10 && v.length <= 15) ? '<i class="mdi mdi-check-circle mr-1"></i>Valid number' : '<i class="mdi mdi-alert-circle mr-1"></i>Must be 10-15 digits';
fb.className = (v.length >= 10 && v.length <= 15) ? 'pf-fb valid' : 'pf-fb invalid';
});
// ── Generic Submit Handler ──
function submitForm(formId, btnId, modalId) {
document.getElementById(formId).addEventListener('submit', function(e) {
e.preventDefault();
const btn = document.getElementById(btnId);
btn.disabled = true;
btn.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i>Menyimpan...';
fetch('save_profile.php', { method: 'POST', body: new FormData(this) })
.then(r => r.json())
.then(d => {
btn.disabled = false;
btn.innerHTML = '<i class="mdi mdi-content-save mr-1"></i>Save';
if (d.status === 'success') {
$('#' + modalId).modal('hide');
if (typeof showCustomAlert === 'function') showCustomAlert('Success', d.message, 'success');
setTimeout(() => location.reload(), 1000);
} else {
if (typeof showCustomAlert === 'function') showCustomAlert('Failed', d.message, 'error');
else alert(d.message);
}
}).catch(() => {
btn.disabled = false;
btn.innerHTML = '<i class="mdi mdi-content-save mr-1"></i>Save';
alert('Connection failed.');
});
});
}
submitForm('formEditSocial', 'btnSaveSocial', 'modalEditSocial');
submitForm('formEditPersonal', 'btnSavePersonal', 'modalEditPersonal');
submitForm('formEditAddress', 'btnSaveAddress', 'modalEditAddress');
</script>