800 lines
40 KiB
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">×</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">×</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">×</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>
|