( ′∀`)σ≡σ☆))Д′)レ(゚∀゚;)ヘ=З=З=Зε≡(ノ´_ゝ`)ノ HEX
HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux mail.thebrand.ai 6.8.0-107-generic #107-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 13 19:51:50 UTC 2026 x86_64
User: www-data (33)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: /var/www/html/tmpr/../tmpr/..//customui/proposal.php
<?php
// ProposalX: Custom multi-step wizard replacing n8n forms.
// Configure webhook endpoints below to match your workflow.
// Config (env-backed, with safe defaults)
$N8N_BASE_URL   = getenv('N8N_BASE_URL') ?: 'https://thebrandai.app.n8n.cloud';
$N8N_AUTH_TOKEN = getenv('N8N_AUTH_TOKEN') ?: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZDcwMDk1Mi1hNjhiLTRhMDMtODFkZC02NTY3OWRmOGE4YWEiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzYxNTA3MzY2LCJleHAiOjE3NjQwMTgwMDB9.w-J43tTtQz_qeTf7CCLIeH9cqsbLKjPRnLyMp0Opn-M';
$PROPOSALX_TRIGGER = getenv('PROPOSALX_TRIGGER') ?: rtrim($N8N_BASE_URL, '/') . '/webhook/proposalx/trigger';
$PROPOSALX_ANSWER  = getenv('PROPOSALX_ANSWER')  ?: rtrim($N8N_BASE_URL, '/') . '/webhook/proposalx/answer';
$PROPOSALX_CONFIRM = getenv('PROPOSALX_CONFIRM') ?: rtrim($N8N_BASE_URL, '/') . '/webhook/proposalx/confirm';

function proxy_to_n8n($url, $authToken = null){
  $method = $_SERVER['REQUEST_METHOD'];
  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HEADER, false);
  curl_setopt($ch, CURLOPT_ENCODING, ''); // auto-decompress gzip
  // Build pass-through headers with safe defaults
  $hdrs = array();
  $incoming = function_exists('getallheaders') ? getallheaders() : array();
  foreach ($incoming as $k=>$v){
    $lk = strtolower($k);
    if ($lk === 'accept-encoding' || $lk === 'host') continue;
    $hdrs[] = $k . ': ' . $v;
  }
  // Inject server-side Authorization if provided (override any incoming Authorization)
  if ($authToken) {
    $tmp = array();
    foreach ($hdrs as $h){ if (stripos($h, 'authorization:') !== 0) $tmp[] = $h; }
    $hdrs = $tmp;
    $hdrs[] = 'Authorization: Bearer ' . $authToken;
  }
  // Forward body and ensure Content-Type for JSON
  if (in_array($method, array('POST','PUT','PATCH'))){
    $body = file_get_contents('php://input');
    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    $hasCt = false;
    foreach ($hdrs as $h){ if (stripos($h, 'Content-Type:') === 0) { $hasCt = true; break; } }
    if (!$hasCt) { $hdrs[] = 'Content-Type: application/json'; }
  }
  if (!empty($hdrs)) curl_setopt($ch, CURLOPT_HTTPHEADER, $hdrs);

  $resp = curl_exec($ch);
  $err = curl_error($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $ctype  = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
  curl_close($ch);

  // If upstream unreachable or failed at transport level, return JSON error
  if ($resp === false){
    header('Content-Type: application/json');
    http_response_code(502);
    echo json_encode(array(
      'error' => 'Proxy request failed',
      'upstream' => $url,
      'method' => $method,
      'curl_error' => $err
    ));
    exit;
  }

  if ($ctype) header('Content-Type: ' . $ctype);
  http_response_code($status ?: 200);
  echo $resp; exit;
}

if (isset($_GET['action'])){
  $a = $_GET['action'];
  $method = $_SERVER['REQUEST_METHOD'];
  if (in_array($a, array('trigger','answer','confirm')) && $method !== 'POST'){
    header('Content-Type: application/json');
    http_response_code(405);
    echo json_encode(array(
      'error' => 'Method Not Allowed',
      'message' => 'Use POST with JSON body for action=' . $a,
      'allowed' => array('POST')
    ));
    exit;
  }
  if ($a === 'trigger') proxy_to_n8n($PROPOSALX_TRIGGER, $N8N_AUTH_TOKEN);
  if ($a === 'answer')  proxy_to_n8n($PROPOSALX_ANSWER,  $N8N_AUTH_TOKEN);
  if ($a === 'confirm') proxy_to_n8n($PROPOSALX_CONFIRM, $N8N_AUTH_TOKEN);
}
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>ProposalX Wizard</title>
  <style>
    :root{--bg:#f7f9fc;--card:#ffffff;--border:#e6e6e6;--text:#0f172a;--muted:#607089;--accent:#1f7aec}
    *{box-sizing:border-box}
    body{margin:0;background:var(--bg);font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;color:var(--text)}
    .wrap{max-width:920px;margin:24px auto;padding:0 16px}
    .card{background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 2px 6px rgba(0,0,0,.04);padding:16px}
    h1{margin:0 0 8px;font-size:20px}
    .small{color:var(--muted);font-size:12px}
    .code{background:#eef2ff;border:1px solid #c7d2fe;border-radius:6px;padding:2px 6px;font-family:ui-monospace,Consolas,Monaco,monospace}
    .row{display:flex;gap:10px;align-items:center;margin-top:8px}
    .btn{background:var(--accent);color:#fff;border:none;border-radius:8px;padding:10px 14px;font-weight:600;cursor:pointer}
    .btn.secondary{background:#eef2ff;color:#0f172a;border:1px solid #c7d2fe}
    .btn:disabled{opacity:.6;cursor:not-allowed}
    .inputs{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px}
    .inputs .field{background:#fafafa;border:1px solid var(--border);border-radius:8px;padding:10px}
    .field label{display:block;font-size:12px;color:var(--muted);margin-bottom:6px}
    .field input,.field textarea,.field select{width:100%;padding:8px;border:1px solid #d1d5db;border-radius:6px}
    .q{margin-top:12px}
    .q .label{font-weight:600;margin-bottom:6px}
    .q textarea{width:100%;height:90px}
    .list{margin-top:8px}
    .item{display:flex;align-items:center;gap:8px;margin-top:6px}
    .item .label{min-width:160px;color:#607089}
    .item .value{flex:1}
    .steps{display:flex;gap:6px;margin-top:12px}
    .pill{padding:6px 10px;border:1px solid var(--border);border-radius:999px;font-size:12px;color:#607089}
    .pill.active{background:#eef2ff;border-color:#c7d2fe;color:#0f172a;font-weight:600}
    .section{display:none;margin-top:12px}
    .section.active{display:block}
    .grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
    pre{background:#0f172a;color:#fff;padding:10px;border-radius:8px;overflow:auto}
  </style>
</head>
<body>
  <div class="wrap">
    <div class="card">
      <h1>ProposalX <span class="small">Webhook Wizard</span></h1>
      <div class="row small">Webhook Base: <span class="code" id="whBase"><?php echo htmlspecialchars(rtrim($N8N_BASE_URL, '/')); ?></span></div>
      <div class="steps"><span class="pill" id="p1">1. Trigger</span><span class="pill" id="p2">2. Answers</span><span class="pill" id="p3">3. Confirm</span></div>

      <div class="section active" id="step1">
        <div class="inputs">
          <div class="field"><label>Proposal Request</label><textarea id="prompt" placeholder="e.g., Build a CRM for SMBs"></textarea></div>
          <div class="field"><label>Depth</label><select id="depth"><option>3</option><option>4</option><option>5</option></select></div>
          <div class="field"><label>Breadth</label><select id="breadth"><option>3</option><option>4</option><option>5</option></select></div>
        </div>
        <div class="row"><button class="btn" id="btnTrigger">Get Questions</button></div>
        <div class="grid">
          <div>
            <div class="list" id="triggerOut"></div>
            <div id="questionsContainer"></div>
            <div class="row"><button class="btn secondary" id="btnBack1">Back</button><button class="btn" id="btnSubmitAnswers">Submit Answers</button></div>
          </div>
          <div>
            <pre id="triggerRaw">(no response yet)</pre>
          </div>
        </div>
      </div>

      <div class="section" id="step2">
        <div class="grid">
          <div>
            <div class="list" id="answersOut"></div>
            <div class="row"><button class="btn secondary" id="btnRestart">Start Over</button><button class="btn" id="btnConfirm">Done</button></div>
          </div>
          <div>
            <pre id="answersRaw">(no response yet)</pre>
          </div>
        </div>
      </div>

      <div class="section" id="step3">
        <div class="grid">
          <div>
            <div class="list" id="finalList"></div>
          </div>
          <div>
            <pre id="finalRaw">(no response yet)</pre>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script>
    function $(id){ return document.getElementById(id); }
    function setStep(n){ ['step1','step2','step3'].forEach((id,i)=>{ const el=$(id); if(el) el.classList.toggle('active', i+1===n); }); ['p1','p2','p3'].forEach((id,i)=>{ const el=$(id); if(el) el.classList.toggle('active', i < n); }); }
    function pretty(obj){ try{ return JSON.stringify(obj, null, 2); }catch(e){ return String(obj); } }
    function firstJson(latest){ let obj = latest; try{ if(Array.isArray(latest)) obj = latest[0] || {}; if (obj.json) obj = obj.json; }catch(e){} return obj || {}; }

    var state = { requestId:null, prompt:'', depth:3, breadth:3, questions:[], triggerResp:null, answersResp:null, notion:{title:'', description:'', url:''} };

    function extractQuestions(resp){
      const obj = firstJson(resp);
      let q = obj.output && obj.output.questions ? obj.output.questions : (obj.questions || []);
      if (!Array.isArray(q)) q = [];
      if (!q.length && Array.isArray(resp)) {
        q = resp.map(it=>{ const j=it.json||it; return j.question || j.label || null; }).filter(Boolean);
      }
      return q;
    }
    function extractRequestId(resp){
      const obj = firstJson(resp); return obj.request_id || obj.requestId || null;
    }
    function renderQuestions(){
      const c = $('questionsContainer'); c.innerHTML = '';
      if (!state.questions.length){ c.innerHTML = '<div class="small">No questions returned.</div>'; return; }
      state.questions.forEach((q,i)=>{
        const wrap = document.createElement('div'); wrap.className='q';
        const lab = document.createElement('div'); lab.className='label'; lab.textContent = (i+1) + '. ' + q;
        const ta = document.createElement('textarea'); ta.id = 'answer_'+i; ta.placeholder = 'Your answer...';
        wrap.appendChild(lab); wrap.appendChild(ta); c.appendChild(wrap);
      });
    }
    function list(containerId, items){
      const rl = $(containerId); rl.innerHTML='';
      items.forEach(it=>{
        const d = document.createElement('div'); d.className='item';
        const l = document.createElement('div'); l.className='label'; l.textContent = it.label;
        const v = document.createElement('div'); v.className='value'; v.textContent = it.value;
        d.appendChild(l); d.appendChild(v); rl.appendChild(d);
      });
    }

    $('btnTrigger').addEventListener('click', function(){
      state.prompt = $('prompt').value || '';
      state.depth = parseInt(($('depth').value||'3'),10);
      state.breadth = parseInt(($('breadth').value||'3'),10);
      const payload = {
        'What proposal would you like to create?': state.prompt,
        depth: state.depth,
        breadth: state.breadth
      };
      fetch('proposal.php?action=trigger', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      }).then(async r => { const txt = await r.text(); try { return JSON.parse(txt); } catch(e){ return { raw: txt, status: r.status, contentType: r.headers.get('Content-Type') }; } }).then(json=>{
        state.triggerResp = json;
        const q = extractQuestions(json);
        state.questions = q;
        state.requestId = extractRequestId(json);
        list('triggerOut', [
          { label: 'Request ID', value: state.requestId || '(none)' },
          { label: 'Total Questions', value: String(q.length) },
        ]);
        $('triggerRaw').textContent = pretty(json);
        renderQuestions();
        setStep(2);
      }).catch(err=>{
        $('triggerRaw').textContent = 'Error: ' + err;
      });
    });

    $('btnBack1').addEventListener('click', function(){ setStep(1); });

    $('btnSubmitAnswers').addEventListener('click', function(){
      const answersMap = {};
      state.questions.forEach((q,i)=>{ const a = (document.getElementById('answer_'+i).value||'').trim(); answersMap[q] = a; });
      const payload = {
        request_id: state.requestId,
        prompt: state.prompt,
        depth: state.depth,
        breadth: state.breadth,
        answers: answersMap
      };
      fetch('proposal.php?action=answer', {
        method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload)
      }).then(async r => { const txt = await r.text(); try { return JSON.parse(txt); } catch(e){ return { raw: txt, status: r.status, contentType: r.headers.get('Content-Type') }; } }).then(json=>{
        state.answersResp = json;
        const obj = firstJson(json);
        const title = (obj.output && obj.output.title) ? obj.output.title : (obj.title || '');
        const desc  = (obj.output && obj.output.description) ? obj.output.description : (obj.description || '');
        const url   = obj.url || (obj.notionUrl || '');
        state.notion = { title: title, description: desc, url: url };
        list('answersOut', [
          { label: 'Title', value: title || '(none)' },
          { label: 'Description', value: desc || '(none)' },
          { label: 'Notion URL', value: url || '(none)' },
        ]);
        $('answersRaw').textContent = pretty(json);
        list('finalList', [
          { label: 'Request ID', value: state.requestId || '(none)' },
          { label: 'Title', value: title || '(none)' },
          { label: 'Description', value: desc || '(none)' },
          { label: 'Notion URL', value: url || '(none)' },
        ]);
        $('finalRaw').textContent = pretty(json);
        setStep(3);
      }).catch(err=>{ $('answersRaw').textContent = 'Error: ' + err; });
    });

    $('btnConfirm').addEventListener('click', function(){
      const payload = { request_id: state.requestId, status: 'done' };
      fetch('proposal.php?action=confirm', {
        method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload)
      }).then(r=>r.text()).then(txt=>{ alert('Confirmation sent.'); }).catch(err=>{ alert('Error: ' + err); });
    });

    $('btnRestart').addEventListener('click', function(){ location.href = '/proposal.php'; });
  </script>
</body>
</html>