added on WebSTLs (webstls.com) at 2026-07-01 22:27:01
| 1 | <div class="page-head"> | |
| 2 | <div> | |
| 3 | <span class="page-eyebrow"><span class="dot"></span> Multi-platform inbox</span> | |
| 4 | <h1 class="page-title">Messages, comments & reviews</h1> | |
| 5 | <p class="page-subtitle">Every comment, review, and DM from every platform. One inbox. No more juggling tabs.</p> | |
| 6 | </div> | |
| 7 | <div class="page-actions"> | |
| 8 | [if:$not_tutorial_mode]<a href="/messages.cgi?tutorial=1" class="btn btn-secondary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>Sample Data View</a>[/if] | |
| 9 | [if:$tutorial_mode]<a href="/messages.cgi" class="btn btn-secondary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>Exit sample view</a>[/if] | |
| 10 | [if:$has_messages][if:$not_tutorial_mode] | |
| 11 | <form method="POST" action="/messages_action.cgi" style="display:inline"> | |
| 12 | <input type="hidden" name="action" value="mark_all_read"> | |
| 13 | <button type="submit" class="btn btn-secondary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>Mark all read</button> | |
| 14 | </form> | |
| 15 | [/if][/if] | |
| 16 | <a href="/marketplaces.cgi" class="btn btn-primary">Connect platforms</a> | |
| 17 | </div> | |
| 18 | </div> | |
| 19 | ||
| 20 | [if:$tutorial_mode] | |
| 21 | <div class="tutorial-banner"> | |
| 22 | <div class="tutorial-banner-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></div> | |
| 23 | <div class="tutorial-banner-body"> | |
| 24 | <div class="tutorial-banner-title">Tutorial · Sample data only</div> | |
| 25 | <div class="tutorial-banner-text">The five messages on the left, the open thread in the middle, and the reply panel are all example data — nothing here is from your real marketplaces or storefront. Use this to see how the inbox reads once you start getting comments and reviews.</div> | |
| 26 | </div> | |
| 27 | <a href="/messages.cgi" class="btn btn-secondary btn-sm">Exit sample view</a> | |
| 28 | </div> | |
| 29 | ||
| 30 | <div class="tutorial-intro"> | |
| 31 | <div class="tutorial-intro-eyebrow">A walkthrough of the multi-platform inbox</div> | |
| 32 | <h2 class="tutorial-intro-title">Every comment and review, in one place.</h2> | |
| 33 | <p class="tutorial-intro-text">A background worker checks each connected marketplace every 10 minutes and writes new comments and reviews into <code>external_comments</code> tagged with the platform name. Comments on your own WebSTLs storefront write here instantly with <code>platform_name='storefront'</code>. The four filter tabs at the top of the list (<strong>All</strong>, <strong>Unread</strong>, <strong>Flagged</strong>, <strong>Archived</strong>) all run off the same table with simple status filters.</p> | |
| 34 | <p class="tutorial-intro-text">When you reply from this page, WebSTLs queues the reply for the source platform and posts it under your connected account — no copy-paste, no tab-juggling. Replies show inline under the original message with a <em>SENT</em>, <em>SENDING</em>, or <em>FAILED</em> pill so you always know what landed. Use the <strong>Flag</strong> button to mark a message for follow-up (refunds, complaints, urgent questions) and <strong>Archive</strong> to hide resolved threads without deleting them.</p> | |
| 35 | <p class="tutorial-intro-text tutorial-intro-foot">Click <a href="/messages.cgi" class="text-brand fw-700">Exit sample view</a> to return to your real inbox, or <a href="/marketplaces.cgi" class="text-brand fw-700">connect a marketplace</a> to start populating it.</p> | |
| 36 | </div> | |
| 37 | [/if] | |
| 38 | ||
| 39 | <!-- Top-level Inbox / Reports tabs. Pill-style so the section | |
| 40 | switch is impossible to miss. Moved above content so they're | |
| 41 | always visible regardless of how big the empty state is. --> | |
| 42 | <style> | |
| 43 | .mm-tabs { display:flex; gap:8px; margin-bottom:20px; } | |
| 44 | .mm-tab { | |
| 45 | display:inline-flex; align-items:center; gap:8px; | |
| 46 | padding:11px 22px; height:42px; | |
| 47 | border-radius:11px; | |
| 48 | font-size:13px; font-weight:700; | |
| 49 | letter-spacing:0.5px; text-transform:uppercase; | |
| 50 | text-decoration:none; | |
| 51 | border:1px solid var(--col-border); | |
| 52 | background:var(--col-surface-2); | |
| 53 | color:var(--col-text-2); | |
| 54 | transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.12s, box-shadow 0.15s; | |
| 55 | } | |
| 56 | .mm-tab:hover { | |
| 57 | background:var(--col-surface-3); color:var(--col-text); | |
| 58 | border-color:rgba(59,130,246,0.40); | |
| 59 | transform:translateY(-1px); | |
| 60 | } | |
| 61 | .mm-tab.active { | |
| 62 | background:linear-gradient(135deg, rgba(59,130,246,0.30) 0%, rgba(99,102,241,0.22) 100%); | |
| 63 | border-color:rgba(59,130,246,0.75); | |
| 64 | color:#fff; | |
| 65 | box-shadow:0 0 18px rgba(59,130,246,0.45); | |
| 66 | } | |
| 67 | .mm-tab svg { width:16px; height:16px; flex-shrink:0; opacity:0.85; } | |
| 68 | .mm-tab.active svg { opacity:1; } | |
| 69 | </style> | |
| 70 | <div class="mm-tabs"> | |
| 71 | <a href="/messages.cgi" class="mm-tab[if:$mtab_inbox] active[/if]"> | |
| 72 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> | |
| 73 | Inbox | |
| 74 | </a> | |
| 75 | <a href="/messages.cgi?tab=reports" class="mm-tab[if:$mtab_reports] active[/if]"> | |
| 76 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18"/><path d="M7 14l4-4 4 4 6-6"/></svg> | |
| 77 | Reports | |
| 78 | </a> | |
| 79 | </div> | |
| 80 | ||
| 81 | [if:$mtab_inbox] | |
| 82 | [if:$has_support_threads] | |
| 83 | <!-- Support threads: ALL recent threads with the WebSTLs team. Shows | |
| 84 | even when read so the user always has a path into their support | |
| 85 | history from the inbox; unread ones get the green NEW badge. --> | |
| 86 | <div style="background:linear-gradient(135deg, rgba(59,130,246,0.10), rgba(59,130,246,0.04));border:1px solid rgba(59,130,246,0.40);border-radius:12px;padding:14px 18px;margin-bottom:18px"> | |
| 87 | <div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px"> | |
| 88 | <span style="display:inline-block;width:9px;height:9px;border-radius:50%;background:#3b82f6;box-shadow:0 0 6px rgba(59,130,246,0.8)"></span> | |
| 89 | <span style="font-size:11px;letter-spacing:1.5px;text-transform:uppercase;font-weight:700;color:#93c5fd">Your support threads</span> | |
| 90 | [if:$has_support_unread]<span style="font-size:11px;color:#6ee7b7;font-weight:700;margin-left:auto">$support_unread_count unread reply from the WebSTLs team</span>[/if] | |
| 91 | [if:!$has_support_unread]<a href="/support.cgi?new=1" style="font-size:11px;color:#93c5fd;text-decoration:none;font-weight:700;margin-left:auto">+ New request</a>[/if] | |
| 92 | </div> | |
| 93 | <!-- Same compact row anatomy as the admin inbox: subject (truncating) | |
| 94 | | status pill | unread badge | timestamp. Keeps padding tight so | |
| 95 | this section fits well above the marketplace-comment inbox. --> | |
| 96 | <style> | |
| 97 | .us-thread { display:flex;align-items:center;gap:8px;padding:6px 12px; | |
| 98 | background:var(--col-surface-2);border:1px solid var(--col-border); | |
| 99 | border-radius:8px;text-decoration:none;color:var(--col-text); | |
| 100 | min-height:32px;transition:border-color 0.12s, background 0.12s; } | |
| 101 | .us-thread:hover { border-color:var(--col-accent); } | |
| 102 | .us-thread-subj { font-size:13px;font-weight:600;color:var(--col-text); | |
| 103 | flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3; } | |
| 104 | .us-pill { padding:1px 7px;border-radius:10px;font-size:10px;font-weight:700; | |
| 105 | letter-spacing:0.4px;flex-shrink:0; } | |
| 106 | .us-pill.st-needs { background:rgba(239,68,68,0.15);color:#f87171;border:1px solid rgba(239,68,68,0.32); } | |
| 107 | .us-pill.st-waiting { background:rgba(245,158,11,0.15);color:#fbbf24;border:1px solid rgba(245,158,11,0.32); } | |
| 108 | .us-pill.st-resolved { background:rgba(34,197,94,0.15);color:#4ade80;border:1px solid rgba(34,197,94,0.32); } | |
| 109 | .us-pill.st-closed { background:rgba(148,163,184,0.15);color:#cbd5e1;border:1px solid rgba(148,163,184,0.32); } | |
| 110 | .us-pill.st-open { background:rgba(59,130,246,0.18);color:#60a5fa;border:1px solid rgba(59,130,246,0.36); } | |
| 111 | .us-thread-unread { min-width:18px;height:18px;padding:0 5px;border-radius:9px; | |
| 112 | background:#10b981;color:#fff;font-size:10px;font-weight:800; | |
| 113 | display:inline-flex;align-items:center;justify-content:center;flex-shrink:0; } | |
| 114 | .us-thread-time { font-size:11px;color:var(--col-text-3);flex-shrink:0; } | |
| 115 | </style> | |
| 116 | <div style="display:flex;flex-direction:column;gap:2px"> | |
| 117 | [loop:@support_threads] | |
| 118 | <a href="$loop1.href" class="us-thread" title="$loop1.subject -- $loop1.status_label"> | |
| 119 | <span class="us-thread-subj">$loop1.subject</span> | |
| 120 | <span class="us-pill $loop1.pill_class">$loop1.status_label</span> | |
| 121 | [if:$loop1.has_unread]<span class="us-thread-unread">$loop1.unread</span>[/if] | |
| 122 | <span class="us-thread-time">$loop1.last_at</span> | |
| 123 | </a> | |
| 124 | [/loop] | |
| 125 | </div> | |
| 126 | </div> | |
| 127 | [/if] | |
| 128 | ||
| 129 | [if:$no_messages] | |
| 130 | [if:$has_mp_connected] | |
| 131 | <!-- User has connected marketplaces but no comments yet. Show | |
| 132 | compact "Connected" pill row + small "Connect more" hint | |
| 133 | so they know which platforms are wired up and which they | |
| 134 | could still add. --> | |
| 135 | <div style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:12px;padding:14px 18px;margin-bottom:18px"> | |
| 136 | <div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px"> | |
| 137 | <span style="display:inline-block;width:9px;height:9px;border-radius:50%;background:#10b981;box-shadow:0 0 6px rgba(16,185,129,0.7)"></span> | |
| 138 | <span style="font-size:11px;letter-spacing:1.5px;text-transform:uppercase;font-weight:700;color:#6ee7b7">Connected · $mp_connected_count</span> | |
| 139 | <span style="font-size:11px;color:var(--col-text-3)">Waiting for comments — we check every 10 minutes.</span> | |
| 140 | <a href="/marketplaces.cgi" style="margin-left:auto;font-size:11px;font-weight:700;color:#93c5fd;text-decoration:none">Manage →</a> | |
| 141 | </div> | |
| 142 | <div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:[if:$has_mp_available]12px[/if][if:!$has_mp_available]0[/if]"> | |
| 143 | [loop:@mp_connected] | |
| 144 | <span style="display:inline-flex;align-items:center;gap:5px;padding:4px 10px;background:rgba(16,185,129,0.16);border:1px solid rgba(16,185,129,0.45);border-radius:999px;font-size:12px;font-weight:600;color:#6ee7b7"> | |
| 145 | <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg> | |
| 146 | $loop1.name | |
| 147 | </span> | |
| 148 | [/loop] | |
| 149 | </div> | |
| 150 | [if:$has_mp_available] | |
| 151 | <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding-top:10px;border-top:1px solid var(--col-border)"> | |
| 152 | <span style="font-size:10px;letter-spacing:1.5px;text-transform:uppercase;font-weight:700;color:var(--col-text-3);margin-right:4px">Connect more</span> | |
| 153 | [loop:@mp_available] | |
| 154 | <a href="/marketplaces.cgi" style="display:inline-flex;align-items:center;gap:4px;padding:3px 9px;background:var(--col-surface-2);border:1px solid var(--col-border);border-radius:999px;font-size:11px;font-weight:600;color:var(--col-text-2);text-decoration:none;transition:border-color 0.12s, color 0.12s">+ $loop1.name</a> | |
| 155 | [/loop] | |
| 156 | </div> | |
| 157 | [/if] | |
| 158 | </div> | |
| 159 | [/if] | |
| 160 | [if:!$has_mp_connected] | |
| 161 | <!-- ===== Compact empty state for users with no connections yet. ===== --> | |
| 162 | <div style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:12px;padding:18px 22px;margin-bottom:18px;display:flex;align-items:center;gap:18px;flex-wrap:wrap"> | |
| 163 | <div style="width:44px;height:44px;border-radius:11px;background:linear-gradient(140deg, rgba(124,58,237,0.20), rgba(59,130,246,0.20));border:1px solid rgba(124,58,237,0.42);display:grid;place-items:center;flex-shrink:0"> | |
| 164 | <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.8" style="color:var(--col-accent-bright)"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z"/></svg> | |
| 165 | </div> | |
| 166 | <div style="flex:1;min-width:240px"> | |
| 167 | <div style="font-family:var(--font-display);font-size:15px;color:#fff;font-weight:700;margin-bottom:2px">No marketplace comments yet</div> | |
| 168 | <div style="font-size:12.5px;color:var(--col-text-2);line-height:1.55">Connect Cults3D / Gumroad / MyMiniFactory / Patreon and replies post back through one inbox. Comments on your WebSTLs storefront and replies from the WebSTLs support team also appear here automatically.</div> | |
| 169 | </div> | |
| 170 | <a href="/marketplaces.cgi" class="btn btn-primary" style="flex-shrink:0">Connect a platform</a> | |
| 171 | </div> | |
| 172 | [/if] | |
| 173 | [/if] | |
| 174 | [/if] | |
| 175 | ||
| 176 | [if:$mtab_reports] | |
| 177 | <!-- ===== Reports tab ===== --> | |
| 178 | [if:$has_reports_data] | |
| 179 | <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:18px"> | |
| 180 | <div style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:12px;padding:16px 18px"> | |
| 181 | <div style="font-size:11px;font-weight:700;color:var(--col-text-3);letter-spacing:1.2px;text-transform:uppercase;margin-bottom:6px">Comments (30d)</div> | |
| 182 | <div style="font-family:var(--font-display,sans-serif);font-size:28px;color:var(--col-text);line-height:1">$rep_total_30d</div> | |
| 183 | </div> | |
| 184 | <div style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:12px;padding:16px 18px"> | |
| 185 | <div style="font-size:11px;font-weight:700;color:var(--col-text-3);letter-spacing:1.2px;text-transform:uppercase;margin-bottom:6px">Avg per day</div> | |
| 186 | <div style="font-family:var(--font-display,sans-serif);font-size:28px;color:#93c5fd;line-height:1">$rep_avg_per_day</div> | |
| 187 | </div> | |
| 188 | <div style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:12px;padding:16px 18px"> | |
| 189 | <div style="font-size:11px;font-weight:700;color:var(--col-text-3);letter-spacing:1.2px;text-transform:uppercase;margin-bottom:6px">Top platform</div> | |
| 190 | <div style="font-family:var(--font-display,sans-serif);font-size:22px;color:#6ee7b7;line-height:1;text-transform:capitalize">$rep_top_platform</div> | |
| 191 | </div> | |
| 192 | <div style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:12px;padding:16px 18px"> | |
| 193 | <div style="font-size:11px;font-weight:700;color:var(--col-text-3);letter-spacing:1.2px;text-transform:uppercase;margin-bottom:6px">Unread now</div> | |
| 194 | <div style="font-family:var(--font-display,sans-serif);font-size:28px;color:#fbbf24;line-height:1">$count_unread</div> | |
| 195 | </div> | |
| 196 | </div> | |
| 197 | <style> | |
| 198 | .mc-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(420px,1fr)); gap:14px; margin-bottom:18px; } | |
| 199 | .mc-card { position:relative; background:var(--col-surface-1); border:1px solid var(--col-border); border-radius:14px; padding:18px 20px; } | |
| 200 | .mc-head { display:flex; align-items:baseline; justify-content:space-between; margin-bottom:10px; } | |
| 201 | .mc-title { font-size:12px; letter-spacing:1.5px; text-transform:uppercase; font-weight:700; color:var(--col-text-3); } | |
| 202 | .mc-sub { font-size:11px; color:var(--col-text-3); } | |
| 203 | .ac-hot { cursor:pointer; } | |
| 204 | .ac-tip { | |
| 205 | position:absolute; pointer-events:none; opacity:0; transition:opacity 0.1s; | |
| 206 | background:rgba(8,12,26,0.96); border:1px solid var(--col-border); | |
| 207 | color:#fff; font-size:12px; padding:7px 11px; border-radius:7px; | |
| 208 | white-space:nowrap; box-shadow:0 6px 18px rgba(0,0,0,0.45); | |
| 209 | transform:translate(-50%, -100%); margin-top:-8px; z-index:50; | |
| 210 | } | |
| 211 | .ac-tip strong { color:var(--col-accent-bright); } | |
| 212 | </style> | |
| 213 | <div class="mc-grid"> | |
| 214 | <div class="mc-card" data-chart-id="vol"> | |
| 215 | <div class="mc-head"> | |
| 216 | <span class="mc-title">Comment volume · last 30 days</span> | |
| 217 | <span class="mc-sub" style="color:#93c5fd">$rep_total_30d total</span> | |
| 218 | </div> | |
| 219 | $chart_vol_html | |
| 220 | <div class="ac-tip"></div> | |
| 221 | </div> | |
| 222 | <div class="mc-card"> | |
| 223 | <div class="mc-head"> | |
| 224 | <span class="mc-title">By platform · last 30 days</span> | |
| 225 | <span class="mc-sub">Top: $rep_top_platform</span> | |
| 226 | </div> | |
| 227 | $chart_plat_html | |
| 228 | </div> | |
| 229 | </div> | |
| 230 | <script> | |
| 231 | (function () { | |
| 232 | document.querySelectorAll('.mc-card').forEach(function (card) { | |
| 233 | var svg = card.querySelector('.ac-svg'); | |
| 234 | var tip = card.querySelector('.ac-tip'); | |
| 235 | var guide= card.querySelector('.ac-guide'); | |
| 236 | var dot = card.querySelector('.ac-dot'); | |
| 237 | if (!svg || !tip) return; | |
| 238 | var vb = svg.viewBox.baseVal; | |
| 239 | function showAt(hot) { | |
| 240 | var x = parseFloat(hot.getAttribute('data-x')); | |
| 241 | var y = parseFloat(hot.getAttribute('data-y')); | |
| 242 | if (guide) { guide.setAttribute('x1', x); guide.setAttribute('x2', x); guide.setAttribute('opacity', 0.55); } | |
| 243 | if (dot) { dot.setAttribute('cx', x); dot.setAttribute('cy', y); dot.setAttribute('opacity', 1); } | |
| 244 | var rect = svg.getBoundingClientRect(); | |
| 245 | var domX = (x / vb.width) * rect.width; | |
| 246 | var domY = (y / vb.height) * rect.height; | |
| 247 | tip.style.left = domX + 'px'; | |
| 248 | tip.style.top = domY + 'px'; | |
| 249 | tip.innerHTML = '<strong>' + hot.getAttribute('data-value') + '</strong> on ' + hot.getAttribute('data-label'); | |
| 250 | tip.style.opacity = '1'; | |
| 251 | } | |
| 252 | function hide() { | |
| 253 | tip.style.opacity = '0'; | |
| 254 | if (guide) guide.setAttribute('opacity', 0); | |
| 255 | if (dot) dot.setAttribute('opacity', 0); | |
| 256 | } | |
| 257 | card.querySelectorAll('.ac-hot').forEach(function (hot) { | |
| 258 | hot.addEventListener('mouseenter', function () { showAt(hot); }); | |
| 259 | hot.addEventListener('mouseleave', hide); | |
| 260 | hot.addEventListener('click', function () { | |
| 261 | var iso = hot.getAttribute('data-iso'); | |
| 262 | if (iso) location.href = '/messages.cgi?day=' + encodeURIComponent(iso); | |
| 263 | }); | |
| 264 | }); | |
| 265 | }); | |
| 266 | })(); | |
| 267 | </script> | |
| 268 | [/if] | |
| 269 | [if:!$has_reports_data] | |
| 270 | <div style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:14px;padding:28px 24px;text-align:center;color:var(--col-text-2)"> | |
| 271 | <div style="font-size:14px;color:var(--col-text);font-weight:600;margin-bottom:6px">No data to chart yet</div> | |
| 272 | <div style="font-size:13px">Once comments and reviews start flowing in from your connected platforms, this page will show trends over time. <a href="/marketplaces.cgi" style="color:var(--col-accent-bright)">Connect a platform →</a></div> | |
| 273 | </div> | |
| 274 | [/if] | |
| 275 | [/if] | |
| 276 | ||
| 277 | [if:$mtab_inbox] | |
| 278 | [if:$has_messages] | |
| 279 | <style> | |
| 280 | /* Messages-page-only: the four filter tabs (All / Unread / Flagged / | |
| 281 | Archived) need to fit on a single line inside the ~340px list | |
| 282 | column. Tight padding + smaller font keeps every tab visible. */ | |
| 283 | .msg-tabs { flex-wrap: nowrap; gap: 2px; overflow-x: auto; } | |
| 284 | .msg-tabs .tab { | |
| 285 | padding: 6px 7px; | |
| 286 | font-size: 11px; | |
| 287 | white-space: nowrap; | |
| 288 | flex-shrink: 0; | |
| 289 | } | |
| 290 | .msg-tabs .tab .msg-count { | |
| 291 | margin-left: 3px; | |
| 292 | font-size: 10px; | |
| 293 | opacity: 0.75; | |
| 294 | } | |
| 295 | </style> | |
| 296 | <div class="dash-grid" style="grid-template-columns:340px 1fr;gap:16px"> | |
| 297 | ||
| 298 | <!-- ===== List ===== --> | |
| 299 | <div class="module" style="padding:0;overflow:hidden;align-self:flex-start"> | |
| 300 | <div class="module-head"> | |
| 301 | <div class="tabs msg-tabs" style="margin:0"> | |
| 302 | <a href="/messages.cgi?filter=all" class="tab[if:$is_filter_all] active[/if]" style="text-decoration:none">All<span class="msg-count">$count_all</span></a> | |
| 303 | <a href="/messages.cgi?filter=unread" class="tab[if:$is_filter_unread] active[/if]" style="text-decoration:none">Unread<span class="msg-count">$count_unread</span></a> | |
| 304 | <a href="/messages.cgi?filter=flagged" class="tab[if:$is_filter_flagged] active[/if]" style="text-decoration:none">Flagged<span class="msg-count">$count_flagged</span></a> | |
| 305 | <a href="/messages.cgi?filter=archived" class="tab[if:$is_filter_archived] active[/if]" style="text-decoration:none">Archived<span class="msg-count">$count_archived</span></a> | |
| 306 | </div> | |
| 307 | </div> | |
| 308 | <div style="display:flex;flex-direction:column;max-height:calc(100vh - 280px);overflow-y:auto"> | |
| 309 | [loop:@list_rows] | |
| 310 | <a href="$loop1.select_url" | |
| 311 | title="$loop1.author_safe -- $loop1.body_preview" | |
| 312 | style="display:flex;align-items:center;gap:8px;padding:7px 12px;border-bottom:1px solid var(--col-border);text-decoration:none;color:inherit;min-height:32px;[if:$loop1.is_selected]background:rgba(59,130,246,0.08);border-left:3px solid var(--col-accent);[/if][if:!$loop1.is_unread]opacity:0.78;[/if]"> | |
| 313 | <span class="pill $loop1.pill_class" style="font-size:9px;flex-shrink:0">$loop1.platform_label</span> | |
| 314 | [if:$loop1.is_flagged]<span class="pill danger" style="font-size:9px;flex-shrink:0">FLAG</span>[/if] | |
| 315 | <span class="text-sm fw-600" style="color:#fff;flex-shrink:0;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">$loop1.author_safe</span> | |
| 316 | <span class="text-xs text-secondary" style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">[if:$loop1.has_rating]<span style="color:var(--col-warning)">$loop1.rating_stars</span> [/if]$loop1.body_preview</span> | |
| 317 | <span class="text-xs text-dim" style="flex-shrink:0">$loop1.ago</span> | |
| 318 | </a> | |
| 319 | [/loop] | |
| 320 | </div> | |
| 321 | </div> | |
| 322 | ||
| 323 | <!-- ===== Detail ===== --> | |
| 324 | <div class="module"> | |
| 325 | [if:$has_selected] | |
| 326 | <div class="module-head"> | |
| 327 | <div class="left"> | |
| 328 | <div class="user-avatar" style="background:linear-gradient(130deg,#3b82f6,#7c3aed)">$selected.author_initials</div> | |
| 329 | <div> | |
| 330 | <div class="module-title">$selected.author_safe</div> | |
| 331 | <div class="module-sub">$selected.platform_label[if:$selected.has_rating] · <span style="color:var(--col-warning)">$selected.rating_stars</span>[/if]</div> | |
| 332 | </div> | |
| 333 | </div> | |
| 334 | <div class="flex gap-2"> | |
| 335 | [if:$selected.has_permalink]<a href="$selected.permalink" target="_blank" rel="noopener" class="icon-btn" title="Open on source platform"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></a>[/if] | |
| 336 | <form method="POST" action="/messages_action.cgi" style="display:inline"> | |
| 337 | <input type="hidden" name="action" value="flag"> | |
| 338 | <input type="hidden" name="id" value="$selected.id"> | |
| 339 | <input type="hidden" name="filter" value="$filter"> | |
| 340 | <button type="submit" class="icon-btn" title="Flag for follow-up"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1Z"/></svg></button> | |
| 341 | </form> | |
| 342 | <form method="POST" action="/messages_action.cgi" style="display:inline"> | |
| 343 | <input type="hidden" name="action" value="archive"> | |
| 344 | <input type="hidden" name="id" value="$selected.id"> | |
| 345 | <input type="hidden" name="filter" value="$filter"> | |
| 346 | <button type="submit" class="icon-btn" title="Archive"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/></svg></button> | |
| 347 | </form> | |
| 348 | </div> | |
| 349 | </div> | |
| 350 | <div class="module-body"> | |
| 351 | <div style="background:var(--col-surface-2);border:1px solid var(--col-border);border-radius:12px;padding:18px;margin-bottom:18px"> | |
| 352 | <div class="flex items-center gap-2 mb-2"> | |
| 353 | <span class="pill $selected.pill_class">$selected.platform_label</span> | |
| 354 | <span class="text-xs text-dim">$selected.occurred_fmt</span> | |
| 355 | [if:$selected.has_rating]<span style="margin-left:auto;color:var(--col-warning)">$selected.rating_stars</span>[/if] | |
| 356 | </div> | |
| 357 | <p style="color:#fff;line-height:1.7;font-size:15px;white-space:pre-wrap;margin:0">$selected.body_safe</p> | |
| 358 | </div> | |
| 359 | ||
| 360 | [if:$has_thread] | |
| 361 | <div class="text-xs text-dim mb-2" style="letter-spacing:1.5px;text-transform:uppercase">Your replies</div> | |
| 362 | [loop:@thread] | |
| 363 | <div style="background:rgba(124,58,237,0.08);border:1px solid rgba(124,58,237,0.30);border-radius:10px;padding:14px 16px;margin-bottom:10px"> | |
| 364 | <div class="flex items-center gap-2 mb-1" style="font-size:11px"> | |
| 365 | [if:$loop1.is_sent]<span class="pill success" style="font-size:9px">SENT</span>[/if] | |
| 366 | [if:$loop1.is_pending]<span class="pill warning" style="font-size:9px">SENDING</span>[/if] | |
| 367 | [if:$loop1.is_failed]<span class="pill danger" style="font-size:9px">FAILED</span>[/if] | |
| 368 | <span class="text-xs text-dim" style="margin-left:auto">$loop1.ago</span> | |
| 369 | </div> | |
| 370 | <div style="font-size:14px;line-height:1.6;color:#fff;white-space:pre-wrap">$loop1.body_safe</div> | |
| 371 | [if:$loop1.is_failed]<div class="text-xs" style="color:#fca5a5;margin-top:8px">Send failed -- retry from the Marketplaces page after fixing the connection.</div>[/if] | |
| 372 | </div> | |
| 373 | [/loop] | |
| 374 | [/if] | |
| 375 | ||
| 376 | <form method="POST" action="/messages_action.cgi"> | |
| 377 | <input type="hidden" name="action" value="reply"> | |
| 378 | <input type="hidden" name="id" value="$selected.id"> | |
| 379 | <input type="hidden" name="filter" value="$filter"> | |
| 380 | ||
| 381 | <div class="text-xs text-dim mb-2" style="letter-spacing:1.5px;text-transform:uppercase">Quick reply</div> | |
| 382 | <textarea class="textarea" rows="4" name="body" required placeholder="Type your reply... we will post it back to $selected.platform_label under your account."></textarea> | |
| 383 | ||
| 384 | <div class="flex gap-2 mt-3" style="flex-wrap:wrap"> | |
| 385 | <button type="button" class="btn btn-secondary btn-sm we-quick-reply" data-text="Thanks for the print!">Thanks for the print!</button> | |
| 386 | <button type="button" class="btn btn-secondary btn-sm we-quick-reply" data-text="Glad it worked out for you!">Glad it worked out for you!</button> | |
| 387 | <button type="button" class="btn btn-secondary btn-sm we-quick-reply" data-text="More like this coming soon -- stay tuned.">More like this coming soon</button> | |
| 388 | <button type="button" class="btn btn-secondary btn-sm we-quick-reply" data-text="Appreciate the feedback -- I will look into it.">Appreciate the feedback</button> | |
| 389 | </div> | |
| 390 | <div class="flex justify-between items-center mt-3" style="flex-wrap:wrap;gap:10px"> | |
| 391 | <span class="text-xs text-dim">Posts back to $selected.platform_label as <strong>$reply_handle</strong></span> | |
| 392 | <button type="submit" class="btn btn-primary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2 11 13"/><path d="m22 2-7 20-4-9-9-4 20-7Z"/></svg>Send</button> | |
| 393 | </div> | |
| 394 | </form> | |
| 395 | </div> | |
| 396 | [/if] | |
| 397 | ||
| 398 | [if:$no_selected] | |
| 399 | <div class="module-body" style="text-align:center;padding:60px 24px"> | |
| 400 | <div class="text-sm fw-600" style="color:#fff;margin-bottom:6px">No message selected</div> | |
| 401 | <div class="text-xs text-dim">Pick a thread on the left to read and reply.</div> | |
| 402 | </div> | |
| 403 | [/if] | |
| 404 | </div> | |
| 405 | </div> | |
| 406 | ||
| 407 | <script> | |
| 408 | // Quick-reply chips: insert their text into the reply textarea so | |
| 409 | // the seller can lightly customize before sending. | |
| 410 | document.querySelectorAll('.we-quick-reply').forEach(function(btn){ | |
| 411 | btn.addEventListener('click', function(){ | |
| 412 | var ta = btn.closest('form').querySelector('textarea[name=body]'); | |
| 413 | if (!ta) return; | |
| 414 | var cur = ta.value || ''; | |
| 415 | ta.value = cur ? (cur.replace(/\s+$/,'') + ' ' + btn.dataset.text) : btn.dataset.text; | |
| 416 | ta.focus(); | |
| 417 | }); | |
| 418 | }); | |
| 419 | </script> | |
| 420 | [/if] | |
| 421 | [/if] | |
| 422 | ||
| 423 | <!-- Trailing non-conditional content: MODS::Template returns eval's | |
| 424 | value, and a file ending in [/if] silently renders empty. --> | |
| 425 | <div style="height:0"></div> |