diff --git a/h5/hyapp/host-center/index.html b/h5/hyapp/host-center/index.html index 4d42d26..5300a60 100644 --- a/h5/hyapp/host-center/index.html +++ b/h5/hyapp/host-center/index.html @@ -4,7 +4,7 @@ Host Center - +
@@ -56,7 +56,7 @@
Join Agency
Search for an agency ID and submit your application.
- Join now +
@@ -124,6 +124,62 @@
+ +
- + diff --git a/h5/hyapp/host-center/script.js b/h5/hyapp/host-center/script.js index 69b117f..5567f20 100644 --- a/h5/hyapp/host-center/script.js +++ b/h5/hyapp/host-center/script.js @@ -29,6 +29,22 @@ join_agency_title: "Join Agency", join_agency_text: "Search for an agency ID and submit your application.", join_agency_action: "Join now", + agency_id: "Agency ID", + enter_agent_id: "Enter agency ID", + search: "Search", + searching: "Searching...", + agency_label: "Agency", + apply: "Apply", + applying: "Applying...", + found: "Agency found", + account_not_found: "Account not found or not an agency", + application_submitted: "Application submitted", + application_failed: "Application failed", + application_records: "Application Records", + pending: "Pending", + join_pending_note: "Application is pending. Please wait for agency approval.", + missing_token: "Missing token", + network_error: "Network error. Please try again.", withdraw_exchange: "Withdraw/Exchange", open_withdraw_exchange: "Open withdraw and exchange page", open_anchor_policy: "Open host policy", @@ -65,6 +81,22 @@ join_agency_title: "الانضمام إلى وكالة", join_agency_text: "ابحث عن معرّف وكالة وأرسل طلبك.", join_agency_action: "انضم الآن", + agency_id: "معرّف الوكالة", + enter_agent_id: "أدخل معرّف الوكالة", + search: "بحث", + searching: "جار البحث...", + agency_label: "الوكالة", + apply: "تقديم", + applying: "جار التقديم...", + found: "تم العثور على الوكالة", + account_not_found: "لم يتم العثور على الحساب أو ليس وكالة", + application_submitted: "تم إرسال الطلب", + application_failed: "فشل إرسال الطلب", + application_records: "سجلات الطلب", + pending: "قيد الانتظار", + join_pending_note: "الطلب قيد الانتظار. يرجى انتظار موافقة الوكالة.", + missing_token: "رمز الدخول مفقود", + network_error: "خطأ في الشبكة. حاول مرة أخرى.", withdraw_exchange: "سحب/استبدال", open_withdraw_exchange: "فتح صفحة السحب والاستبدال", open_anchor_policy: "فتح سياسة المضيف", @@ -101,6 +133,22 @@ join_agency_title: "Ajansa Katıl", join_agency_text: "Ajans ID ile arama yapıp başvurunu gönder.", join_agency_action: "Şimdi katıl", + agency_id: "Ajans ID", + enter_agent_id: "Ajans ID gir", + search: "Ara", + searching: "Aranıyor...", + agency_label: "Ajans", + apply: "Başvur", + applying: "Başvuruluyor...", + found: "Ajans bulundu", + account_not_found: "Hesap bulunamadı veya ajans değil", + application_submitted: "Başvuru gönderildi", + application_failed: "Başvuru başarısız", + application_records: "Başvuru Kayıtları", + pending: "Beklemede", + join_pending_note: "Başvuru beklemede. Lütfen ajans onayını bekleyin.", + missing_token: "Token eksik", + network_error: "Ağ hatası. Lütfen tekrar deneyin.", withdraw_exchange: "Çek/Değiştir", open_withdraw_exchange: "Çekme ve değişim sayfasını aç", open_anchor_policy: "Host politikasını aç", @@ -137,6 +185,22 @@ join_agency_title: "Gabung Agensi", join_agency_text: "Cari ID agensi dan kirim pengajuan Anda.", join_agency_action: "Gabung sekarang", + agency_id: "ID Agensi", + enter_agent_id: "Masukkan ID agensi", + search: "Cari", + searching: "Mencari...", + agency_label: "Agensi", + apply: "Ajukan", + applying: "Mengajukan...", + found: "Agensi ditemukan", + account_not_found: "Akun tidak ditemukan atau bukan agensi", + application_submitted: "Pengajuan terkirim", + application_failed: "Pengajuan gagal", + application_records: "Catatan Pengajuan", + pending: "Menunggu", + join_pending_note: "Pengajuan sedang menunggu. Harap tunggu persetujuan agensi.", + missing_token: "Token tidak ditemukan", + network_error: "Kesalahan jaringan. Silakan coba lagi.", withdraw_exchange: "Tarik/Tukar", open_withdraw_exchange: "Buka halaman tarik dan tukar", open_anchor_policy: "Buka kebijakan host", @@ -166,6 +230,8 @@ let currentStreamingDuration = "0"; let currentGiftValue = "0"; let currentDailyTargets = []; + let foundJoinAgency = null; + let pendingJoinRecord = null; function readURLParams() { const result = new URLSearchParams(window.location.search); @@ -223,21 +289,34 @@ return fallbackMessages[normalizedLang]; } + function localizedValue(messages, key) { + return messages?.[key] || fallbackMessages[currentLang]?.[key] || fallbackMessages.en[key] || ""; + } + + function message(key, fallback = "") { + return localizedValue(currentMessages, key) || fallback || key; + } + function renderMessages(messages) { document.querySelectorAll("[data-i18n]").forEach((node) => { - const value = messages[node.dataset.i18n]; + const value = localizedValue(messages, node.dataset.i18n); if (value) node.textContent = value; }); document.querySelectorAll("[data-i18n-html]").forEach((node) => { - const value = messages[node.dataset.i18nHtml]; + const value = localizedValue(messages, node.dataset.i18nHtml); if (value) node.innerHTML = value; }); document.querySelectorAll("[data-i18n-aria]").forEach((node) => { - const value = messages[node.dataset.i18nAria]; + const value = localizedValue(messages, node.dataset.i18nAria); if (value) node.setAttribute("aria-label", value); }); + + document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => { + const value = localizedValue(messages, node.dataset.i18nPlaceholder); + if (value) node.setAttribute("placeholder", value); + }); } function apiBaseURL() { @@ -264,6 +343,33 @@ }; } + async function requestJSON(path, options = {}) { + const token = readRawParam("token"); + if (!token) throw new Error(message("missing_token", "Missing token")); + + const headers = authHeadersFromToken(token); + const request = { + method: options.method || "GET", + cache: "no-store", + headers + }; + + if (options.body !== undefined) { + headers["Content-Type"] = "application/json"; + request.body = JSON.stringify(options.body); + } + + const response = await fetch(new URL(path, `${apiBaseURL()}/`).toString(), request); + const data = await response.json().catch(() => ({})); + if (!response.ok || data.status === false) { + const error = new Error(data.errorMsg || data.message || `Request failed: ${response.status}`); + error.response = data; + throw error; + } + + return data.body ?? data.data ?? null; + } + function profileUidPrefix(messages) { const template = messages.uid || fallbackMessages.en.uid; const marker = "1234567890"; @@ -313,6 +419,54 @@ syncPolicyLink(); } + function pickSpecialAccount(profile) { + return profile?.ownSpecialId?.account || + profile?.specialId?.account || + profile?.specialAccount || + profile?.shortAccount || + profile?.shortId || + ""; + } + + function pickJoinAgencyProfile(body) { + return body?.ownUserProfile || body?.userProfile || body?.teamProfile || body || null; + } + + function pickSearchAgency(body) { + const teamProfile = body?.teamProfile || null; + const userProfile = body?.ownUserProfile || body?.userProfile || teamProfile?.ownUserProfile || teamProfile?.userProfile || null; + if (!teamProfile?.id) return null; + + const specialAccount = pickSpecialAccount(userProfile) || pickSpecialAccount(teamProfile); + + return { + ...(teamProfile || {}), + ...(userProfile || {}), + id: teamProfile.id, + teamId: teamProfile.id, + teamName: teamProfile.teamName || userProfile?.teamName || "", + userNickname: userProfile?.userNickname || teamProfile.userNickname || teamProfile.name || teamProfile.teamName || "", + name: userProfile?.name || teamProfile.name || teamProfile.teamName || "", + userAvatar: userProfile?.userAvatar || teamProfile.userAvatar || userProfile?.avatar || teamProfile.avatar || "", + avatar: userProfile?.avatar || teamProfile.avatar || userProfile?.userAvatar || teamProfile.userAvatar || "", + displayAccount: specialAccount || userProfile?.account || teamProfile.account || "", + account: userProfile?.account || teamProfile.account || "", + ownSpecialId: userProfile?.ownSpecialId || teamProfile.ownSpecialId || (specialAccount ? { account: specialAccount } : null), + specialId: userProfile?.specialId || teamProfile.specialId || null, + specialAccount, + shortAccount: userProfile?.shortAccount || teamProfile.shortAccount || "", + shortId: userProfile?.shortId || teamProfile.shortId || "" + }; + } + + function pickJoinAgencyName(profile) { + return profile?.userNickname || profile?.name || profile?.teamName || "-"; + } + + function pickJoinAgencyAccount(profile) { + return pickSpecialAccount(profile) || profile?.displayAccount || profile?.account || profile?.id || "-"; + } + function pickAgencyTeamId(profile) { return profile?.teamId || profile?.teamProfile?.id || profile?.id || ""; } @@ -443,22 +597,6 @@ const agencyButton = document.querySelector(".agency"); if (agencyButton) agencyButton.hidden = showJoinAgency; - - syncJoinAgencyLink(); - } - - function buildPageURL(path) { - const url = new URL(path, window.location.href); - const nextParams = new URLSearchParams(params); - const token = readRawParam("token"); - if (token) nextParams.set("token", token); - if (currentLang) nextParams.set("lang", currentLang); - url.search = nextParams.toString(); - return url.toString(); - } - - function openJoinAgencyPage() { - window.location.replace(buildPageURL("../join-agency/index.html")); } function closeHostCenterPage() { @@ -604,6 +742,11 @@ replaceNodeChildren(list, currentDailyTargets.map(createDailyRow)); } + function updateModalBodyLock() { + const hasOpenModal = Array.from(document.querySelectorAll(".daily-modal, .join-agency-modal")).some((modal) => !modal.hidden); + document.body.classList.toggle("daily-modal-open", hasOpenModal); + } + function renderMemberWorkSummary(memberWork) { const workTarget = pickFirstMemberWorkTarget(memberWork); renderStreamingDuration(pickStreamingDuration(memberWork)); @@ -615,10 +758,204 @@ const modal = document.querySelector("#dailyModal"); if (!modal) return; modal.hidden = !open; - document.body.classList.toggle("daily-modal-open", open); + updateModalBodyLock(); if (open) renderDailyTargets(); } + function showJoinAgencyStatus(text, type = "") { + const node = document.querySelector("#joinAgencyStatus"); + if (!node) return; + + node.textContent = text || ""; + node.hidden = !text; + node.classList.toggle("is-error", type === "error"); + node.classList.toggle("is-success", type === "success"); + } + + function renderJoinAgencyAvatar(profile, imageSelector, fallbackSelector) { + const image = document.querySelector(imageSelector); + const fallback = document.querySelector(fallbackSelector); + if (!image || !fallback) return; + + const name = pickJoinAgencyName(profile); + const avatar = profile?.userAvatar || profile?.avatar || ""; + fallback.textContent = String(name || "A").slice(0, 1).toUpperCase() || "A"; + image.alt = name; + + if (!avatar) { + image.hidden = true; + image.removeAttribute("src"); + fallback.hidden = false; + return; + } + + fallback.hidden = true; + image.hidden = false; + image.onerror = () => { + image.hidden = true; + image.removeAttribute("src"); + fallback.hidden = false; + }; + image.src = avatar; + } + + function syncJoinAgencyApplyAvailability() { + const button = document.querySelector("#joinAgencyApplyButton"); + if (button) button.hidden = Boolean(pendingJoinRecord) || !foundJoinAgency?.id; + } + + function renderFoundJoinAgency(profile = foundJoinAgency) { + const card = document.querySelector("#joinAgencyResult"); + if (!card) return; + + if (!profile) { + card.hidden = true; + syncJoinAgencyApplyAvailability(); + return; + } + + card.hidden = false; + const nameNode = document.querySelector("#joinAgencyName"); + const accountNode = document.querySelector("#joinAgencyAccountText"); + if (nameNode) nameNode.textContent = pickJoinAgencyName(profile); + if (accountNode) accountNode.textContent = `${profileUidPrefix(currentMessages)} ${pickJoinAgencyAccount(profile)}`.trim(); + renderJoinAgencyAvatar(profile, "#joinAgencyAvatar", "#joinAgencyAvatarFallback"); + syncJoinAgencyApplyAvailability(); + } + + function renderPendingJoinRecord(record = pendingJoinRecord) { + const card = document.querySelector("#joinAgencyPending"); + if (!card) return; + + if (!record) { + card.hidden = true; + syncJoinAgencyApplyAvailability(); + return; + } + + const profile = pickJoinAgencyProfile(record); + card.hidden = false; + const nameNode = document.querySelector("#joinAgencyPendingName"); + const accountNode = document.querySelector("#joinAgencyPendingAccount"); + if (nameNode) nameNode.textContent = pickJoinAgencyName(profile); + if (accountNode) accountNode.textContent = `${profileUidPrefix(currentMessages)} ${pickJoinAgencyAccount(profile)}`.trim(); + renderJoinAgencyAvatar(profile, "#joinAgencyPendingAvatar", "#joinAgencyPendingAvatarFallback"); + syncJoinAgencyApplyAvailability(); + } + + function joinAgencyErrorMessage(error, fallbackKey) { + return error?.response?.errorMsg || error?.response?.message || error?.message || message(fallbackKey); + } + + async function fetchPendingJoinRecord() { + try { + pendingJoinRecord = await requestJSON("/team/wait-apply/profile"); + renderPendingJoinRecord(pendingJoinRecord); + return pendingJoinRecord; + } catch (error) { + pendingJoinRecord = null; + renderPendingJoinRecord(null); + if (error.message === message("missing_token")) showJoinAgencyStatus(error.message, "error"); + return null; + } + } + + async function searchJoinAgency(event) { + event?.preventDefault(); + const input = document.querySelector("#joinAgencyAccount"); + const button = document.querySelector("#joinAgencySearchButton"); + const account = String(input?.value || "").trim(); + + if (!account) { + showJoinAgencyStatus(message("enter_agent_id"), "error"); + return; + } + + foundJoinAgency = null; + renderFoundJoinAgency(null); + if (button) { + button.disabled = true; + button.textContent = message("searching"); + } + showJoinAgencyStatus(""); + + try { + const body = await requestJSON(`/team/search-account?account=${encodeURIComponent(account)}`); + const profile = pickSearchAgency(body); + if (!profile?.id) { + showJoinAgencyStatus(message("account_not_found"), "error"); + return; + } + + foundJoinAgency = profile; + renderFoundJoinAgency(foundJoinAgency); + showJoinAgencyStatus(message("found"), "success"); + } catch (error) { + foundJoinAgency = null; + renderFoundJoinAgency(null); + showJoinAgencyStatus(joinAgencyErrorMessage(error, "network_error"), "error"); + } finally { + if (button) { + button.disabled = false; + button.textContent = message("search"); + } + } + } + + async function applyToJoinAgency() { + if (pendingJoinRecord || !foundJoinAgency?.id) return; + + const button = document.querySelector("#joinAgencyApplyButton"); + if (button) { + button.disabled = true; + button.textContent = message("applying"); + } + showJoinAgencyStatus(""); + + try { + const appliedAgency = foundJoinAgency; + await requestJSON("/team/user/apply", { + method: "POST", + body: { + teamId: appliedAgency.id, + reason: "JOIN" + } + }); + + foundJoinAgency = null; + renderFoundJoinAgency(null); + showJoinAgencyStatus(message("application_submitted"), "success"); + const pending = await fetchPendingJoinRecord(); + if (!pending) { + pendingJoinRecord = { ownUserProfile: appliedAgency }; + renderPendingJoinRecord(pendingJoinRecord); + } + } catch (error) { + showJoinAgencyStatus(joinAgencyErrorMessage(error, "application_failed"), "error"); + } finally { + if (button) { + button.disabled = false; + button.textContent = message("apply"); + } + syncJoinAgencyApplyAvailability(); + } + } + + function setJoinAgencyModalOpen(open) { + const modal = document.querySelector("#joinAgencyModal"); + if (!modal) return; + + modal.hidden = !open; + updateModalBodyLock(); + if (!open) return; + + showJoinAgencyStatus(""); + fetchPendingJoinRecord(); + window.setTimeout(() => { + document.querySelector("#joinAgencyAccount")?.focus(); + }, 0); + } + async function fetchUserProfile() { const token = readRawParam("token"); if (!token) { @@ -806,21 +1143,6 @@ document.querySelectorAll('a[href^="../withdraw-exchange/index.html"]').forEach((link) => { link.setAttribute("href", `../withdraw-exchange/index.html${query ? `?${query}` : ""}`); }); - - syncJoinAgencyLink(); - } - - function syncJoinAgencyLink() { - const link = document.querySelector("#joinAgencyLink"); - if (!link) return; - - const nextParams = new URLSearchParams(params); - const token = readRawParam("token"); - if (token) nextParams.set("token", token); - if (currentLang) nextParams.set("lang", currentLang); - - const query = nextParams.toString(); - link.setAttribute("href", `../join-agency/index.html${query ? `?${query}` : ""}`); } function syncPolicyLink() { @@ -876,6 +1198,8 @@ renderProfile(currentProfile, messages); renderAgencyProfile(currentAgencyProfile, messages); renderDailyTargets(); + renderFoundJoinAgency(foundJoinAgency); + renderPendingJoinRecord(pendingJoinRecord); } document.querySelector(".language-button")?.addEventListener("click", (event) => { @@ -901,7 +1225,23 @@ }); document.querySelector(".agency")?.addEventListener("click", () => { - if (currentIdentity && shouldOpenJoinAgency(currentIdentity)) openJoinAgencyPage(); + if (currentIdentity && shouldOpenJoinAgency(currentIdentity)) setJoinAgencyModalOpen(true); + }); + + document.querySelector("#joinAgencyButton")?.addEventListener("click", () => { + setJoinAgencyModalOpen(true); + }); + + document.querySelector("#joinAgencySearchForm")?.addEventListener("submit", searchJoinAgency); + + document.querySelector("#joinAgencyApplyButton")?.addEventListener("click", () => { + applyToJoinAgency(); + }); + + document.querySelectorAll("[data-close-join-agency]").forEach((node) => { + node.addEventListener("click", () => { + setJoinAgencyModalOpen(false); + }); }); document.querySelector(".gift-value")?.addEventListener("click", () => { @@ -915,7 +1255,10 @@ }); document.addEventListener("keydown", (event) => { - if (event.key === "Escape") setDailyModalOpen(false); + if (event.key === "Escape") { + setDailyModalOpen(false); + setJoinAgencyModalOpen(false); + } }); ensureAvatarFallback("Oscar"); diff --git a/h5/hyapp/host-center/style.css b/h5/hyapp/host-center/style.css index 2024ea3..0830629 100644 --- a/h5/hyapp/host-center/style.css +++ b/h5/hyapp/host-center/style.css @@ -1098,6 +1098,254 @@ a { text-align: center; } +.join-agency-modal[hidden] { + display: none; +} + +.join-agency-modal { + position: fixed; + z-index: 24; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 18px; +} + +.join-agency-backdrop { + position: absolute; + inset: 0; + background: rgba(15, 21, 28, 0.38); +} + +.join-agency-dialog { + position: relative; + display: flex; + flex-direction: column; + width: min(394px, 100%); + max-height: 78vh; + max-height: 78dvh; + overflow: hidden; + border-radius: 14px; + background: #fff; + box-shadow: 0 18px 44px rgba(23, 31, 42, 0.2); +} + +.join-agency-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 58px; + padding: 16px 18px 12px; + border-bottom: 1px solid #eef0f2; +} + +.join-agency-head h2 { + margin: 0; + color: #24282e; + font-size: 18px; + font-weight: 950; + line-height: 1.2; +} + +.join-agency-close { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 50%; + background: #f4f6f7; + color: #50545b; + font-size: 25px; + font-weight: 500; + line-height: 1; +} + +.join-agency-modal-body { + display: grid; + flex: 1 1 auto; + gap: 12px; + min-height: 0; + overflow-y: auto; + padding: 16px 18px max(18px, env(safe-area-inset-bottom)); + -webkit-overflow-scrolling: touch; +} + +.join-agency-field { + display: grid; + gap: 10px; +} + +.join-agency-field > span, +.join-agency-section-title { + color: #24282e; + font-size: 16px; + font-weight: 900; + line-height: 1.2; +} + +.join-agency-search-control { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; +} + +.join-agency-search-control input { + width: 100%; + height: 44px; + min-width: 0; + border: 1px solid #eef0f2; + border-radius: 8px; + outline: none; + background: #f8fafa; + color: #25282e; + font: inherit; + font-size: 15px; + font-weight: 750; + padding: 0 12px; +} + +.join-agency-search-control input::placeholder { + color: #b3b5ba; +} + +.join-agency-search-button, +.join-agency-apply-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + border-radius: 8px; + font-size: 15px; + font-weight: 900; +} + +.join-agency-search-button { + min-width: 86px; + padding: 0 16px; + background: #31d9cb; + color: #fff; +} + +.join-agency-search-button:disabled, +.join-agency-apply-button:disabled { + opacity: 0.58; +} + +.join-agency-status { + min-height: 38px; + border-radius: 8px; + padding: 10px 12px; + background: #f8fafa; + color: #8b8d92; + font-size: 13px; + font-weight: 800; + line-height: 1.3; +} + +.join-agency-status.is-error { + color: #d64f4f; +} + +.join-agency-status.is-success { + color: #0d988d; +} + +.join-agency-modal-card { + display: grid; + gap: 14px; + padding: 14px; + border: 1px solid #eef0f2; + border-radius: 8px; + background: #fbfcfc; +} + +.join-agency-profile-row { + display: flex; + align-items: center; + gap: 14px; + min-width: 0; +} + +.join-agency-avatar { + display: flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + flex: 0 0 56px; + overflow: hidden; + border-radius: 50%; + background: linear-gradient(145deg, #d7fff9, #45e7d8); + color: #0c5f58; + font-size: 21px; + font-weight: 950; +} + +.join-agency-avatar img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + +.join-agency-profile-copy { + min-width: 0; + flex: 1; +} + +.join-agency-profile-name { + overflow: hidden; + color: #25282e; + font-size: 18px; + font-weight: 900; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.join-agency-profile-account { + margin-top: 6px; + overflow: hidden; + color: #8b8d92; + font-size: 13px; + font-weight: 800; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.join-agency-apply-button { + width: 100%; + background: linear-gradient(135deg, #35dfd2 0%, #18bfb4 100%); + color: #fff; +} + +.join-agency-pending-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.join-agency-pending-head span { + flex: 0 0 auto; + color: #f2a329; + font-size: 13px; + font-weight: 900; +} + +.join-agency-pending-note { + margin: 0; + color: #8b8d92; + font-size: 13px; + font-weight: 800; + line-height: 1.35; +} + .daily-modal-open { overflow: hidden; } @@ -1163,6 +1411,21 @@ a { direction: rtl; } +[dir="rtl"] .join-agency-profile-copy, +[dir="rtl"] .join-agency-field { + text-align: right; +} + +@media (max-width: 360px) { + .join-agency-search-control { + grid-template-columns: 1fr; + } + + .join-agency-search-button { + width: 100%; + } +} + @media (max-width: 360px) { .status-bar { padding-right: 22px;