{"id":8,"date":"2026-07-30T13:25:23","date_gmt":"2026-07-30T13:25:23","guid":{"rendered":"https:\/\/mahall.org\/?page_id=8"},"modified":"2026-07-30T13:27:10","modified_gmt":"2026-07-30T13:27:10","slug":"admin","status":"publish","type":"page","link":"https:\/\/mahall.org\/index.php\/admin\/","title":{"rendered":"Admin"},"content":{"rendered":"\n<!--\n  MAHALL \u2014 admin approval queue (v0.7)\n  ------------------------------------------------------------------\n  HOW TO USE\n  Paste into a WordPress page, e.g. mahall.org\/admin\/listings\n  PROTECT THIS PAGE \u2014 it must only be accessible to admins.\n  Simplest method: add this to your theme's functions.php:\n\n    add_action('template_redirect', function(){\n      if(is_page('admin-listings') && !current_user_can('manage_options')){\n        wp_redirect(home_url('\/'));\n        exit;\n      }\n    });\n\n  WHAT'S REAL vs DEMO\n  - All UI (tabs, filters, expandable cards, approve\/reject\/edit) works\n    in the browser with mock data immediately.\n  - Real data loads from:\n      GET  \/wp-json\/mahall\/v1\/admin\/submissions  (pending venues)\n      GET  \/wp-json\/mahall\/v1\/admin\/live-venues  (live\/featured)\n      POST \/wp-json\/mahall\/v1\/admin\/venue-action (approve\/reject\/feature)\n    Falls back to mock if not yet wired.\n  - The PHP handlers for these endpoints are in mahall-ai-proxy-v0.6.php\n    (stubs added at the bottom of this comment block \u2014 paste them in).\n  - AI review assistant button sends the listing details to Claude and\n    returns a structured quality review: completeness score, missing\n    elements, suggested improvements, approval recommendation.\n\n  PHP STUBS TO ADD to mahall-ai-proxy-v0.6.php\n  (inside the rest_api_init callback):\n  -------------------------------------------------------\n  \/\/ GET \/wp-json\/mahall\/v1\/admin\/submissions\n  register_rest_route('mahall\/v1','\/admin\/submissions',array(\n    'methods'=>'GET',\n    'permission_callback'=>function(){ return current_user_can('manage_options'); },\n    'callback'=>function(){\n      $posts = get_posts(array('post_type'=>'mahall_venue','post_status'=>'pending','posts_per_page'=>50));\n      $out=array();\n      foreach($posts as $p){\n        $out[]=array('id'=>$p->ID,'name'=>$p->post_title,'description'=>$p->post_content,\n          'category'=>get_post_meta($p->ID,'mahall_category',true),\n          'city'=>get_post_meta($p->ID,'mahall_city',true),\n          'capacity'=>get_post_meta($p->ID,'mahall_capacity',true),\n          'plan'=>get_post_meta($p->ID,'mahall_plan',true),\n          'submitted'=>human_time_diff(strtotime($p->post_date),current_time('timestamp')).' ago');\n      }\n      return new WP_REST_Response($out,200);\n    }\n  ));\n  \/\/ GET \/wp-json\/mahall\/v1\/admin\/live-venues\n  register_rest_route('mahall\/v1','\/admin\/live-venues',array(\n    'methods'=>'GET',\n    'permission_callback'=>function(){ return current_user_can('manage_options'); },\n    'callback'=>function(){\n      $posts=get_posts(array('post_type'=>'mahall_venue','post_status'=>'publish','posts_per_page'=>100));\n      $out=array();\n      foreach($posts as $p){\n        $out[]=array('id'=>$p->ID,'name'=>$p->post_title,\n          'category'=>get_post_meta($p->ID,'mahall_category',true),\n          'city'=>get_post_meta($p->ID,'mahall_city',true),\n          'plan'=>get_post_meta($p->ID,'mahall_plan',true),\n          'featured'=>(bool)get_post_meta($p->ID,'mahall_featured',true));\n      }\n      return new WP_REST_Response($out,200);\n    }\n  ));\n  \/\/ POST \/wp-json\/mahall\/v1\/admin\/venue-action\n  register_rest_route('mahall\/v1','\/admin\/venue-action',array(\n    'methods'=>'POST',\n    'permission_callback'=>function(){ return current_user_can('manage_options'); },\n    'callback'=>function(WP_REST_Request $req){\n      $id=absint($req->get_param('venueId'));\n      $action=sanitize_text_field($req->get_param('action'));\n      if(!$id||!in_array($action,array('approve','reject','feature','unfeature','suspend'),true))\n        return new WP_REST_Response(array('error'=>'Invalid'),400);\n      if($action==='approve') wp_update_post(array('ID'=>$id,'post_status'=>'publish'));\n      elseif($action==='reject') wp_update_post(array('ID'=>$id,'post_status'=>'draft'));\n      elseif($action==='feature') update_post_meta($id,'mahall_featured',1);\n      elseif($action==='unfeature') delete_post_meta($id,'mahall_featured');\n      elseif($action==='suspend') wp_update_post(array('ID'=>$id,'post_status'=>'private'));\n      return new WP_REST_Response(array('success'=>true),200);\n    }\n  ));\n------------------------------------------------------------------- -->\n<style>\n@import url('https:\/\/fonts.googleapis.com\/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..700;1,9..144,400..600&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');\n\n#mahall-admin{\n  --bg:#16130F; --bg-raised:#1E1A14; --bg-card:#241F18;\n  --ink:#F3ECE0; --ink-soft:#BBAE99; --ink-faint:#7A7163;\n  --gold:#D98E33; --gold-soft:#E8B978;\n  --teal:#2F6F62; --teal-soft:#5C988B;\n  --terracotta:#B5532D;\n  --line:rgba(243,236,224,0.12);\n  --radius:14px;\n  font-family:'Inter',sans-serif; background:var(--bg); color:var(--ink);\n  -webkit-font-smoothing:antialiased; display:flex; min-height:100vh;\n}\n#mahall-admin *{box-sizing:border-box; margin:0; padding:0;}\n#mahall-admin a{color:inherit; text-decoration:none;}\n#mahall-admin h1,#mahall-admin h2,#mahall-admin h3{font-family:'Fraunces',serif; font-weight:500; color:var(--ink); line-height:1.15;}\n#mahall-admin p{color:var(--ink-soft); line-height:1.6;}\n#mahall-admin .eyebrow{font-family:'JetBrains Mono',monospace; font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--gold-soft);}\n#mahall-admin .star{position:relative;width:14px;height:14px;flex:none;}\n#mahall-admin .star span{position:absolute;inset:0;border:1.4px solid var(--gold);}\n#mahall-admin .star span:last-child{transform:rotate(45deg);}\n#mahall-admin .btn{display:inline-flex;align-items:center;gap:7px;font-weight:500;font-size:13px;padding:8px 16px;border-radius:999px;border:1px solid var(--line);cursor:pointer;background:transparent;color:var(--ink);transition:border-color .2s,transform .15s,background .2s;}\n#mahall-admin .btn:hover{border-color:var(--gold-soft);transform:translateY(-1px);}\n#mahall-admin .btn-solid{background:var(--gold);border-color:var(--gold);color:#1A1305;}\n#mahall-admin .btn-solid:hover{background:var(--gold-soft);}\n#mahall-admin .btn-teal{background:var(--teal);border-color:var(--teal);color:var(--ink);}\n#mahall-admin .btn-teal:hover{background:var(--teal-soft);}\n#mahall-admin .btn-danger{border-color:var(--terracotta);color:var(--terracotta);}\n#mahall-admin .btn-danger:hover{background:var(--terracotta);color:var(--ink);}\n#mahall-admin .btn-sm{padding:5px 12px;font-size:12px;}\n#mahall-admin .btn-xs{padding:4px 10px;font-size:11px;}\n\n\/* sidebar *\/\n#mahall-admin .sidebar{width:220px;flex:none;border-right:1px solid var(--line);background:var(--bg-raised);display:flex;flex-direction:column;position:sticky;top:0;height:100vh;overflow-y:auto;}\n#mahall-admin .sb-logo{padding:20px 18px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:8px;font-family:'Fraunces',serif;font-size:17px;}\n#mahall-admin .sb-badge{background:var(--terracotta);color:var(--ink);font-size:10px;font-family:'JetBrains Mono',monospace;padding:2px 7px;border-radius:999px;margin-left:auto;}\n#mahall-admin .sb-nav{padding:12px 8px;flex:1;}\n#mahall-admin .sb-label{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-faint);padding:10px 10px 6px;}\n#mahall-admin .sb-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:10px;font-size:13px;color:var(--ink-soft);cursor:pointer;margin-bottom:2px;transition:background .15s,color .15s;}\n#mahall-admin .sb-item:hover{background:var(--bg-card);color:var(--ink);}\n#mahall-admin .sb-item.active{background:rgba(217,142,51,.12);color:var(--gold-soft);}\n#mahall-admin .sb-item .cnt{margin-left:auto;font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ink-faint);}\n#mahall-admin .sb-item .cnt.hi{color:var(--terracotta);}\n#mahall-admin .sb-foot{padding:14px 18px;border-top:1px solid var(--line);font-size:12px;color:var(--ink-faint);}\n\n\/* main *\/\n#mahall-admin .main{flex:1;overflow-x:hidden;display:flex;flex-direction:column;}\n#mahall-admin .topbar{display:flex;align-items:center;justify-content:space-between;padding:18px 28px;border-bottom:1px solid var(--line);position:sticky;top:0;background:rgba(22,19,15,.92);backdrop-filter:blur(10px);z-index:10;}\n#mahall-admin .topbar h2{font-size:18px;}\n#mahall-admin .content{padding:28px;flex:1;}\n\n\/* stat strip *\/\n#mahall-admin .stat-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:28px;}\n#mahall-admin .sc{background:var(--bg-raised);border:1px solid var(--line);border-radius:var(--radius);padding:16px 18px;}\n#mahall-admin .sc .lbl{font-size:11px;font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-bottom:8px;}\n#mahall-admin .sc .val{font-family:'Fraunces',serif;font-size:26px;color:var(--gold-soft);}\n#mahall-admin .sc .sub{font-size:12px;color:var(--ink-faint);margin-top:3px;}\n#mahall-admin .sc.alert .val{color:var(--terracotta);}\n\n\/* tabs *\/\n#mahall-admin .tabs{display:flex;gap:6px;margin-bottom:20px;flex-wrap:wrap;}\n#mahall-admin .tab{font-size:12px;font-family:'JetBrains Mono',monospace;padding:7px 16px;border-radius:999px;border:1px solid var(--line);color:var(--ink-soft);cursor:pointer;background:transparent;transition:border-color .2s,color .2s;}\n#mahall-admin .tab.active{border-color:var(--gold);color:var(--gold-soft);}\n#mahall-admin .tab .n{margin-left:6px;background:var(--bg-card);padding:1px 6px;border-radius:999px;font-size:10px;}\n#mahall-admin .tab.active .n{background:rgba(217,142,51,.2);}\n\n\/* search \/ filter bar *\/\n#mahall-admin .filter-bar{display:flex;gap:10px;margin-bottom:18px;flex-wrap:wrap;}\n#mahall-admin .filter-bar input{flex:1;min-width:200px;background:var(--bg-raised);border:1px solid var(--line);border-radius:8px;padding:9px 13px;color:var(--ink);font-family:'Inter',sans-serif;font-size:13px;outline:none;}\n#mahall-admin .filter-bar input:focus{border-color:var(--gold-soft);}\n#mahall-admin .filter-bar select{background:var(--bg-raised);border:1px solid var(--line);border-radius:8px;padding:9px 13px;color:var(--ink-soft);font-size:13px;outline:none;cursor:pointer;}\n\n\/* venue cards *\/\n#mahall-admin .venue-card{background:var(--bg-raised);border:1px solid var(--line);border-radius:var(--radius);margin-bottom:12px;overflow:hidden;transition:border-color .2s;}\n#mahall-admin .venue-card:hover{border-color:rgba(243,236,224,.22);}\n#mahall-admin .venue-card.expanded .vc-body{display:block;}\n#mahall-admin .vc-head{display:flex;align-items:center;gap:14px;padding:14px 18px;cursor:pointer;}\n#mahall-admin .vc-thumb{width:52px;height:42px;border-radius:8px;border:1px solid var(--line);flex:none;background:repeating-linear-gradient(45deg,rgba(217,142,51,.10) 0 6px,rgba(47,111,98,.10) 6px 12px);}\n#mahall-admin .vc-meta{flex:1;min-width:0;}\n#mahall-admin .vc-meta b{display:block;font-size:14px;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}\n#mahall-admin .vc-meta span{font-size:12px;color:var(--ink-faint);}\n#mahall-admin .vc-pills{display:flex;gap:6px;flex-wrap:wrap;align-items:center;}\n#mahall-admin .pill{font-size:10px;font-family:'JetBrains Mono',monospace;padding:3px 8px;border-radius:999px;border:1px solid;white-space:nowrap;}\n#mahall-admin .pill.pending{border-color:var(--gold);color:var(--gold-soft);}\n#mahall-admin .pill.live{border-color:var(--teal-soft);color:var(--teal-soft);}\n#mahall-admin .pill.rejected{border-color:var(--terracotta);color:var(--terracotta);}\n#mahall-admin .pill.suspended{border-color:var(--ink-faint);color:var(--ink-faint);}\n#mahall-admin .pill.featured{border-color:var(--gold);color:var(--gold-soft);background:rgba(217,142,51,.1);}\n#mahall-admin .pill.free{border-color:var(--line);color:var(--ink-faint);}\n#mahall-admin .pill.paid{border-color:var(--teal-soft);color:var(--teal-soft);}\n\n#mahall-admin .vc-body{display:none;border-top:1px solid var(--line);padding:18px;}\n#mahall-admin .vc-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:16px;}\n#mahall-admin .vd{font-size:12px;}\n#mahall-admin .vd span{display:block;color:var(--ink-faint);margin-bottom:3px;}\n#mahall-admin .vd b{color:var(--ink);}\n#mahall-admin .vc-desc{font-size:13px;color:var(--ink-soft);background:var(--bg-card);border-radius:10px;padding:12px 14px;margin-bottom:14px;line-height:1.6;}\n#mahall-admin .vc-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}\n#mahall-admin .vc-divider{width:1px;height:20px;background:var(--line);}\n\n\/* AI review panel *\/\n#mahall-admin .ai-review{margin-top:16px;display:none;background:var(--bg-card);border:1px solid var(--line);border-radius:12px;padding:16px;}\n#mahall-admin .ai-review.show{display:block;}\n#mahall-admin .ai-review h4{font-size:13px;font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:.08em;color:var(--gold-soft);margin-bottom:12px;}\n#mahall-admin .score-row{display:flex;align-items:center;gap:14px;margin-bottom:12px;}\n#mahall-admin .score-circle{width:52px;height:52px;border-radius:50%;border:2px solid var(--gold);display:flex;align-items:center;justify-content:center;font-family:'Fraunces',serif;font-size:18px;color:var(--gold-soft);flex:none;}\n#mahall-admin .score-circle.hi{border-color:var(--teal-soft);color:var(--teal-soft);}\n#mahall-admin .score-circle.lo{border-color:var(--terracotta);color:var(--terracotta);}\n#mahall-admin .ai-rec{font-size:13px;color:var(--ink-soft);}\n#mahall-admin .ai-rec b{color:var(--ink);}\n#mahall-admin .ai-items{display:flex;flex-direction:column;gap:6px;margin-top:10px;}\n#mahall-admin .ai-item{font-size:12px;display:flex;gap:8px;align-items:flex-start;}\n#mahall-admin .ai-item .dot{width:6px;height:6px;border-radius:50%;flex:none;margin-top:4px;}\n#mahall-admin .ai-item .dot.ok{background:var(--teal-soft);}\n#mahall-admin .ai-item .dot.warn{background:var(--gold);}\n#mahall-admin .ai-item .dot.bad{background:var(--terracotta);}\n#mahall-admin .ai-thinking{font-size:12px;color:var(--gold-soft);font-family:'JetBrains Mono',monospace;display:none;}\n#mahall-admin .ai-thinking.show{display:block;}\n\n\/* rejection modal *\/\n#mahall-admin .modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:50;display:none;align-items:center;justify-content:center;}\n#mahall-admin .modal-overlay.open{display:flex;}\n#mahall-admin .modal{background:var(--bg-raised);border:1px solid var(--line);border-radius:var(--radius);padding:24px;width:420px;max-width:90vw;}\n#mahall-admin .modal h3{font-size:18px;margin-bottom:12px;}\n#mahall-admin .modal textarea{width:100%;background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:11px 13px;color:var(--ink);font-family:'Inter',sans-serif;font-size:13px;outline:none;resize:vertical;margin-bottom:14px;}\n#mahall-admin .modal textarea:focus{border-color:var(--gold-soft);}\n#mahall-admin .modal-actions{display:flex;gap:10px;justify-content:flex-end;}\n\n\/* empty *\/\n#mahall-admin .empty{text-align:center;padding:48px;color:var(--ink-faint);font-size:13px;}\n#mahall-admin .empty .ic{font-size:32px;display:block;margin-bottom:10px;opacity:.4;}\n\n\/* views *\/\n#mahall-admin .view{display:none;}\n#mahall-admin .view.active{display:block;}\n\n@media(max-width:860px){\n  #mahall-admin{flex-direction:column;}\n  #mahall-admin .sidebar{width:100%;height:auto;position:static;}\n  #mahall-admin .sb-nav{display:flex;overflow-x:auto;padding:8px;}\n  #mahall-admin .sb-label{display:none;}\n  #mahall-admin .stat-strip{grid-template-columns:1fr 1fr;}\n  #mahall-admin .vc-grid{grid-template-columns:1fr 1fr;}\n}\n@media(max-width:560px){\n  #mahall-admin .stat-strip{grid-template-columns:1fr;}\n  #mahall-admin .content{padding:16px;}\n}\n<\/style>\n\n<div id=\"mahall-admin\">\n\n  <aside class=\"sidebar\">\n    <div class=\"sb-logo\">\n      <span class=\"star\"><span><\/span><span><\/span><\/span> Mahall\n      <span class=\"sb-badge\" id=\"sb-pending-badge\">3<\/span>\n    <\/div>\n    <nav class=\"sb-nav\">\n      <div class=\"sb-label\">Content<\/div>\n      <div class=\"sb-item active\" data-view=\"queue\">&#9201; Approval queue <span class=\"cnt hi\" id=\"sb-q-count\">3<\/span><\/div>\n      <div class=\"sb-item\" data-view=\"live\">&#9679; Live venues <span class=\"cnt\" id=\"sb-live-count\">9<\/span><\/div>\n      <div class=\"sb-item\" data-view=\"analytics\">&#128202; Analytics<\/div>\n      <div class=\"sb-label\">System<\/div>\n      <div class=\"sb-item\" data-view=\"settings\">&#9881; Settings<\/div>\n      <div class=\"sb-item\"><a href=\"\/\" style=\"color:inherit;\">&#8592; Back to site<\/a><\/div>\n    <\/nav>\n    <div class=\"sb-foot\">Mahall admin<br><b style=\"color:var(--ink)\">mahall.org<\/b><\/div>\n  <\/aside>\n\n  <div class=\"main\">\n    <div class=\"topbar\">\n      <h2 id=\"view-title\">Approval queue<\/h2>\n      <div style=\"display:flex;gap:10px;\">\n        <span style=\"font-size:12px;color:var(--ink-faint);align-self:center;\" id=\"last-refreshed\"><\/span>\n        <button class=\"btn btn-sm\" id=\"refresh-btn\">&#8635; Refresh<\/button>\n      <\/div>\n    <\/div>\n\n    <div class=\"content\">\n\n      <div class=\"stat-strip\" id=\"stat-strip\"><\/div>\n\n      <!-- APPROVAL QUEUE -->\n      <div class=\"view active\" data-pane=\"queue\">\n        <div class=\"filter-bar\">\n          <input id=\"q-search\" placeholder=\"Search by name or city...\">\n          <select id=\"q-cat-filter\">\n            <option value=\"\">All categories<\/option>\n            <option value=\"heritage\">Heritage &#038; cultural<\/option>\n            <option value=\"resort\">Hospitality &#038; resort<\/option>\n            <option value=\"private\">Private &#038; exclusive<\/option>\n            <option value=\"nature\">Nature &#038; adventure<\/option>\n            <option value=\"coastal\">Coastal &#038; leisure<\/option>\n            <option value=\"urban\">Urban &#038; lifestyle<\/option>\n            <option value=\"corporate\">Corporate &#038; MICE<\/option>\n            <option value=\"entertainment\">Entertainment &#038; production<\/option>\n          <\/select>\n          <select id=\"q-plan-filter\">\n            <option value=\"\">All plans<\/option>\n            <option value=\"free\">Free<\/option>\n            <option value=\"featured\">Featured<\/option>\n            <option value=\"premium\">Premium<\/option>\n          <\/select>\n        <\/div>\n        <div id=\"queue-list\"><\/div>\n      <\/div>\n\n      <!-- LIVE VENUES -->\n      <div class=\"view\" data-pane=\"live\">\n        <div class=\"tabs\" id=\"live-tabs\">\n          <button class=\"tab active\" data-ltab=\"all\">All <span class=\"n\" id=\"lt-all\">0<\/span><\/button>\n          <button class=\"tab\" data-ltab=\"featured\">Featured <span class=\"n\" id=\"lt-feat\">0<\/span><\/button>\n          <button class=\"tab\" data-ltab=\"free\">Free plan <span class=\"n\" id=\"lt-free\">0<\/span><\/button>\n        <\/div>\n        <div class=\"filter-bar\">\n          <input id=\"live-search\" placeholder=\"Search live venues...\">\n        <\/div>\n        <div id=\"live-list\"><\/div>\n      <\/div>\n\n      <!-- ANALYTICS -->\n      <div class=\"view\" data-pane=\"analytics\">\n        <div style=\"background:var(--bg-raised);border:1px solid var(--line);border-radius:var(--radius);padding:24px;\">\n          <span class=\"eyebrow\" style=\"display:block;margin-bottom:16px;\">Platform overview<\/span>\n          <div id=\"analytics-grid\" style=\"display:grid;grid-template-columns:repeat(3,1fr);gap:16px;\"><\/div>\n        <\/div>\n      <\/div>\n\n      <!-- SETTINGS -->\n      <div class=\"view\" data-pane=\"settings\">\n        <div style=\"background:var(--bg-raised);border:1px solid var(--line);border-radius:var(--radius);padding:24px;max-width:480px;\">\n          <h3 style=\"font-size:16px;margin-bottom:18px;\">Admin settings<\/h3>\n          <div style=\"display:flex;flex-direction:column;gap:14px;\">\n            <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--line);\">\n              <span style=\"font-size:13px;\">Email me on new submission<\/span>\n              <input type=\"checkbox\" checked style=\"accent-color:var(--gold);width:16px;height:16px;\">\n            <\/div>\n            <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--line);\">\n              <span style=\"font-size:13px;\">Auto-approve Featured\/Premium plans<\/span>\n              <input type=\"checkbox\" style=\"accent-color:var(--gold);width:16px;height:16px;\">\n            <\/div>\n            <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--line);\">\n              <span style=\"font-size:13px;\">AI review on every submission<\/span>\n              <input type=\"checkbox\" checked style=\"accent-color:var(--gold);width:16px;height:16px;\">\n            <\/div>\n            <button class=\"btn btn-solid\" style=\"align-self:flex-start;margin-top:4px;\">Save settings<\/button>\n          <\/div>\n        <\/div>\n      <\/div>\n\n    <\/div>\n  <\/div>\n\n  <!-- Rejection modal -->\n  <div class=\"modal-overlay\" id=\"reject-modal\">\n    <div class=\"modal\">\n      <h3>Reason for rejection<\/h3>\n      <p style=\"font-size:13px;margin-bottom:14px;\">This message will be emailed to the venue owner so they can improve their submission and reapply.<\/p>\n      <textarea id=\"reject-reason\" rows=\"5\" placeholder=\"e.g. The listing is missing at least 4 photos. Please also add a description of at least 100 words before resubmitting.\"><\/textarea>\n      <div class=\"modal-actions\">\n        <button class=\"btn\" id=\"reject-cancel\">Cancel<\/button>\n        <button class=\"btn btn-danger\" id=\"reject-confirm\">Send rejection &#10148;<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n\n  \/* ---- mock data ---- *\/\n  var PENDING = [\n    {id:101, name:'Riad Al Baraka', category:'heritage', city:'Fes', capacity:60, plan:'featured', photoCount:8, hasVideo:false, has3D:false, description:'A beautifully restored 18th-century riad in the heart of the Fes medina. Perfect for weddings and intimate ceremonies with a stunning central courtyard and rooftop terrace.', submitted:'2 hours ago'},\n    {id:102, name:'Sahara Luxe Camp', category:'nature', city:'Zagora', capacity:40, plan:'premium', photoCount:12, hasVideo:true, has3D:true, description:'Exclusive glamping experience in the Zagora dunes. Each tent is fully furnished with en-suite bathroom. Ideal for corporate retreats and luxury private events.', submitted:'5 hours ago'},\n    {id:103, name:'Villa Esperanza', category:'private', city:'Tangier', capacity:100, plan:'free', photoCount:3, hasVideo:false, has3D:false, description:'Modern villa with pool.', submitted:'1 day ago'}\n  ];\n\n  var LIVE = [\n    {id:1,  name:'Riad Zahra',         category:'heritage',      city:'Marrakech',  plan:'featured', featured:true},\n    {id:2,  name:'Dunes Camp Erg',      category:'nature',        city:'Merzouga',   plan:'featured', featured:false},\n    {id:3,  name:'Atlas Beach Club',    category:'coastal',       city:'Essaouira',  plan:'premium',  featured:true},\n    {id:4,  name:'Villa Janoub',        category:'private',       city:'Tangier',    plan:'featured', featured:false},\n    {id:5,  name:'Kasbah Ait Noir',     category:'heritage',      city:'Ouarzazate', plan:'free',     featured:false},\n    {id:6,  name:'Skyline Rooftop',     category:'urban',         city:'Casablanca', plan:'featured', featured:false},\n    {id:7,  name:'Palmeraie Resort',    category:'resort',        city:'Marrakech',  plan:'premium',  featured:true},\n    {id:8,  name:'Centre Atlantique',   category:'corporate',     city:'Rabat',      plan:'featured', featured:false},\n    {id:9,  name:'Studio Lumi\u00e8re',      category:'entertainment', city:'Casablanca', plan:'featured', featured:true}\n  ];\n\n  var ANALYTICS = [\n    {label:'Total venues live', value:9},\n    {label:'Pending review', value:3},\n    {label:'Total leads (30d)', value:27},\n    {label:'New registrations (30d)', value:14},\n    {label:'Featured listings', value:4},\n    {label:'Monthly revenue (MAD)', value:'7,947'}\n  ];\n\n  var pendingList = [...PENDING];\n  var liveList    = [...LIVE];\n  var rejectTargetId = null;\n  var liveFacet = 'all';\n\n  \/* ---- view routing ---- *\/\n  document.querySelectorAll('.sb-item[data-view]').forEach(function(item){\n    item.addEventListener('click', function(){\n      var view = this.getAttribute('data-view');\n      document.querySelectorAll('.sb-item').forEach(function(i){ i.classList.remove('active'); });\n      this.classList.add('active');\n      document.querySelectorAll('.view').forEach(function(v){ v.classList.toggle('active', v.getAttribute('data-pane')===view); });\n      var titles = {queue:'Approval queue', live:'Live venues', analytics:'Analytics', settings:'Settings'};\n      document.getElementById('view-title').textContent = titles[view] || view;\n    });\n  });\n\n  \/* ---- stats strip ---- *\/\n  function renderStats(){\n    var pCount = pendingList.length;\n    var lCount = liveList.length;\n    var featCount = liveList.filter(function(v){ return v.featured; }).length;\n    document.getElementById('stat-strip').innerHTML = [\n      {lbl:'Pending review', val:pCount, sub:'Awaiting approval', alert:pCount>0},\n      {lbl:'Live venues', val:lCount, sub:'Published on Mahall', alert:false},\n      {lbl:'Featured', val:featCount, sub:'Homepage\/category promoted', alert:false},\n      {lbl:'This month revenue', val:'7,947 MAD', sub:'From active subscriptions', alert:false}\n    ].map(function(s){\n      return '<div class=\"sc'+(s.alert?' alert':'')+'\"><div class=\"lbl\">'+s.lbl+'<\/div><div class=\"val\">'+s.val+'<\/div><div class=\"sub\">'+s.sub+'<\/div><\/div>';\n    }).join('');\n    document.getElementById('sb-pending-badge').textContent = pCount;\n    document.getElementById('sb-q-count').textContent = pCount;\n    document.getElementById('sb-live-count').textContent = lCount;\n  }\n\n  \/* ---- AI review ---- *\/\n  async function runAiReview(venue, cardId){\n    var reviewEl = document.getElementById('ai-review-'+cardId);\n    var thinkEl  = document.getElementById('ai-think-'+cardId);\n    reviewEl.classList.remove('show');\n    thinkEl.classList.add('show');\n\n    var prompt = 'Review this venue submission for a Morocco venue marketplace called Mahall. ' +\n      'Assess: completeness, description quality, photo count, 3D\/video presence, plan tier. ' +\n      'Venue: name=\"'+venue.name+'\", category=\"'+venue.category+'\", city=\"'+venue.city+\n      '\", capacity='+venue.capacity+', plan=\"'+venue.plan+'\", photos='+venue.photoCount+\n      ', video='+(venue.hasVideo?'yes':'no')+', 3D='+(venue.has3D?'yes':'no')+\n      ', description=\"'+venue.description.substring(0,200)+'\". ' +\n      'Reply ONLY with JSON, no markdown: ' +\n      '{\"score\":0-100,\"recommendation\":\"approve\"|\"review\"|\"reject\",\"summary\":\"1 sentence\",' +\n      '\"items\":[{\"status\":\"ok\"|\"warn\"|\"bad\",\"text\":\"finding\"}]}';\n\n    var parsed = null;\n    try{\n      var res = await fetch('\/wp-json\/mahall\/v1\/ai-concierge',{\n        method:'POST', headers:{'Content-Type':'application\/json'},\n        body:JSON.stringify({message:prompt, history:[]})\n      });\n      if(res.ok){\n        var data = await res.json();\n        var clean = (data.reply||'').replace(\/```json|```\/g,'').trim();\n        parsed = JSON.parse(clean);\n      }\n    }catch(e){}\n\n    \/* Fallback scoring if API unavailable *\/\n    if(!parsed){\n      var score = 40;\n      var items = [];\n      if(venue.description.length > 80){ score+=15; items.push({status:'ok', text:'Description provided'}); }\n      else { items.push({status:'bad', text:'Description too short (under 80 chars)'}); }\n      if(venue.photoCount >= 6){ score+=20; items.push({status:'ok', text:venue.photoCount+' photos uploaded \u2014 good'}); }\n      else { items.push({status:'warn', text:'Only '+venue.photoCount+' photos \u2014 recommend at least 6'}); }\n      if(venue.hasVideo){ score+=10; items.push({status:'ok', text:'Video tour present'}); }\n      else { items.push({status:'warn', text:'No video \u2014 consider adding one for higher conversion'}); }\n      if(venue.has3D){ score+=10; items.push({status:'ok', text:'3D tour present'}); }\n      else { items.push({status:'warn', text:'No 3D tour'}); }\n      if(venue.plan!=='free'){ score+=5; items.push({status:'ok', text:'Paid plan: '+venue.plan}); }\n      score = Math.min(score, 100);\n      parsed = {\n        score: score,\n        recommendation: score>=75?'approve':(score>=50?'review':'reject'),\n        summary: score>=75?'Listing looks complete and ready to go live.':\n                 score>=50?'Listing is acceptable but could be improved before going live.':\n                 'Listing needs more content before it can be approved.',\n        items: items\n      };\n    }\n\n    thinkEl.classList.remove('show');\n    var cls = parsed.score>=75?'hi':(parsed.score<50?'lo':'');\n    var recColor = parsed.recommendation==='approve'?'var(--teal-soft)':\n                   parsed.recommendation==='reject'?'var(--terracotta)':'var(--gold-soft)';\n    reviewEl.innerHTML =\n      '<h4>&#10022; AI review<\/h4>'+\n      '<div class=\"score-row\">'+\n      '<div class=\"score-circle '+cls+'\">'+parsed.score+'<\/div>'+\n      '<div class=\"ai-rec\"><b style=\"color:'+recColor+'\">'+parsed.recommendation.toUpperCase()+'<\/b><br>'+parsed.summary+'<\/div>'+\n      '<\/div>'+\n      '<div class=\"ai-items\">'+\n      (parsed.items||[]).map(function(it){\n        return '<div class=\"ai-item\"><span class=\"dot '+it.status+'\"><\/span><span>'+it.text+'<\/span><\/div>';\n      }).join('')+'<\/div>';\n    reviewEl.classList.add('show');\n  }\n\n  \/* ---- queue rendering ---- *\/\n  function renderQueue(){\n    var search  = (document.getElementById('q-search').value||'').toLowerCase();\n    var catF    = document.getElementById('q-cat-filter').value;\n    var planF   = document.getElementById('q-plan-filter').value;\n    var list = pendingList.filter(function(v){\n      if(search && v.name.toLowerCase().indexOf(search)<0 &#038;&#038; v.city.toLowerCase().indexOf(search)<0) return false;\n      if(catF  &#038;&#038; v.category!==catF) return false;\n      if(planF &#038;&#038; v.plan!==planF) return false;\n      return true;\n    });\n    var el = document.getElementById('queue-list');\n    if(!list.length){ el.innerHTML='<div class=\"empty\"><span class=\"ic\">&#9989;<\/span>No pending submissions matching your filters.<\/div>'; return; }\n    el.innerHTML = list.map(function(v){\n      var cid = 'vc-'+v.id;\n      return '<div class=\"venue-card\" data-id=\"'+v.id+'\">'+\n        '<div class=\"vc-head\">'+\n        '<div class=\"vc-thumb\"><\/div>'+\n        '<div class=\"vc-meta\"><b>'+v.name+'<\/b><span>'+v.city+' &middot; '+v.category+'<\/span><\/div>'+\n        '<div class=\"vc-pills\">'+\n        '<span class=\"pill pending\">Pending<\/span>'+\n        '<span class=\"pill '+(v.plan==='free'?'free':'paid')+'\">'+v.plan+'<\/span>'+\n        '<span style=\"font-size:12px;color:var(--ink-faint)\">'+v.submitted+'<\/span>'+\n        '<\/div><\/div>'+\n        '<div class=\"vc-body\">'+\n        '<div class=\"vc-grid\">'+\n        '<div class=\"vd\"><span>Category<\/span><b>'+v.category+'<\/b><\/div>'+\n        '<div class=\"vd\"><span>City<\/span><b>'+v.city+'<\/b><\/div>'+\n        '<div class=\"vd\"><span>Capacity<\/span><b>'+v.capacity+'<\/b><\/div>'+\n        '<div class=\"vd\"><span>Photos<\/span><b>'+v.photoCount+(v.hasVideo?' + video':'')+(v.has3D?' + 3D':'')+'<\/b><\/div>'+\n        '<\/div>'+\n        '<div class=\"vc-desc\">'+v.description+'<\/div>'+\n        '<div class=\"vc-actions\">'+\n        '<button class=\"btn btn-teal btn-sm\" data-act=\"approve\">&#10003; Approve<\/button>'+\n        '<button class=\"btn btn-danger btn-sm\" data-act=\"reject\">&#10005; Reject<\/button>'+\n        '<div class=\"vc-divider\"><\/div>'+\n        '<button class=\"btn btn-sm\" data-act=\"ai-review\">&#10022; AI review<\/button>'+\n        '<\/div>'+\n        '<div class=\"ai-thinking\" id=\"ai-think-'+v.id+'\">Analysing listing...<\/div>'+\n        '<div class=\"ai-review\" id=\"ai-review-'+v.id+'\"><\/div>'+\n        '<\/div>'+\n        '<\/div>';\n    }).join('');\n\n    el.querySelectorAll('.venue-card').forEach(function(card){\n      card.querySelector('.vc-head').addEventListener('click', function(e){\n        if(e.target.closest('button')) return;\n        card.classList.toggle('expanded');\n      });\n      card.querySelectorAll('[data-act]').forEach(function(btn){\n        btn.addEventListener('click', function(){\n          var id = parseInt(card.getAttribute('data-id'));\n          var act = this.getAttribute('data-act');\n          if(act==='approve') approveVenue(id);\n          else if(act==='reject'){ rejectTargetId=id; document.getElementById('reject-modal').classList.add('open'); }\n          else if(act==='ai-review'){\n            var venue = pendingList.find(function(v){ return v.id===id; });\n            if(venue){ card.classList.add('expanded'); runAiReview(venue, id); }\n          }\n        });\n      });\n    });\n  }\n\n  \/* ---- live venues rendering ---- *\/\n  function renderLive(){\n    var search = (document.getElementById('live-search').value||'').toLowerCase();\n    var list = liveList.filter(function(v){\n      if(search && v.name.toLowerCase().indexOf(search)<0 &#038;&#038; v.city.toLowerCase().indexOf(search)<0) return false;\n      if(liveFacet==='featured' &#038;&#038; !v.featured) return false;\n      if(liveFacet==='free' &#038;&#038; v.plan!=='free') return false;\n      return true;\n    });\n    document.getElementById('lt-all').textContent  = liveList.length;\n    document.getElementById('lt-feat').textContent = liveList.filter(function(v){ return v.featured; }).length;\n    document.getElementById('lt-free').textContent = liveList.filter(function(v){ return v.plan==='free'; }).length;\n\n    var el = document.getElementById('live-list');\n    if(!list.length){ el.innerHTML='<div class=\"empty\"><span class=\"ic\">&#127968;<\/span>No venues match this filter.<\/div>'; return; }\n    el.innerHTML = list.map(function(v){\n      return '<div class=\"venue-card\" data-id=\"'+v.id+'\">'+\n        '<div class=\"vc-head\" style=\"cursor:default;\">'+\n        '<div class=\"vc-thumb\"><\/div>'+\n        '<div class=\"vc-meta\"><b>'+v.name+'<\/b><span>'+v.city+' &middot; '+v.category+'<\/span><\/div>'+\n        '<div class=\"vc-pills\">'+\n        '<span class=\"pill live\">&#9679; Live<\/span>'+\n        (v.featured?'<span class=\"pill featured\">&#10022; Featured<\/span>':'')+\n        '<span class=\"pill '+(v.plan==='free'?'free':'paid')+'\">'+v.plan+'<\/span>'+\n        '<\/div>'+\n        '<div style=\"display:flex;gap:6px;margin-left:8px;\">'+\n        (v.featured\n          ? '<button class=\"btn btn-xs\" data-act=\"unfeature\">Unfeature<\/button>'\n          : '<button class=\"btn btn-xs btn-solid\" data-act=\"feature\">&#10022; Feature<\/button>')+\n        '<button class=\"btn btn-xs btn-danger\" data-act=\"suspend\">Suspend<\/button>'+\n        '<\/div>'+\n        '<\/div><\/div>';\n    }).join('');\n\n    el.querySelectorAll('[data-act]').forEach(function(btn){\n      btn.addEventListener('click', function(){\n        var id   = parseInt(this.closest('.venue-card').getAttribute('data-id'));\n        var act  = this.getAttribute('data-act');\n        venueAction(id, act);\n        if(act==='feature'||act==='unfeature'){\n          var v = liveList.find(function(x){ return x.id===id; });\n          if(v){ v.featured = (act==='feature'); renderLive(); renderStats(); }\n        } else if(act==='suspend'){\n          liveList = liveList.filter(function(x){ return x.id!==id; });\n          renderLive(); renderStats();\n        }\n      });\n    });\n  }\n\n  \/* ---- live tab filter ---- *\/\n  document.getElementById('live-tabs').addEventListener('click', function(e){\n    var tab = e.target.closest('.tab'); if(!tab) return;\n    document.querySelectorAll('.tab').forEach(function(t){ t.classList.remove('active'); });\n    tab.classList.add('active');\n    liveFacet = tab.getAttribute('data-ltab');\n    renderLive();\n  });\n\n  \/* ---- analytics ---- *\/\n  function renderAnalytics(){\n    document.getElementById('analytics-grid').innerHTML = ANALYTICS.map(function(a){\n      return '<div class=\"sc\"><div class=\"lbl\">'+a.label+'<\/div><div class=\"val\">'+a.value+'<\/div><\/div>';\n    }).join('');\n  }\n\n  \/* ---- actions ---- *\/\n  function approveVenue(id){\n    pendingList = pendingList.filter(function(v){ return v.id!==id; });\n    renderQueue(); renderStats();\n    venueAction(id, 'approve');\n  }\n\n  function venueAction(id, action){\n    fetch('\/wp-json\/mahall\/v1\/admin\/venue-action',{\n      method:'POST', headers:{'Content-Type':'application\/json'},\n      body:JSON.stringify({venueId:id, action:action})\n    }).catch(function(){});\n  }\n\n  \/* ---- reject modal ---- *\/\n  document.getElementById('reject-cancel').addEventListener('click', function(){ document.getElementById('reject-modal').classList.remove('open'); });\n  document.getElementById('reject-confirm').addEventListener('click', function(){\n    var reason = document.getElementById('reject-reason').value.trim();\n    if(!reason){ alert('Please enter a reason before rejecting.'); return; }\n    venueAction(rejectTargetId, 'reject');\n    pendingList = pendingList.filter(function(v){ return v.id!==rejectTargetId; });\n    document.getElementById('reject-modal').classList.remove('open');\n    document.getElementById('reject-reason').value='';\n    renderQueue(); renderStats();\n  });\n\n  \/* ---- search \/ filter wiring ---- *\/\n  document.getElementById('q-search').addEventListener('input', renderQueue);\n  document.getElementById('q-cat-filter').addEventListener('change', renderQueue);\n  document.getElementById('q-plan-filter').addEventListener('change', renderQueue);\n  document.getElementById('live-search').addEventListener('input', renderLive);\n\n  \/* ---- refresh ---- *\/\n  async function refresh(){\n    var btn = document.getElementById('refresh-btn');\n    btn.textContent = 'Refreshing...';\n    try{\n      var res = await fetch('\/wp-json\/mahall\/v1\/admin\/submissions');\n      if(res.ok){ pendingList = await res.json(); }\n      var res2 = await fetch('\/wp-json\/mahall\/v1\/admin\/live-venues');\n      if(res2.ok){ liveList = await res2.json(); }\n    }catch(e){}\n    renderQueue(); renderLive(); renderStats();\n    btn.textContent = '\u21bb Refresh';\n    document.getElementById('last-refreshed').textContent = 'Updated ' + new Date().toLocaleTimeString();\n  }\n  document.getElementById('refresh-btn').addEventListener('click', refresh);\n\n  \/* ---- boot ---- *\/\n  renderStats();\n  renderQueue();\n  renderLive();\n  renderAnalytics();\n  document.getElementById('last-refreshed').textContent = 'Updated ' + new Date().toLocaleTimeString();\n\n})();\n<\/script>\n\n","protected":false},"excerpt":{"rendered":"<p>Mahall 3 Content &#9201; Approval queue 3 &#9679; Live venues 9 &#128202; Analytics System &#9881; Settings &#8592; Back to site Mahall adminmahall.org Approval queue &#8635; Refresh All categoriesHeritage &#038; culturalHospitality &#038; resortPrivate &#038; exclusiveNature &#038; adventureCoastal &#038; leisureUrban &#038; lifestyleCorporate &#038; MICEEntertainment &#038; production All plansFreeFeaturedPremium All 0 Featured 0 Free plan 0 Platform overview [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"folder":[],"class_list":["post-8","page","type-page","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/pages\/8","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/comments?post=8"}],"version-history":[{"count":2,"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/pages\/8\/revisions"}],"predecessor-version":[{"id":10,"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/pages\/8\/revisions\/10"}],"wp:attachment":[{"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/media?parent=8"}],"wp:term":[{"taxonomy":"folder","embeddable":true,"href":"https:\/\/mahall.org\/index.php\/wp-json\/wp\/v2\/folder?post=8"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}