1/** 2 * My Profile workspace â desktop-adapted iOS Me tab (Glow Up | Receipts). 3 * Peer `/app/profile/:id` is handled by FruitiPeerProfile (full iOS content parity). 4 */ 5(function (global) { 6 "use strict"; 7 8 var abort = null; 9 var photoIndex = 0; 10 var activeTab = "glow"; 11 var temporal = "present"; 12 var profileCache = null; 13 var shellCtx = null; 14 var meRoot = null; 15 var bodyHostRef = null; 16 var tabsHostRef = null; 17 var incognitoHostRef = null; 18 var premium = false; 19 var subStatus = null; 20 var incognitoOn = false; 21 var glowState = { 22 viewers: null, 23 viewersError: null, 24 tiles: { historic: null, present: null, future: null }, 25 tilesError: null, 26 }; 27 28 var INCOGNITO_ON = [ 29 "You're a ghost now. ð» No one will see you creeping.", 30 "Incognito on. *poof* You've vanished.", 31 "Nobody can see you. Perfect for that deep dive.", 32 "You're invisible. Use your powers wisely.", 33 ]; 34 var INCOGNITO_OFF = [ 35 "You're back. The spotlight missed you.", 36 "Incognito off. You're visible again. Wave hello! ð", 37 "You've emerged from the shadows. Hello, world.", 38 ]; 39 40 function stop() { 41 if (abort) abort.abort(); 42 abort = null; 43 if (global.FruitiPeerProfile && FruitiPeerProfile.stop) FruitiPeerProfile.stop(); 44 var lb = document.getElementById("me-lightbox"); 45 if (lb) lb.remove(); 46 var plan = document.getElementById("me-plan-host"); 47 if (plan) plan.remove(); 48 } 49 50 function pick(arr) { 51 return arr[Math.floor(Math.random() * arr.length)] || arr[0]; 52 } 53 54 function meIcon(name) { 55 var icons = { 56 camera: 57 '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.25" y="5.25" width="17.5" height="14" rx="3"/><circle cx="9" cy="10" r="1.5"/><path d="m4 17 4.2-4.2a2 2 0 0 1 2.8 0l1.6 1.6 1-1a2 2 0 0 1 2.8 0L20 17M17.5 3v4M15.5 5h4"/></svg>', 58 edit: 59 '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M21.17 6.81a1 1 0 0 0-3.98-3.98L3.84 16.17a2 2 0 0 0-.5.83l-1.32 4.36a.5.5 0 0 0 .62.62L7 20.66a2 2 0 0 0 .83-.5z"/><path d="m15 5 4 4"/></svg>', 60 verify: 61 '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg>', 62 // Incognito uses an eye: open = visible, slashed = hidden (active). 63 eye: '<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg>', 64 eyeOff: 65 '<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10.7 5.1A10.8 10.8 0 0 1 12 5c6.5 0 10 7 10 7a18.4 18.4 0 0 1-2.2 3.2"/><path d="M6.6 6.6C3.9 8.5 2 12 2 12s3.5 7 10 7a10.4 10.4 0 0 0 5.4-1.5"/><path d="M14.1 14.1A3 3 0 0 1 9.9 9.9"/><path d="m2 2 20 20"/></svg>', 66 }; 67 var icon = FruitiUI.el("span", "me-action-icon"); 68 icon.innerHTML = icons[name] || ""; 69 return icon; 70 } 71 72 function configureIconButton(button, label, iconName) { 73 button.setAttribute("aria-label", label); 74 button.title = label; 75 button.appendChild(meIcon(iconName)); 76 } 77 78 function openDialog(opts) { 79 var existing = document.getElementById("me-dialog"); 80 if (existing) existing.remove(); 81 var host = FruitiUI.el("div", "settings-dialog"); 82 host.id = "me-dialog"; 83 host.setAttribute("role", "dialog"); 84 host.setAttribute("aria-modal", "true"); 85 var panel = FruitiUI.el("div", "settings-dialog__panel"); 86 panel.appendChild(FruitiUI.el("h2", "settings-dialog__title", opts.title)); 87 if (opts.body) panel.appendChild(FruitiUI.el("p", "settings-dialog__body", opts.body)); 88 var foot = FruitiUI.el("div", "settings-dialog__foot"); 89 function close() { 90 if (host.parentNode) host.parentNode.removeChild(host); 91 } 92 if (opts.cancelLabel !== false) { 93 var cancel = FruitiUI.el( 94 "button", 95 "settings-dialog__btn settings-dialog__btn--ghost", 96 opts.cancelLabel || "Cancel" 97 ); 98 cancel.type = "button"; 99 cancel.addEventListener("click", function () { 100 close(); 101 if (opts.onCancel) opts.onCancel(); 102 }); 103 foot.appendChild(cancel); 104 } 105 var confirm = FruitiUI.el( 106 "button", 107 "settings-dialog__btn settings-dialog__btn--" + (opts.destructive ? "danger" : "primary"), 108 opts.confirmLabel || "OK" 109 ); 110 confirm.type = "button"; 111 confirm.addEventListener("click", function () { 112 if (opts.onConfirm) opts.onConfirm(close, confirm); 113 else close(); 114 }); 115 foot.appendChild(confirm); 116 panel.appendChild(foot); 117 host.appendChild(panel); 118 host.addEventListener("click", function (e) { 119 if (e.target === host) close(); 120 }); 121 document.body.appendChild(host); 122 } 123 124 function openLightbox(url) { 125 var existing = document.getElementById("me-lightbox"); 126 if (existing) existing.remove(); 127 if (!url) return; 128 var host = FruitiUI.el("div", "me-lightbox"); 129 host.id = "me-lightbox"; 130 host.setAttribute("role", "dialog"); 131 var img = document.createElement("img"); 132 img.src = url; 133 img.alt = ""; 134 host.appendChild(img); 135 var closeBtn = FruitiUI.el("button", "me-lightbox__close", "Close"); 136 closeBtn.type = "button"; 137 function close() { 138 if (host.parentNode) host.parentNode.removeChild(host); 139 } 140 closeBtn.addEventListener("click", close); 141 host.addEventListener("click", function (e) { 142 if (e.target === host) close(); 143 }); 144 host.appendChild(closeBtn); 145 document.body.appendChild(host); 146 } 147 148 function openChoosePlan(root, ctx, signal, onBack) { 149 var existing = document.getElementById("me-plan-host"); 150 if (existing) existing.remove(); 151 var host = FruitiUI.el("div", "me-plan-overlay"); 152 host.id = "me-plan-host"; 153 // Prefer the workspace so CYP covers the full right panel (not Meâs photo/detail columns). 154 var hostParent = 155 document.getElementById("app-workspace") || root || document.body; 156 hostParent.appendChild(host); 157 if (!window.FruitiSubscriptionPlan) { 158 FruitiAppShell.toast("Choose Your Plan isnât available right now."); 159 host.remove(); 160 return; 161 } 162 FruitiSubscriptionPlan.mount(host, { 163 profile: ctx.profile, 164 entitlement: ctx.entitlement, 165 signal: signal, 166 shellCtx: ctx, 167 backLabel: "Back", 168 onBack: function () { 169 host.remove(); 170 if (onBack) onBack(); 171 }, 172 openDialog: openDialog, 173 }); 174 } 175 176 function renderReceipts(host, profile) { 177 FruitiUI.clear(host); 178 var wrap = FruitiUI.el("div", "me-receipts"); 179 FruitiProfileModel.paintReceiptsPane( 180 wrap, 181 FruitiProfileModel.receiptsFromProfile(profile), 182 { 183 showUnsetLinks: true, 184 includeBio: true, 185 emptyCopy: false, 186 } 187 ); 188 host.appendChild(wrap); 189 } 190 191 function renderWhoViewed(host) { 192 var section = FruitiUI.el("section", "me-who"); 193 if (!premium) { 194 section.classList.add("me-who--gate"); 195 var copy = FruitiUI.el("div", "me-who__summary"); 196 copy.appendChild(FruitiUI.el("h3", "me-who__title", "Who viewed you")); 197 copy.appendChild( 198 FruitiUI.el( 199 "p", 200 "me-who__hint", 201 "See whoâs checking your profile with Fruiti+." 202 ) 203 ); 204 section.appendChild(copy); 205 var teaser = FruitiUI.el("div", "me-who__teaser"); 206 teaser.setAttribute("aria-hidden", "true"); 207 for (var g = 0; g < 4; g++) { 208 teaser.appendChild(FruitiUI.el("span", "me-who__ghost")); 209 } 210 section.appendChild(teaser); 211 var up = FruitiUI.el("button", "me-chip me-who__cta", "Choose Your Plan"); 212 up.type = "button"; 213 up.addEventListener("click", function () { 214 openChoosePlan( 215 document.querySelector(".page-me") || host.closest(".page") || document.body, 216 shellCtx || { profile: profileCache }, 217 abort && abort.signal, 218 null 219 ); 220 }); 221 section.appendChild(up); 222 host.appendChild(section); 223 return; 224 } 225 var head = FruitiUI.el("div", "me-who__head"); 226 head.appendChild(FruitiUI.el("h3", "me-who__title", "Who viewed you")); 227 section.appendChild(head); 228 if (glowState.viewersError) { 229 var fail = FruitiUI.el("div", "me-who__fail"); 230 fail.appendChild( 231 FruitiUI.el( 232 "p", 233 "me-quiet-empty", 234 glowState.viewersError || "Couldnât load who viewed you." 235 ) 236 ); 237 var retry = FruitiUI.el("button", "me-chip me-who__retry", "Retry"); 238 retry.type = "button"; 239 retry.addEventListener("click", function () { 240 loadGlowData().then(function () { 241 refreshTabBody(bodyHostRef || host); 242 }); 243 }); 244 fail.appendChild(retry); 245 section.appendChild(fail); 246 host.appendChild(section); 247 return; 248 } 249 if (!glowState.viewers) { 250 section.appendChild(FruitiUI.el("div", "list-skeleton")); 251 host.appendChild(section); 252 return; 253 } 254 if (!glowState.viewers.length) { 255 section.appendChild( 256 FruitiUI.el("p", "me-quiet-empty", "No views in the last 10 days yet.") 257 ); 258 host.appendChild(section); 259 return; 260 } 261 var rail = FruitiUI.el("div", "me-who__rail");
262 glowState.viewers.forEach(function (item) { 263 var row = FruitiProfileInsights.viewerRow(item); 264 var btn = FruitiUI.el("button", "me-who__card"); 265 btn.type = "button"; 266 var avatar = FruitiUI.el("div", "me-who__avatar"); 267 if (row.photoUrl) { 268 var img = document.createElement("img"); 269 img.src = row.photoUrl; 270 img.alt = ""; 271 img.loading = "lazy"; 272 avatar.appendChild(img); 273 } 274 btn.appendChild(avatar); 275 btn.appendChild(FruitiUI.el("span", "me-who__name", row.name)); 276 if (row.whenLabel) btn.appendChild(FruitiUI.el("span", "me-who__when", row.whenLabel)); 277 btn.addEventListener("click", function () { 278 if (row.id) FruitiAppRouter.navigate("/app/profile/" + encodeURIComponent(row.id)); 279 }); 280 rail.appendChild(btn); 281 }); 282 section.appendChild(rail); 283 host.appendChild(section); 284 } 285 286 function renderTiles(host) { 287 var section = FruitiUI.el("section", "me-tiles"); 288 var head = FruitiUI.el("div", "me-tiles__head"); 289 head.appendChild(FruitiUI.el("h3", "me-tiles__heading", "Insights")); 290 var control = FruitiUI.el("div", "me-temporal"); 291 ["historic", "present", "future"].forEach(function (key) {
292 var label = key.charAt(0).toUpperCase() + key.slice(1); 293 var btn = FruitiUI.el( 294 "button", 295 "me-temporal__btn" + (temporal === key ? " is-active" : ""), 296 label 297 ); 298 btn.type = "button"; 299 btn.addEventListener("click", function () { 300 if (key !== "present" && !premium) { 301 openChoosePlan( 302 document.querySelector(".page-me") || host.closest(".page") || document.body, 303 shellCtx || { profile: profileCache }, 304 abort && abort.signal, 305 null 306 ); 307 return; 308 } 309 temporal = key; 310 refreshTabBody(bodyHostRef || host); 311 }); 312 control.appendChild(btn); 313 }); 314 head.appendChild(control); 315 section.appendChild(head); 316 317 var tiles = FruitiProfileInsights.normalizeTiles( 318 temporal, 319 glowState.tiles[temporal], 320 premium 321 ); 322 if (glowState.tiles[temporal] == null && temporal === "present") { 323 section.appendChild(FruitiUI.el("div", "list-skeleton")); 324 host.appendChild(section); 325 return; 326 } 327 328 var list = FruitiUI.el("div", "me-tiles__list me-tiles__list--aura"); 329 var anyData = tiles.some(function (t) { 330 return t.raw && t.summary; 331 }); 332 333 var ACCENT = { 334 conversation_heat: "heat", 335 intent_density: "density", 336 crowd_competition: "crowd", 337 hunting_session: "hunt", 338 conversion_funnel: "funnel", 339 pursuit_missed: "miss", 340 momentum_outlook: "momentum", 341 advantage_window: "window", 342 initiative_projection: "initiative", 343 }; 344 345 function appendInsightCard(tile, muted, meta) { 346 var accent = ACCENT[tile.key] || "density"; 347 var row = FruitiUI.el( 348 "article", 349 "me-insight-row me-insight-card me-insight-card--" + 350 accent + 351 (muted ? " me-insight-row--muted" : " is-live") 352 ); 353 if (meta) row.title = meta; 354 355 var aura = FruitiUI.el("div", "me-insight-card__aura"); 356 aura.setAttribute("aria-hidden", "true"); 357 aura.appendChild(FruitiUI.el("span", "me-insight-card__ring me-insight-card__ring--outer")); 358 aura.appendChild(FruitiUI.el("span", "me-insight-card__ring me-insight-card__ring--mid")); 359 aura.appendChild(FruitiUI.el("span", "me-insight-card__core")); 360 row.appendChild(aura); 361 362 var body = FruitiUI.el("div", "me-insight-card__body"); 363 var cardHead = FruitiUI.el("div", "me-insight-row__head"); 364 cardHead.appendChild(FruitiUI.el("span", "me-insight-row__title", tile.title)); 365 cardHead.appendChild( 366 FruitiUI.el( 367 "span", 368 "me-insight-row__chip" + (muted ? " me-insight-row__chip--quiet" : ""), 369 muted ? "Quiet" : "Live" 370 ) 371 ); 372 body.appendChild(cardHead); 373 374 var wave = FruitiUI.el("div", "me-insight-card__wave"); 375 wave.setAttribute("aria-hidden", "true"); 376 for (var i = 0; i < 7; i++) { 377 var bar = FruitiUI.el("span", "me-insight-card__bar"); 378 bar.style.setProperty("--i", String(i)); 379 wave.appendChild(bar); 380 } 381 body.appendChild(wave); 382 383 if (!muted && meta) { 384 var short = 385 meta.length > 52 ? meta.slice(0, 49).replace(/\s+\S*$/, "") + "â¦" : meta; 386 body.appendChild(FruitiUI.el("p", "me-insight-row__meta", short)); 387 } 388 389 row.appendChild(body); 390 list.appendChild(row); 391 } 392 393 if (!tiles.length) { 394 list.appendChild(FruitiUI.el("p", "me-quiet-empty", "No signals yet.")); 395 } else if (!anyData && temporal === "present") { 396 tiles.forEach(function (tile) { 397 appendInsightCard(tile, true, ""); 398 }); 399 } else { 400 tiles.forEach(function (tile) { 401 var muted = !!(tile.missing || !tile.summary); 402 appendInsightCard(tile, muted, muted ? "" : tile.summary || ""); 403 }); 404 } 405 section.appendChild(list); 406 host.appendChild(section); 407 } 408 409 function renderMeetVibes(host, profile) { 410 var mv = FruitiProfileModel.meetVibesVisible(profile); 411 if (!mv) return; 412 var card = FruitiUI.el("section", "me-meet-vibes"); 413 card.appendChild(FruitiUI.el("h3", "me-meet-vibes__title", "My Meet Vibes")); 414 card.appendChild( 415 FruitiUI.el( 416 "p", 417 "me-meet-vibes__sub", 418 "Private patterns from confirmed IRL catch-ups." 419 ) 420 ); 421 var status = String(mv.status || "").toLowerCase(); 422 var copy = ""; 423 if (status === "empty") copy = "No Meet Vibes yet."; 424 else if (status === "building") { 425 copy = 426 mv.privateProgressCopy || 427 mv.summary || 428 "Still building from confirmed IRL catch-ups."; 429 } else if (status === "visible") { 430 copy = mv.summary || ""; 431 if (!copy && Array.isArray(mv.signals) && mv.signals.length) { 432 var labels = mv.signals 433 .map(function (s) { 434 return s && (s.label || s.name); 435 }) 436 .filter(Boolean) 437 .slice(0, 3); 438 copy = labels.length 439 ? "People often describe your IRL vibe as " + labels.join(", ") + "." 440 : "People often describe your IRL vibe after confirmed catch-ups."; 441 } 442 if (!copy) { 443 copy = "People often describe your IRL vibe after confirmed catch-ups."; 444 } 445 } else { 446 copy = mv.summary || mv.privateProgressCopy || ""; 447 } 448 if (!copy) return; 449 card.appendChild(FruitiUI.el("p", "me-meet-vibes__copy", String(copy))); 450 card.appendChild( 451 FruitiUI.el( 452 "p", 453 "me-meet-vibes__footer", 454 mv.footer || "Aggregated only. Fruiti never shows who said what." 455 ) 456 ); 457 host.appendChild(card); 458 } 459 460 function refreshTabBody(bodyHost) { 461 if (!bodyHost) return; 462 FruitiUI.clear(bodyHost); 463 if (activeTab === "receipts") { 464 renderReceipts(bodyHost, profileCache); 465 return; 466 } 467 var stack = FruitiUI.el("div", "me-glow-stack"); 468 renderWhoViewed(stack); 469 var divider = FruitiUI.el("div", "me-glow-divider"); 470 divider.setAttribute("aria-hidden", "true"); 471 stack.appendChild(divider); 472 renderTiles(stack); 473 renderMeetVibes(stack, profileCache); 474 bodyHost.appendChild(stack); 475 } 476 477 async function loadGlowData() { 478 var signal = abort && abort.signal; 479 glowState.viewersError = null; 480 glowState.tilesError = null; 481 if (premium) { 482 var results = await Promise.all([ 483 FruitiProfileInsights.getWhoViewedMe(signal).catch(function (err) { 484 glowState.viewersError = (err && err.message) || "Couldnât load who viewed you."; 485 return []; 486 }), 487 FruitiProfileInsights.getHistoricTiles(signal).catch(function () { 488 return {}; 489 }), 490 FruitiProfileInsights.getFutureTiles(signal).catch(function () { 491 return {}; 492 }), 493 FruitiProfileInsights.getIncognito(signal).catch(function () { 494 return null; 495 }), 496 FruitiProfileInsights.getPresentAll(signal).catch(function () { 497 return {}; 498 }), 499 ]); 500 glowState.viewers = results[0] || []; 501 glowState.tiles.historic = results[1] || {}; 502 glowState.tiles.future = results[2] || {}; 503 if (results[3]) { 504 incognitoOn = !!results[3].enabled; 505 if (incognitoHostRef) renderIncognito(incognitoHostRef, shellCtx, signal); 506 } 507 glowState.tiles.present = results[4] || {}; 508 } else { 509 glowState.viewers = []; 510 glowState.tiles.historic = {}; 511 glowState.tiles.future = {};
512 try { 513 glowState.tiles.present = await FruitiProfileInsights.getPresentAll(signal); 514 } catch (err) { 515 glowState.tiles.present = {}; 516 glowState.tilesError = null; 517 try { 518 console.warn("[Fruiti] Present insights load failed", err && err.message); 519 } catch (_) {} 520 } 521 } 522 } 523 524 function renderGallery(host, photos, profile) { 525 FruitiUI.clear(host); 526 host.classList.toggle("is-empty", !photos.length); 527 var wrap = FruitiUI.el("div", "me-gallery" + (photos.length ? "" : " me-gallery--empty")); 528 var stage = FruitiUI.el("div", "me-gallery__stage"); 529 if (photos.length) { 530 var img = document.createElement("img"); 531 img.className = "me-gallery__hero"; 532 img.alt = ""; 533 var idx = Math.max(0, Math.min(photoIndex, photos.length - 1)); 534 photoIndex = idx; 535 img.src = photos[idx].url; 536 img.addEventListener("click", function () { 537 openLightbox(photos[idx].url); 538 }); 539 stage.appendChild(img); 540 } else { 541 var wash = FruitiUI.el("div", "me-empty-wash"); 542 wash.setAttribute("aria-hidden", "true"); 543 wash.innerHTML = 544 '<span class="me-empty-wash__blob me-empty-wash__blob--a"></span>' + 545 '<span class="me-empty-wash__blob me-empty-wash__blob--b"></span>' + 546 '<span class="me-empty-wash__blob me-empty-wash__blob--c"></span>' + 547 '<span class="me-empty-wash__blob me-empty-wash__blob--d"></span>'; 548 stage.appendChild(wash); 549 var empty = FruitiUI.el("div", "me-gallery__empty"); 550 empty.appendChild(FruitiUI.el("p", "me-gallery__empty-title", "Add your first photo")); 551 empty.appendChild( 552 FruitiUI.el("p", "me-gallery__empty-sub", "This is how people meet you on Fruiti.") 553 ); 554 var addPhoto = FruitiUI.el( 555 "button", 556 "me-icon-btn me-icon-btn--primary me-gallery__empty-cta" 557 ); 558 addPhoto.type = "button"; 559 configureIconButton(addPhoto, "Add photos", "camera"); 560 addPhoto.addEventListener("click", function () { 561 FruitiAppRouter.navigate("/app/edit-profile"); 562 }); 563 empty.appendChild(addPhoto); 564 stage.appendChild(empty); 565 } 566 567 if (photos.length > 1) { 568 var prev = FruitiUI.el("button", "me-gallery__nav me-gallery__nav--prev", "â¹"); 569 var next = FruitiUI.el("button", "me-gallery__nav me-gallery__nav--next", "âº"); 570 prev.type = "button"; 571 next.type = "button"; 572 prev.setAttribute("aria-label", "Previous photo"); 573 next.setAttribute("aria-label", "Next photo"); 574 prev.addEventListener("click", function () { 575 photoIndex = (photoIndex - 1 + photos.length) % photos.length; 576 renderGallery(host, photos, profile); 577 }); 578 next.addEventListener("click", function () { 579 photoIndex = (photoIndex + 1) % photos.length; 580 renderGallery(host, photos, profile); 581 }); 582 stage.appendChild(prev); 583 stage.appendChild(next); 584 } 585 wrap.appendChild(stage); 586 587 if (photos.length > 1) { 588 var strip = FruitiUI.el("div", "me-gallery__strip"); 589 photos.forEach(function (ph, i) { 590 var thumb = FruitiUI.el("button", "me-gallery__thumb" + (i === photoIndex ? " is-active" : "")); 591 thumb.type = "button"; 592 var tImg = document.createElement("img"); 593 tImg.src = ph.url; 594 tImg.alt = ""; 595 tImg.loading = "lazy"; 596 thumb.appendChild(tImg); 597 thumb.addEventListener("click", function () { 598 photoIndex = i; 599 renderGallery(host, photos, profile); 600 }); 601 strip.appendChild(thumb); 602 }); 603 wrap.appendChild(strip); 604 } 605 606 host.appendChild(wrap); 607 } 608 609 function renderIdentity(host, profile, ctx, signal) { 610 FruitiUI.clear(host); 611 var name = FruitiProfileModel.chromeDisplayName(profile); 612 var status = FruitiProfileModel.userStatusText(profile); 613 var verified = FruitiProfileModel.isVerified(profile); 614 var reverify = FruitiProfileModel.needsReverify(profile); 615 var receipts = FruitiProfileModel.receiptsFromProfile(profile); 616 617 var chrome = FruitiUI.el("div", "me-identity"); 618 var header = FruitiUI.el("div", "me-identity__header"); 619 var copy = FruitiUI.el("div", "me-identity__copy"); 620 var nameRow = FruitiUI.el("div", "me-identity__name-row"); 621 if (name) { 622 nameRow.appendChild(FruitiUI.el("h1", "me-identity__name", name)); 623 } else { 624 nameRow.appendChild(
625 FruitiUI.el("div", "me-identity__name-empty", "No display name yet") 626 ); 627 } 628 if (verified) { 629 var badge = FruitiUI.el("span", "me-identity__verified", "Verified"); 630 badge.title = "Photo verified"; 631 nameRow.appendChild(badge); 632 } 633 copy.appendChild(nameRow); 634 635 if (status) { 636 copy.appendChild(FruitiUI.el("p", "me-identity__status", status)); 637 } else { 638 copy.appendChild( 639 FruitiUI.el("p", "me-identity__status-empty", "No status set") 640 ); 641 } 642 header.appendChild(copy); 643 644 var actions = FruitiUI.el("div", "me-chips"); 645 var edit = FruitiUI.el("button", "me-icon-btn me-icon-btn--primary"); 646 edit.type = "button"; 647 configureIconButton(edit, "Edit profile", "edit"); 648 edit.addEventListener("click", function () { 649 FruitiAppRouter.navigate("/app/edit-profile"); 650 }); 651 actions.appendChild(edit); 652 653 var verifyLabel = reverify ? "Re-verify" : verified ? "Verified" : "Verify"; 654 var verify = FruitiUI.el( 655 "button", 656 "me-icon-btn" + (verified && !reverify ? " me-icon-btn--muted" : "") 657 ); 658 verify.type = "button"; 659 configureIconButton(verify, verifyLabel, "verify"); 660 verify.addEventListener("click", function () { 661 if (verified && !reverify) { 662 openDialog({ 663 title: "Photo verified", 664 body: "Your photo is already verified.", 665 cancelLabel: false, 666 confirmLabel: "OK", 667 }); 668 return; 669 } 670 if (!window.FruitiSmartVerifyFlow) { 671 FruitiAppShell.toast("Smart Verify isnât available right now."); 672 return; 673 } 674 FruitiSmartVerifyFlow.open({ 675 profile: profile, 676 onComplete: function () { 677 FruitiAppShell.toast("Verification submitted."); 678 if (meRoot && shellCtx) mountMe(meRoot, shellCtx); 679 }, 680 }); 681 }); 682 actions.appendChild(verify); 683 684 var incognitoSlot = FruitiUI.el("div", "me-identity__incognito"); 685 actions.appendChild(incognitoSlot); 686 incognitoHostRef = incognitoSlot; 687 header.appendChild(actions); 688 chrome.appendChild(header); 689 690 if (receipts.basics.length) { 691 var facts = FruitiUI.el("div", "me-identity__facts"); 692 receipts.basics.slice(0, 4).forEach(function (r) { 693 facts.appendChild(FruitiUI.el("span", "me-fact", r.value)); 694 }); 695 chrome.appendChild(facts); 696 } 697 698 host.appendChild(chrome); 699 } 700 701 function createPresencePill(profile) { 702 var presence = FruitiProfileModel.presenceChrome(profile); 703 var pill = FruitiUI.el( 704 "span", 705 "me-presence-pill me-presence-pill--" + presence.key 706 ); 707 pill.appendChild(FruitiUI.el("span", "me-presence-pill__dot")); 708 pill.appendChild(document.createTextNode(presence.label)); 709 return pill; 710 } 711 712 function renderTabs(host, bodyHost, ctx, signal) { 713 FruitiUI.clear(host); 714 var bar = FruitiUI.el("div", "me-tabs"); 715 bar.appendChild(createPresencePill(profileCache)); 716 var seg = FruitiUI.el("div", "me-tabs__seg"); 717 ["glow", "receipts"].forEach(function (key) {
718 var label = key === "glow" ? "Glow Up" : "Receipts"; 719 var btn = FruitiUI.el( 720 "button", 721 "me-tabs__btn" + (activeTab === key ? " is-active" : ""), 722 label 723 ); 724 btn.type = "button"; 725 btn.addEventListener("click", function () { 726 activeTab = key; 727 renderTabs(host, bodyHost, ctx, signal); 728 refreshTabBody(bodyHost); 729 }); 730 seg.appendChild(btn); 731 }); 732 bar.appendChild(seg); 733 host.appendChild(bar); 734 } 735 736 function syncIncognitoButton(button, on) { 737 var label = on ? "Turn Incognito off" : "Turn Incognito on"; 738 button.classList.toggle("is-on", on); 739 button.setAttribute("aria-pressed", String(on)); 740 button.setAttribute("aria-label", label); 741 button.title = label; 742 FruitiUI.clear(button); 743 button.appendChild(meIcon(on ? "eyeOff" : "eye")); 744 } 745 746 function renderIncognito(host, ctx, signal) { 747 if (!host) return; 748 FruitiUI.clear(host); 749 var incognito = FruitiUI.el( 750 "button", 751 "me-icon-btn me-incognito" + (incognitoOn ? " is-on" : "") 752 ); 753 incognito.type = "button"; 754 syncIncognitoButton(incognito, incognitoOn); 755 incognito.addEventListener("click", async function () { 756 if (!premium) { 757 openChoosePlan( 758 document.querySelector(".page-me") || host.closest(".page") || document.body, 759 ctx || shellCtx, 760 signal, 761 null 762 ); 763 return; 764 } 765 var next = !incognitoOn; 766 incognitoOn = next; 767 syncIncognitoButton(incognito, next); 768 FruitiAppShell.toast(pick(next ? INCOGNITO_ON : INCOGNITO_OFF)); 769 try { 770 await FruitiProfileInsights.setIncognito(next, signal); 771 } catch (err) { 772 incognitoOn = !next; 773 syncIncognitoButton(incognito, incognitoOn); 774 FruitiAppShell.toast((err && err.message) || "Couldnât update Incognito."); 775 } 776 }); 777 host.appendChild(incognito); 778 } 779 780 async function mountMe(root, ctx) { 781 stop(); 782 abort = new AbortController(); 783 var signal = abort.signal; 784 shellCtx = ctx; 785 meRoot = root; 786 FruitiUI.clear(root); 787 788 var page = FruitiUI.el("div", "page page-me"); 789 790 var stage = FruitiUI.el("div", "me-stage"); 791 var photoCol = FruitiUI.el("div", "me-stage__photo me-gallery-host"); 792 photoCol.appendChild(FruitiUI.el("div", "list-skeleton")); 793 794 var panel = FruitiUI.el("div", "me-stage__panel"); 795 var identityHost = FruitiUI.el("div", "me-stage__chrome me-identity-host"); 796 var tabsHost = FruitiUI.el("div", "me-toolbar me-tabs-host"); 797 var bodyHost = FruitiUI.el("div", "me-body"); 798 bodyHost.appendChild(FruitiUI.el("div", "list-skeleton")); 799 tabsHostRef = tabsHost; 800 bodyHostRef = bodyHost; 801 panel.appendChild(identityHost); 802 panel.appendChild(tabsHost); 803 panel.appendChild(bodyHost); 804 805 stage.appendChild(photoCol); 806 stage.appendChild(panel); 807 page.appendChild(stage); 808 root.appendChild(page); 809 810 try { 811 var profile = await FruitiProfileEdit.getProfile(signal); 812 profileCache = profile; 813 if (ctx) ctx.profile = profile; 814 815 try {
816 subStatus = await FruitiSettings.fetchSubscriptionStatus(signal); 817 } catch (_) { 818 subStatus = null; 819 } 820 premium = FruitiProfileModel.isPremiumTier( 821 ctx && ctx.entitlement, 822 profile, 823 subStatus 824 ); 825 826 var photos = FruitiProfileModel.photoList(profile); 827 photoIndex = 0; 828 renderGallery(photoCol, photos, profile); 829 renderIdentity(identityHost, profile, ctx, signal); 830 renderTabs(tabsHost, bodyHost, ctx, signal); 831 renderIncognito(incognitoHostRef, ctx, signal); 832 await loadGlowData(); 833 if (signal.aborted) return; 834 renderIncognito(incognitoHostRef, ctx, signal); 835 refreshTabBody(bodyHost); 836 } catch (err) { 837 if (signal.aborted) return; 838 FruitiUI.clear(root); 839 var errPage = FruitiUI.el("div", "page page-me"); 840 errPage.appendChild( 841 FruitiUI.errorState({ 842 body: (err && err.message) || "Couldnât load your profile.", 843 onRetry: function () { 844 mountMe(root, ctx); 845 }, 846 }) 847 ); 848 root.appendChild(errPage); 849 } 850 } 851 852 async function mount(root, ctx, route) { 853 if (route && route.profileId) { 854 if (!global.FruitiPeerProfile) { 855 FruitiUI.clear(root); 856 root.appendChild( 857 FruitiUI.errorState({ 858 body: "Other-user profile isnât available right now.", 859 }) 860 ); 861 return; 862 } 863 return FruitiPeerProfile.mount(root, ctx, route); 864 } 865 return mountMe(root, ctx); 866 } 867 868 global.FruitiPageProfile = { mount: mount, stop: stop }; 869})(window);
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.