PageSourceSearch

https://gba-speaker-demo-2026.netlify.app/assets/register.js

js gba-speaker-demo-2026.netlify.app collected 2026-10-03 10:49:23 UTC 7,619 bytes, 138 lines download raw bytes

1/* ============================================================================
2   register.js — 報名表控制器
3   平台四選一 / 加購切換 / 未成年→監護人同意 / 驗證 / 提交。
4   payload 欄位與 apps_script/register.gs 的 HEADERS 對應順序一致(紅線)。
5   ENDPOINT 留空 = 示範模式(即提交即出成功畫面);貼上 Apps Script Web App URL 即上線。
6   ========================================================================== */
7(function () {
8  "use strict";
9  var ENDPOINT = ""; // 貼上 Apps Script Web App URL 即上線;留空為示範模式
10  var $ = function (id) { return document.getElementById(id); };
11  var PLATFORM = { state: "YouTube" };
12  var addon = false;
13  var isEmail = function (v) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); };
14  function mark(el, bad) { if (el) el.classList.toggle("bad", bad); }
15
16  function notifyErr(m) {
17    var fe = $("formerr");
18    if (fe) { fe.textContent = m; fe.classList.add("show"); }
19    if (window.toast) window.toast(m, "err", { title: "未能提交", duration: 6000 });
20  }
21  function hideErr() { var fe = $("formerr"); if (fe) fe.classList.remove("show"); }
22
23  document.addEventListener("DOMContentLoaded", function () {
24    // platform picker
25    var seg = $("seg");
26    if (seg) seg.addEventListener("click", function (e) {
27      var b = e.target.closest("button[data-p]"); if (!b) return;
28      Array.prototype.forEach.call(seg.querySelectorAll("button"), function (x) { x.classList.remove("on"); });
29      b.classList.add("on"); PLATFORM.state = b.getAttribute("data-p");
30      var url = $("videoUrl"), file = $("videoFile"), lab = $("vlabel"), hint = $("vhint");
31      if (PLATFORM.state === "直接上傳") {
32        url.style.display = "none"; file.style.display = "block";
33        lab.innerHTML = '上傳演講影片<span class="req">*</span>';
34        hint.textContent = "選擇影片檔(建議 1–3 分鐘)。正式版會上載至雲端儲存;示範版只記錄檔名。";
35      } else {
36        url.style.display = "block"; file.style.display = "none";
37        lab.innerHTML = PLATFORM.state + ' 影片連結<span class="req">*</span>';
38        var ph = { YouTube: "https://youtu.be/...", Bilibili: "https://www.bilibili.com/video/...", "微信視頻號": "貼上視頻號分享連結 / 影片頁網址" };
39        url.placeholder = ph[PLATFORM.state] || "https://";
40        hint.textContent = PLATFORM.state === "微信視頻號" ? "貼上微信視頻號的分享連結或影片頁網址。" : "貼上影片連結(公開或不公開均可)。";
41      }
42    });
43
44    // plan addon toggle
45    var pa = $("plan-addon");
46    if (pa) pa.addEventListener("click", function () { addon = !addon; pa.classList.toggle("on", addon); });
47
48    // minor logic
49    function isMinor() {
50      var cat = $("category").value;
51      if (cat === "少兒組" || cat === "青年組") return true;
52      var dob = $("dob").value; if (!dob) return false;
53      var d = new Date(dob), n = new Date(), a = n.getFullYear() - d.getFullYear();
54      if (n.getMonth() < d.getMonth() || (n.getMonth() === d.getMonth() && n.getDate() < d.getDate())) a--;
55      return a < 18;
56    }
57    function refreshGuardian() {
58      var show = isMinor();
59      $("guardian-section").style.display = show ? "block" : "none";
60      var g = $("guardian"); if (g) g.classList.toggle("show", show);
61      if ($("consent-n")) $("consent-n").textContent = show ? "6" : "5";
62    }
63    if ($("category")) $("category").addEventListener("change", refreshGuardian);
64    if ($("dob")) $("dob").addEventListener("change", refreshGuardian);
65    window.__isMinor = isMinor;
66
67    // submit
68    var form = $("regform");
69    if (form) form.addEventListener("submit", function (e) {
70      e.preventDefault(); hideErr();
71      var errs = [];
72      [["name", "姓名"], ["dob", "出生日期"], ["category", "組別"], ["region", "所在地區"], ["email", "電郵"], ["phone", "聯絡電話"], ["title", "演講題目"]].forEach(function (p) {
73        var el = $(p[0]), bad = !el.value.trim(); mark(el, bad); if (bad) errs.push(p[1]);
74      });
75      if ($("email").value && !isEmail($("email").value)) { mark($("email"), true); errs.push("有效電郵"); }
76
77      var videoVal = "";
78      if (PLATFORM.state === "直接上傳") {
79        var f = $("videoFile").files[0]; if (!f) { mark($("videoFile"), true); errs.push("演講影片檔"); } else videoVal = f.name;
80      } else {
81        var u = $("videoUrl").value.trim(), bad = !u; mark($("videoUrl"), bad);
82        if (bad) errs.push("演講影片連結"); else videoVal = u;
83      }
84
85      if (isMinor()) {
86        [["gname", "家長姓名"], ["grel", "與參賽者關係"], ["gemail", "家長電郵"], ["gphone", "家長電話"]].forEac
86h(function (p) {
87          var el = $(p[0]), bad = !el.value.trim(); mark(el, bad); if (bad) errs.push(p[1]);
88        });
89        if ($("gemail").value && !isEmail($("gemail").value)) { mark($("gemail"), true); errs.push("有效家長電郵"); }
90        if (!$("gconsent").checked) errs.push("家長同意");
91      }
92      if (!$("consent1").checked) errs.push("AI 評估同意條款");
93
94      if (errs.length) { notifyErr("請完成以下必填/同意項目:" + errs.join("、") + "。"); window.scrollTo({ top: 0, behavior: "smooth" }); return; }
95
96      var payload = {
97        timestamp: new Date().toISOString(),
98        name: $("name").value.trim(), dob: $("dob").value, category: $("category").value, region: $("region").value,
99        email: $("email").value.trim(), phone: $("phone").value.trim(), wechat: $("wechat").value.trim(), org: $("org").value.trim(),
100        title: $("title").value.trim(), language: $("language").value,
101        videoPlatform: PLATFORM.state, video: videoVal, round: $("round").value,
102        plan: addon ? "標準初評 + 進階報告(加購)" : "標準初評",
103        minor: isMinor(),
104        guardianName: $("gname").value.trim(), guardianRelation: $("grel").value.trim(),
105        guardianEmail: $("gemail").value.trim(), guardianPhone: $("gphone").value.trim(),
106        consentAI: $("consent1").checked, consentContact: $("consent2").checked
107      };
108      var id = "GBA" + Date.now().toString().slice(-8);
109      var btn = $("submitBtn"); btn.disabled = true; btn.textContent = "提交中…";
110
111      var done = function () { succeed(id); };
112      var fail = function (err) { notifyErr("提交未能完成,請稍後再試。技術訊息:" + (err && err.message || err)); btn.disabled = false; btn.textContent = "提交報名"; };
113
114      if (ENDPOINT) {
115        fetch(ENDPOINT, { method: "POST", mode: "no-cors", headers: { "Content-Type": "text/plain;charset=utf-8" }, body: JSON.stringify(Object.assign({ id: id }, payload)) })
116          .then(done).catch(fail);
117      } else {
118        setTimeout(done, 700); // 示範模式
119      }
120    });
121
122    function succeed(id) {
123      if ($("okid")) $("okid").textContent = id;
124      if (form) form.style.display = "none";
125      var s = $("success"); if (s) s.classList.add("show");
126      if (window.toast) window.toast("報名已提交,報名編號 " + id, "ok", { title: "提交成功" });
127      window.scrollTo({ top: 0, behavior: "smooth" });
128    }
129    if ($("again")) $("again").addEventListener("click", function () {
130      var s = $("success"); if (s) s.classList.remove("show");
131      if (form) { form.reset(); form.style.display = "block"; }
132      refreshGuardian(); var btn = $("submitBtn"); btn.disabled = false; btn.textContent = "提交報名";
133      addon = false; if (pa) pa.classList.remove("on");
134    });
135
136    refreshGuardian();
137  });
138})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.