← back to Patterndesignlab

public/designer-dashboard.html

168 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Your studio · Pattern Design Lab</title>
<link rel="stylesheet" href="/styles.css">
<style>
  .dash{max-width:760px;margin:32px auto 80px;padding:0 20px}
  .dash h1{font-size:26px;margin:0 0 2px}
  .dash .sub{color:#6b6b6b;font-size:13px;margin:0 0 22px}
  .sec{border:1px solid #ebebeb;border-radius:14px;padding:20px 22px;margin:0 0 18px}
  .sec h2{font-size:15px;text-transform:uppercase;letter-spacing:.05em;color:#444;margin:0 0 14px}
  label{display:block;font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:#555;margin:12px 0 5px}
  input,textarea{width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid #ddd;border-radius:9px;font-size:14px;font-family:inherit}
  textarea{min-height:88px;resize:vertical}
  input:focus,textarea:focus{outline:none;border-color:var(--accent-ink,#1c1c1c)}
  .row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
  .row3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}
  .imgrow{display:flex;gap:18px;align-items:center;flex-wrap:wrap}
  .prev{width:88px;height:88px;border-radius:12px;object-fit:cover;background:#f0f0f0;border:1px solid #e5e5e5}
  .prev.logo{border-radius:8px;object-fit:contain;background:#fafafa}
  .imgopts{flex:1;min-width:240px}
  .hint{font-size:12px;color:#8a8a8a;margin:4px 0 0}
  .bar2{position:sticky;bottom:0;background:#fff;border-top:1px solid #eee;padding:14px 0;display:flex;gap:10px;align-items:center;margin-top:8px}
  .btn{display:inline-flex;align-items:center;gap:6px;font-size:14px;border:1px solid #ddd;background:#fff;color:#222;padding:9px 16px;border-radius:999px;cursor:pointer;text-decoration:none}
  .btn:hover{background:#f6f6f6}
  .btn.pri{background:var(--accent-ink,#1c1c1c);color:#fff;border-color:transparent}
  .btn.pri:disabled{opacity:.6}
  .ok{color:#1a7f4b;font-size:13px}
  .err{color:#c0392b;font-size:13px}
  .filebtn{position:relative;overflow:hidden;display:inline-block}
  .filebtn input[type=file]{position:absolute;inset:0;opacity:0;cursor:pointer}
  .topnav{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
</style>
</head>
<body>
<header class="top"><div class="wrap">
  <a class="brand" href="/">Pattern Design<span class="dot">.</span>Lab</a>
  <nav class="navlinks"><a id="viewpub" href="#" target="_blank">View public page ↗</a> <a href="#" id="logout">Sign out</a></nav>
</div></header>
<div class="dash" id="dash"><p style="padding:40px 0;color:#6b6b6b">Loading…</p></div>
<script>
const $=id=>document.getElementById(id);
const esc=s=>(s==null?'':String(s)).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
let D=null;

async function load(){
  const r=await fetch('/api/designer/me');
  if(!r.ok){ location.href='/designer-login'; return; }
  D=(await r.json()).designer;
  $('viewpub').href='/designer/'+D.slug;
  render();
}
function field(id,label,val,ph){ return `<label for="${id}">${label}</label><input id="${id}" value="${esc(val||'')}" placeholder="${ph||''}">`; }

function render(){
  $('dash').innerHTML=`
    <h1>${esc(D.name)}</h1>
    <p class="sub">Your studio profile — everything here shows on your public page.</p>

    <div class="sec">
      <h2>Photo &amp; logo</h2>
      <div class="imgrow" style="margin-bottom:16px">
        <img class="prev" id="avPrev" src="${esc(D.avatar||'')}" alt="">
        <div class="imgopts">
          <strong style="font-size:14px">Studio photo</strong>
          <div class="hint">Your generated photo is set. Replace it by uploading, or paste an image URL.</div>
          <div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">
            <span class="btn filebtn">Upload photo<input type="file" accept="image/*" onchange="doUpload(this,'avatar')"></span>
            <input id="avUrl" placeholder="…or paste image URL" style="flex:1;min-width:180px" value="">
            <button class="btn" onclick="setUrl('avatar')">Use URL</button>
          </div>
        </div>
      </div>
      <div class="imgrow">
        <img class="prev logo" id="logoPrev" src="${esc(D.logo||'')}" alt="" ${D.logo?'':'style="display:none"'}>
        <div class="imgopts">
          <strong style="font-size:14px">Your logo</strong>
          <div class="hint">Optional — upload your own studio logo (shows above your name).</div>
          <div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">
            <span class="btn filebtn">Upload logo<input type="file" accept="image/*" onchange="doUpload(this,'logo')"></span>
            <input id="logoUrl" placeholder="…or paste logo URL" style="flex:1;min-width:180px" value="">
            <button class="btn" onclick="setUrl('logo')">Use URL</button>
          </div>
        </div>
      </div>
    </div>

    <div class="sec">
      <h2>Studio details</h2>
      ${field('name','Studio name',D.name)}
      ${field('tagline','Tagline',D.tagline,'e.g. Botanicals from the forest floor')}
      ${field('founder_name','Founder / lead designer',D.founder_name)}
      <label for="bio">Bio</label><textarea id="bio" placeholder="Tell buyers about your studio…">${esc(D.bio||'')}</textarea>
      <div class="row3">
        <div>${field('city','City',D.city)}</div>
        <div>${field('state_region','Region',D.state_region)}</div>
        <div>${field('country','Country',D.country)}</div>
      </div>
      <label for="accent_hex">Accent color</label>
      <input id="accent_hex" type="text" value="${esc(D.accent_hex||'#1c1c1c')}" placeholder="#1c1c1c" style="max-width:160px">
    </div>

    <div class="sec">
      <h2>Social links &amp; sharing</h2>
      <div class="hint" style="margin-bottom:6px">Handles or full URLs — these become share/follow buttons on your public page.</div>
      <div class="row2">
        <div>${field('website','Website',D.website,'https://…')}</div>
        <div>${field('instagram','Instagram',D.instagram,'@handle')}</div>
        <div>${field('pinterest','Pinterest',D.pinterest,'handle')}</div>
        <div>${field('tiktok','TikTok',D.tiktok,'@handle')}</div>
        <div>${field('facebook','Facebook',D.facebook,'handle')}</div>
        <div>${field('twitter','X / Twitter',D.twitter,'@handle')}</div>
        <div>${field('youtube','YouTube',D.youtube,'@handle')}</div>
        <div>${field('linkedin','LinkedIn',D.linkedin,'company')}</div>
      </div>
    </div>

    <div class="bar2">
      <button class="btn pri" id="save" onclick="save()">Save changes</button>
      <a class="btn" href="/designer/${esc(D.slug)}" target="_blank">Preview ↗</a>
      <span id="msg"></span>
    </div>`;
}

const FIELDS=['name','tagline','founder_name','bio','city','state_region','country','accent_hex','website','instagram','pinterest','tiktok','facebook','twitter','youtube','linkedin'];
async function save(){
  $('save').disabled=true; $('msg').textContent='';
  const body={}; FIELDS.forEach(k=>{const el=$(k); if(el) body[k]=el.value;});
  try{
    const r=await fetch('/api/designer/me',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
    const j=await r.json();
    if(!r.ok){ $('msg').innerHTML='<span class="err">'+(j.error||'Save failed')+'</span>'; }
    else { D=j.designer; $('msg').innerHTML='<span class="ok">Saved ✓</span>'; }
  }catch{ $('msg').innerHTML='<span class="err">Network error</span>'; }
  $('save').disabled=false;
}
async function doUpload(input,type){
  if(!input.files||!input.files[0]) return;
  const fd=new FormData(); fd.append('file',input.files[0]);
  $('msg').textContent='Uploading…';
  try{
    const r=await fetch('/api/designer/upload?type='+type,{method:'POST',body:fd});
    const j=await r.json();
    if(!r.ok){ $('msg').innerHTML='<span class="err">'+(j.error||'Upload failed')+'</span>'; return; }
    D[type]=j.url; applyImg(type,j.url); $('msg').innerHTML='<span class="ok">'+type+' updated ✓</span>';
  }catch{ $('msg').innerHTML='<span class="err">Upload error</span>'; }
}
async function setUrl(type){
  const url=$(type==='avatar'?'avUrl':'logoUrl').value.trim(); if(!url) return;
  try{
    const r=await fetch('/api/designer/me',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({[type]:url})});
    const j=await r.json();
    if(!r.ok){ $('msg').innerHTML='<span class="err">'+(j.error||'Failed')+'</span>'; return; }
    D=j.designer; applyImg(type,url); $('msg').innerHTML='<span class="ok">'+type+' updated ✓</span>';
  }catch{ $('msg').innerHTML='<span class="err">Error</span>'; }
}
function applyImg(type,url){
  const el=$(type==='avatar'?'avPrev':'logoPrev');
  if(el){ el.src=url; el.style.display=''; }
}
$('logout').addEventListener('click',async e=>{ e.preventDefault(); await fetch('/api/designer/logout',{method:'POST'}); location.href='/designer-login'; });
load();
</script>
</body>
</html>