Submit a TYFCB or One-to-One Slip

Site bniconnectglobal.comTask submit-tyfcb-slipVersion v20Updated Aug 13, 2026Category browser-automation

Fill or submit authenticated TYFCB and One-to-One slips in BNI Connect Global using context-qualified controls, exact Material UI option matching, React-safe field updates, and post-action verification. This skill was captured from a live agent session on bniconnectglobal.com and publishes here verbatim, exactly as an agent receives it.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Purpose

Fill or submit an authenticated TYFCB or One-to-One slip on BNI Connect Global. Support placeholders {member-name}, {inviter-name}, {location}, {date}, and {topic} for One-to-One entries. Fill-only requests must stop after verification; submit only when the caller explicitly requests submission.

When to Use

Use for TYFCB or One-to-One form inspection, filling, or confirmed submission. Do not use for internal referrals, cross-chapter member lookup, or reviewing existing slips.

Workflow

  1. If authentication is required, navigate to https://www.bniconnectglobal.com/login, fill input[name="username"] and input[name="password"] with runtime credentials, activate button[type="submit"], and wait for the authenticated /web/ portal. Never retain credentials or expose transient ;jsessionid values.
  2. No stable direct form URL is known. On the authenticated dashboard, for One-to-One activate only the visible exact 提交 control whose nearby context matches 一對一會面, 一對一, 一对一, One-to-One, or 1-2-1, excluding 提供引薦 and 分會教育. For TYFCB, activate the visible Submit TYFCB control qualified by referral/TYFCB context. Wait for the rendered form or div[role="dialog"].
  3. For One-to-One, open the member .MuiSelect-select or [role="combobox"] associated with member/recipient by dispatching bubbling mousedown and calling click(). Wait for ul[role="listbox"] > li[role="option"], then run the evaluator below with {member-name} and select=true. Select only an enabled option whose normalized visible text exactly matches the requested name.
  4. Reopen the current inviter select identified by 邀請人, 邀請者, Inviter, or invite, wait for its current listbox options, and run the same evaluator with {inviter-name} and select=true. Do not assume member and inviter options share a list or ordinal.
  5. Run the One-to-One fill-and-verify evaluator below with {member-name}, {inviter-name}, {location}, {date}, and {topic}. It identifies fields by labels/context, uses React-safe native setters for location/date/topic, and verifies the actual values. Resolve “today” at runtime and verify the resulting date value rather than assuming a format.
  6. For fill-only requests, stop at allMatch:true and do not activate Save, Submit, or another action control. For an explicitly confirmed submission, re-run verification, qualify an enabled action control within the same verified form, activate it, wait for visible success/result evidence, and run the submission-result extractor. Dialog closure or navigation alone is insufficient proof.
  7. For TYFCB, use the same exact enabled listbox selection technique, then fill and verify {recipient-name}, {amount}, {classification}, {status}, and optional {comment} before any explicitly confirmed submission.

Exact option evaluator:

((wantedName = "", select = false) => {
  const v = (e) =>
      !!e && (e.offsetWidth || e.offsetHeight || e.getClientRects().length),
    c = (s) => (s || "").replace(/\\s+/g, " ").trim(),
    q = c(wantedName).toLocaleLowerCase(),
    els = [
      ...document.querySelectorAll('ul[role="listbox"] > li[role="option"]'),
    ].filter(v),
    opts = els.map((e, i) => ({
      index: i,
      text: c(e.innerText || e.textContent),
      value: e.getAttribute("data-value") || e.getAttribute("value") || null,
      disabled:
        e.getAttribute("aria-disabled") === "true" ||
        e.classList.contains("Mui-disabled"),
      selected: e.getAttribute("aria-selected") === "true",
    })),
    m = opts.filter((x) => x.text.toLocaleLowerCase() === q),
    chosen = m.find((x) => !x.disabled);
  if (select && chosen) els[chosen.index].click();
  return {
    query: wantedName,
    optionCount: opts.length,
    matches: m,
    selected: !!(select && chosen),
    reason: select && !chosen ? "exact enabled option not found" : null,
  };
})("{name}", true);

One-to-One fill-and-verify evaluator:

((expected = {}) => {
  const c = (s) => (s || "").replace(/\\s+/g, " ").trim(),
    v = (e) => !!e && (e.offsetWidth || e.offsetHeight || e.getClientRects().length),
    r = document.querySelector('div[role="dialog"]') || document,
    els = [
      ...r.querySelectorAll('input,textarea,[role="combobox"],.MuiSelect-select'),
    ].filter(v),
    ctx = (e) =>
      c(
        e.closest(".MuiFormControl-root,.MuiFormControl,fieldset,form,div")
          ?.innerText || "",
      ),
    allText = (e) =>
      [
        e.getAttribute("aria-label"),
        e.getAttribute("name"),
        e.getAttribute("placeholder"),
        ctx(e),
      ]
        .filter(Boolean)
        .join(" "),
    set = (e, x) => {
      if (!e) return;
      const p =
          e.tagName === "TEXTAREA"
            ? HTMLTextAreaElement.prototype
            : HTMLInputElement.prototype,
        s = Object.getOwnPropertyDescriptor(p, "value")?.set;
      s ? s.call(e, x) : (e.value = x);
      e.dispatchEvent(new Event("input", { bubbles: true }));
      e.dispatchEvent(new Event("change", { bubbles: true }));
    },
    find = (re) => els.find((e) => re.test(allText(e))),
    val = (e) => c(e?.value ?? e?.innerText ?? e?.textContent),
    member =
      find(/member|recipient|會員|成員|對象|對方/i) ||
      els.find((e) => e.matches('.MuiSelect-select,[role="combobox"]')),
    inviter = find(/inviter|invite|邀請人|邀請者/i),
    location = find(/location|地點|場所/i),
    date =
      find(/date|日期|時間/i) ||
      els.find((e) => e.tagName === "INPUT" && /20\d\d/.test(e.value)),
    topic =
      find(/topic|主題|內容|交流/i) || [...r.querySelectorAll("textarea")].find(v),
    locFallback = [...r.querySelectorAll("input.MuiOutlinedInput-input")].find(
      (e) => !location && !/20\d\d/.test(e.value),
    );
  if (expected.location != null) set(location || locFallback, expected.location);
  if (expected.date != null) set(date, expected.date);
  if (expected.topic != null) set(topic, expected.topic);
  const actual = {
      member: val(member) || null,
      inviter: val(inviter) || null,
      location: val(location || locFallback) || null,
      date: val(date) || null,
      topic: val(topic) || null,
    },
    matches = Object.fromEntries(
      Object.entries(expected)
        .filter(([, x]) => x != null)
        .map(([k, x]) => [k, actual[k] === c(x)]),
    );
  return {
    expected,
    actual,
    matches,
    allMatch: Object.values(matches).every(Boolean),
    fields: {
      member: !!member,
      inviter: !!inviter,
      location: !!(location || locFallback),
      date: !!date,
      topic: !!topic,
    },
    formText: c(r.innerText || r.textContent).slice(0, 3000),
  };
})({
  member: "{member-name}",
  inviter: "{inviter-name}",
  location: "{location}",
  date: "{date}",
  topic: "{topic}",
});

Submission-result extractor:

(() => {
  const c = (s) => (s || "").replace(/\\s+/g, " ").trim(),
    v = (e) => !!e && (e.offsetWidth || e.offsetHeight || e.getClientRects().length),
    t = c(document.body?.innerText || document.body?.textContent),
    d = !!document.querySelector('div[role="dialog"]'),
    success =
      /success|submitted|saved|completed|成功|已提交|提交成功|儲存成功|保存成功/i.test(
        t,
      ),
    controls = [...document.querySelectorAll('button,a,[role="button"]')]
      .filter(v)
      .map((e) => ({
        label: c(e.innerText || e.textContent || e.getAttribute("aria-label")),
        href: e.getAttribute("href"),
        disabled: !!e.disabled,
      }));
  return {
    url: location.href,
    dialogPresent: d,
    modalGone: !d,
    success,
    visibleText: t.slice(0, 4000),
    controls,
  };
})();

Site-Specific Gotchas

  • One-to-One and TYFCB forms have no stable direct URL in the observed installation; use authenticated dashboard controls.
  • The dashboard contains multiple 提交 controls. Qualify One-to-One by nearby One-to-One text and exclude internal-referral and chapter-education contexts.
  • Material UI options render only after bubbling mousedown plus click, followed by a wait for ul[role="listbox"] > li[role="option"].
  • Match options by exact normalized visible text and reject disabled options; ordinals vary by session.
  • Member and inviter lists may refresh independently; reopen the inviter list after member selection and wait for its current options.
  • One-to-One location and date are commonly input.MuiOutlinedInput-input; identify them by labels first. A year-like value is only a date fallback, and a non-year-like value is only a location fallback.
  • React-controlled fields require the native prototype value setter plus bubbling input and change events.
  • Do not use positional selectors for the final action. Qualify the enabled action within the verified form and verify visible success evidence afterward.
  • Authentication may append a transient ;jsessionid=...; preserve the session but never hard-code or expose it. Keep credentials, cookies, and session URLs runtime-only.

Expected Output

Return form-open state, option counts, exact selected member/inviter matches, identified fields, requested and actual values, matches, and allMatch. For fill-only requests, state that no submission was attempted. For confirmed submissions, include the qualified action and verified post-submit success evidence. Never claim submission based only on a click, modal closure, or navigation.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=bniconnectglobal.com&task=submit-tyfcb-slip