feng-x commited on
Commit
eecdb88
·
verified ·
1 Parent(s): 2d4b091

Upload folder using huggingface_hub

Browse files
AGENTS.md CHANGED
@@ -274,6 +274,10 @@ create table public.feedback (
274
  id uuid primary key default gen_random_uuid(),
275
  submitted_at timestamptz not null default now(),
276
  kol_email text not null, -- normalized join key
 
 
 
 
277
  received_size text, received_model text, best_fit_finger text,
278
  fit_quality text, hand text, photo_url text, notes text
279
  );
 
274
  id uuid primary key default gen_random_uuid(),
275
  submitted_at timestamptz not null default now(),
276
  kol_email text not null, -- normalized join key
277
+ kol_name text, -- also captured: legacy KOLs measured
278
+ -- pre-email-switch have kol_name (not
279
+ -- kol_email) on their measurements row,
280
+ -- so name lets their feedback join back
281
  received_size text, received_model text, best_fit_finger text,
282
  fit_quality text, hand text, photo_url text, notes text
283
  );
CLAUDE.md CHANGED
@@ -274,6 +274,10 @@ create table public.feedback (
274
  id uuid primary key default gen_random_uuid(),
275
  submitted_at timestamptz not null default now(),
276
  kol_email text not null, -- normalized join key
 
 
 
 
277
  received_size text, received_model text, best_fit_finger text,
278
  fit_quality text, hand text, photo_url text, notes text
279
  );
 
274
  id uuid primary key default gen_random_uuid(),
275
  submitted_at timestamptz not null default now(),
276
  kol_email text not null, -- normalized join key
277
+ kol_name text, -- also captured: legacy KOLs measured
278
+ -- pre-email-switch have kol_name (not
279
+ -- kol_email) on their measurements row,
280
+ -- so name lets their feedback join back
281
  received_size text, received_model text, best_fit_finger text,
282
  fit_quality text, hand text, photo_url text, notes text
283
  );
web_demo/app.py CHANGED
@@ -651,6 +651,14 @@ def api_fit_feedback():
651
  kol_email = (request.form.get("kol_email") or "").strip().lower()
652
  if not kol_email or "@" not in kol_email:
653
  return jsonify({"success": False, "error": "A valid email is required"}), 400
 
 
 
 
 
 
 
 
654
 
655
  def _field(name: str) -> Optional[str]:
656
  v = (request.form.get(name) or "").strip()
@@ -675,6 +683,7 @@ def api_fit_feedback():
675
 
676
  record = {
677
  "kol_email": kol_email,
 
678
  "received_size": _field("received_size"),
679
  "received_model": _field("received_model"),
680
  "best_fit_finger": _field("best_fit_finger"),
 
651
  kol_email = (request.form.get("kol_email") or "").strip().lower()
652
  if not kol_email or "@" not in kol_email:
653
  return jsonify({"success": False, "error": "A valid email is required"}), 400
654
+ # Name is also required: legacy KOLs were measured under the old
655
+ # name-based identity (before the email switch), so their measurements
656
+ # rows have kol_name but no kol_email. Capturing the name here lets their
657
+ # post-shipment feedback join back to those historical rows. See
658
+ # doc/v8/PRD.md.
659
+ kol_name = (request.form.get("kol_name") or "").strip()
660
+ if not kol_name:
661
+ return jsonify({"success": False, "error": "A name is required"}), 400
662
 
663
  def _field(name: str) -> Optional[str]:
664
  v = (request.form.get(name) or "").strip()
 
683
 
684
  record = {
685
  "kol_email": kol_email,
686
+ "kol_name": kol_name,
687
  "received_size": _field("received_size"),
688
  "received_model": _field("received_model"),
689
  "best_fit_finger": _field("best_fit_finger"),
web_demo/static/feedback/feedback.css CHANGED
@@ -32,7 +32,10 @@
32
  .controls input[type="file"] {
33
  width: 100%;
34
  font-size: 1rem;
35
- color: var(--ink-soft);
 
 
 
36
  }
37
 
38
  /* Enlarge the native "Choose File" button — iOS Safari renders it small
 
32
  .controls input[type="file"] {
33
  width: 100%;
34
  font-size: 1rem;
35
+ /* Hide the native status text ("No file chosen" / the picked filename)
36
+ it's redundant next to the labelled button. The button keeps its own
37
+ color (set on ::file-selector-button below), so it stays visible. */
38
+ color: transparent;
39
  }
40
 
41
  /* Enlarge the native "Choose File" button — iOS Safari renders it small
web_demo/static/feedback/feedback.js CHANGED
@@ -34,7 +34,7 @@ const REQUIRED = [
34
  ];
35
 
36
  // Every field that can produce a validation error, in DOM order.
37
- const FIELD_IDS = ["kolEmail", ...REQUIRED.map(([id]) => id)];
38
 
39
  // Inject an inline error slot under each validatable field and clear it
40
  // as soon as the user edits that field.
@@ -80,6 +80,9 @@ function showFormError(msg) {
80
 
81
  // Returns [fieldId, message] for the first invalid field, or null.
82
  function firstInvalid() {
 
 
 
83
  if (!EMAIL_RE.test(val("kolEmail").toLowerCase())) {
84
  return ["kolEmail", "Please enter a valid email."];
85
  }
@@ -107,6 +110,7 @@ form.addEventListener("submit", async (event) => {
107
  }
108
 
109
  const fd = new FormData();
 
110
  fd.append("kol_email", val("kolEmail").toLowerCase());
111
  fd.append("received_size", val("receivedSize"));
112
  fd.append("received_model", val("receivedModel"));
 
34
  ];
35
 
36
  // Every field that can produce a validation error, in DOM order.
37
+ const FIELD_IDS = ["kolName", "kolEmail", ...REQUIRED.map(([id]) => id)];
38
 
39
  // Inject an inline error slot under each validatable field and clear it
40
  // as soon as the user edits that field.
 
80
 
81
  // Returns [fieldId, message] for the first invalid field, or null.
82
  function firstInvalid() {
83
+ if (!val("kolName")) {
84
+ return ["kolName", "Please enter your name."];
85
+ }
86
  if (!EMAIL_RE.test(val("kolEmail").toLowerCase())) {
87
  return ["kolEmail", "Please enter a valid email."];
88
  }
 
110
  }
111
 
112
  const fd = new FormData();
113
+ fd.append("kol_name", val("kolName"));
114
  fd.append("kol_email", val("kolEmail").toLowerCase());
115
  fd.append("received_size", val("receivedSize"));
116
  fd.append("received_model", val("receivedModel"));
web_demo/templates/admin.html CHANGED
@@ -285,6 +285,7 @@
285
  <table>
286
  <thead>
287
  <tr>
 
288
  <th>Email</th>
289
  <th>Submitted</th>
290
  <th>Size</th>
@@ -298,7 +299,7 @@
298
  </tr>
299
  </thead>
300
  <tbody id="fbTableBody">
301
- <tr><td colspan="10" class="empty">Loading...</td></tr>
302
  </tbody>
303
  </table>
304
  </div>
@@ -526,6 +527,7 @@
526
  ? `<img class="thumb" loading="lazy" src="${esc(r.photo_url)}" onclick="window.open('${esc(r.photo_url)}')" />`
527
  : "-";
528
  return `<tr data-id="${esc(r.id)}">
 
529
  <td><strong>${esc(r.kol_email) || "-"}</strong></td>
530
  <td>${fmtDate(r.submitted_at)}</td>
531
  <td>${esc(r.received_size) || "-"}</td>
@@ -546,7 +548,7 @@
546
  if (fbCurrentPage < 1) fbCurrentPage = 1;
547
  if (total === 0) {
548
  fbCountLabel.textContent = "0 feedback rows";
549
- fbTableBody.innerHTML = '<tr><td colspan="10" class="empty">No feedback yet</td></tr>';
550
  } else {
551
  const start = (fbCurrentPage - 1) * PAGE_SIZE;
552
  const slice = fbAllRows.slice(start, start + PAGE_SIZE);
@@ -573,7 +575,7 @@
573
  fbCurrentPage = 1;
574
  renderFbPage();
575
  } catch (e) {
576
- fbTableBody.innerHTML = `<tr><td colspan="10" class="empty">Error loading feedback: ${e.message}</td></tr>`;
577
  }
578
  };
579
 
 
285
  <table>
286
  <thead>
287
  <tr>
288
+ <th>Name</th>
289
  <th>Email</th>
290
  <th>Submitted</th>
291
  <th>Size</th>
 
299
  </tr>
300
  </thead>
301
  <tbody id="fbTableBody">
302
+ <tr><td colspan="11" class="empty">Loading...</td></tr>
303
  </tbody>
304
  </table>
305
  </div>
 
527
  ? `<img class="thumb" loading="lazy" src="${esc(r.photo_url)}" onclick="window.open('${esc(r.photo_url)}')" />`
528
  : "-";
529
  return `<tr data-id="${esc(r.id)}">
530
+ <td>${esc(r.kol_name) || "-"}</td>
531
  <td><strong>${esc(r.kol_email) || "-"}</strong></td>
532
  <td>${fmtDate(r.submitted_at)}</td>
533
  <td>${esc(r.received_size) || "-"}</td>
 
548
  if (fbCurrentPage < 1) fbCurrentPage = 1;
549
  if (total === 0) {
550
  fbCountLabel.textContent = "0 feedback rows";
551
+ fbTableBody.innerHTML = '<tr><td colspan="11" class="empty">No feedback yet</td></tr>';
552
  } else {
553
  const start = (fbCurrentPage - 1) * PAGE_SIZE;
554
  const slice = fbAllRows.slice(start, start + PAGE_SIZE);
 
575
  fbCurrentPage = 1;
576
  renderFbPage();
577
  } catch (e) {
578
+ fbTableBody.innerHTML = `<tr><td colspan="11" class="empty">Error loading feedback: ${e.message}</td></tr>`;
579
  }
580
  };
581
 
web_demo/templates/feedback.html CHANGED
@@ -22,6 +22,17 @@
22
 
23
  <form class="panel" id="feedbackForm" novalidate>
24
  <div class="controls">
 
 
 
 
 
 
 
 
 
 
 
25
  <label>
26
  <span>Email</span>
27
  <input
 
22
 
23
  <form class="panel" id="feedbackForm" novalidate>
24
  <div class="controls">
25
+ <label>
26
+ <span>Name</span>
27
+ <input
28
+ type="text"
29
+ id="kolName"
30
+ placeholder="Your name"
31
+ autocomplete="name"
32
+ required
33
+ />
34
+ </label>
35
+
36
  <label>
37
  <span>Email</span>
38
  <input