Skip to content

Conversation

evanpelle
Copy link
Collaborator

Description:

Describe the PR.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

DISCORD_USERNAME

Copy link
Contributor

coderabbitai bot commented Sep 23, 2025

Walkthrough

Adds rewarded ad support. Extends Window.ramp with rewarded-video APIs and ad request. Integrates rewarded ad loading and UI into WinModal, including event listeners, button state, and reward flow. Adjusts hash navigation after lobby join. Leaves commented spaNewPage lines unchanged.

Changes

Cohort / File(s) Summary
Global RAMP API extensions
src/client/Main.ts
Declares new Window.ramp methods: manuallyCreateRewardUi, showRewardedVideoModal, showRewardedVideoConfirmationModal, optional showRewardedVideoConfirmModal, and requestAd(adType). Comments out alternate spaNewPage calls. Changes post-join hash to #google_sample_tag=1.
WinModal rewarded ad integration
src/client/graphics/layers/WinModal.ts
Adds rewarded ad state, event listeners (ready/accept/completed/reward/close), rewarded button and container, ad loading via RAMP queue for rewarded_ad_video, initialization to enable button, flow to show rewarded modal and grant reward, error handling, and cleanup.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant WinModal
  participant RAMP as window.ramp
  participant Ad as Rewarded Ad

  rect rgb(245,248,255)
    note over WinModal: Initialization on show()
    WinModal->>RAMP: queue rewarded_ad_video into container
    RAMP-->>WinModal: rewarded ad ready (event)
    WinModal->>WinModal: enable "Rewarded Ad" button
  end

  User->>WinModal: Click "Rewarded Ad"
  WinModal->>RAMP: showRewardedVideoModal(cta, confirm)
  RAMP->>Ad: display modal / play video
  Ad-->>RAMP: completed
  RAMP-->>WinModal: reward granted (event)
  WinModal->>WinModal: grantReward()
  RAMP-->>WinModal: closed (event)
  WinModal->>WinModal: tidy up / re-enable UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ads on death screen #1223 — Also touches WinModal for ad UI/events (gutter-ad), overlapping with this rewarded-ad event wiring.
  • Delay win modal #1224 — Adjusts WinModal show/button timing; related to this PR’s ad-loading-before-show changes.
  • add playwire ads #1128 — Extends Playwire RAMP integration and hooks, similar to new Window.ramp methods used here.

Poem

A win-screen gleams, a button wakes,
The ad rolls in with gentle stakes.
Events align, rewards appear,
A tidy flow from click to cheer.
Hash tags shift, the path is clear—
Ship it now, the prize is near. 🎉

Pre-merge checks

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description Check ❓ Inconclusive The PR description is still the template placeholder with a checklist and does not describe the actual code changes; it is too generic to determine scope, testing, or impact and therefore is inconclusive under the provided criteria. Please replace the template with a short summary of the changes (what was added/modified and why), attach screenshots for UI updates, list any tests added or missing, confirm translation updates, and provide your Discord username before requesting review.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "rewarded ad test" references rewarded ads, which matches the changes adding a rewarded ad API and UI in the changeset, but it is terse and labels the PR as a "test", making the scope and readiness unclear; it therefore relates to the main change but is not fully descriptive.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/client/graphics/layers/WinModal.ts (1)

303-313: Enable the button on “ad ready” event, not on timer.

Rely on the RAMP “ready” event to avoid false enables and errors on click.

   async show() {
-    this.loadAds();
+    this.loadAds();
     await this.loadPatternContent();
-    this.initializeRewardedAd();
+    this.initializeRewardedAd();

And update initializeRewardedAd below (see next comment).

🧹 Nitpick comments (5)
src/client/graphics/layers/WinModal.ts (3)

336-351: Ad unit lifecycle — destroy on detach/hide to avoid duplicates.

Call destroyUnits('rewarded_ad_video') on hide/disconnect.

Add this outside the hunk (two places):

hide() {
  this.isVisible = false;
  this.showButtons = false;
  this.requestUpdate();
  try {
    window.ramp?.destroyUnits?.("rewarded_ad_video");
  } catch {/* no-op */}
}

disconnectedCallback() {
  super.disconnectedCallback();
  try {
    window.ramp?.destroyUnits?.("rewarded_ad_video");
  } catch {/* no-op */}
}

399-422: Avoid re-enabling here; handled in finally. Also keep one confirmation path.

Prevent double confirmation and duplicated state toggles.

-    // Show Playwire confirmation modal if available
-    try {
-      if (window.ramp.showRewardedVideoConfirmationModal) {
-        window.ramp.showRewardedVideoConfirmationModal();
-      } else if (window.ramp.showRewardedVideoConfirmModal) {
-        window.ramp.showRewardedVideoConfirmModal();
-      }
-    } catch (e) {
-      // no-op
-    }
-
-    // Re-enable button after flow
-    this.rewardedAdAvailable = true;
-    this.requestUpdate();
+    // Show Playwire confirmation modal if available
+    try {
+      window.ramp?.showRewardedVideoConfirmationModal?.() ??
+        window.ramp?.showRewardedVideoConfirmModal?.();
+    } catch {/* no-op */}

81-110: Type safety for custom events.

Augment WindowEventMap with a typed union for the Playwire events to keep handlers type-safe.

Add a .d.ts ambient declaration:

declare global {
  interface WindowEventMap {
    rewardedAdVideoRewardReady: Event;
    userAcceptsRewardedAd: Event;
    rewardedAdCompleted: Event;
    rewardedAdRewardGranted: Event;
    rewardedCloseButtonTriggered: Event;
    userClosedWithRewardCanResolve: Event;
    rejectAdCloseCta: Event;
  }
}
export {};
src/client/Main.ts (2)

62-95: Shape Window.ramp as a reusable type (in .d.ts), and narrow string types.

  • Move this to a shared ambient .d.ts so all modules see it.
  • Use narrower types: adType as a union like "rewarded_ad_video".
  • Consider making window.ramp optional to match runtime.

Example:

// global-ramp.d.ts
declare global {
  type RampAdType = "rewarded_ad_video";
  interface RampApi {
    que: Array<() => void>;
    passiveMode: boolean;
    spaAddAds: (ads: Array<{ type: RampAdType; selectorId: string }>) => void;
    destroyUnits: (adType: RampAdType) => void;
    settings?: { slots?: unknown };
    spaNewPage: (url: string) => void;
    manuallyCreateRewardUi(config: { skipConfirmation?: boolean; watchAdId?: string; closeId?: string }): Promise<void>;
    showRewardedVideoModal(
      ctaSettings?: { logoSrc?: string; nameLogoSrc?: string; title?: string; backgroundOverlay?: boolean; backgroundColor?: string; titleColor?: string; confirmButtonText?: string; confirmButtonColor?: string; },
      confirmSettings?: { logoSrc?: string; title?: string; subTitle?: string; closeButtonText?: string; backgroundOverlay?: boolean; backgroundColor?: string; buttonColor?: string; subTitleTextColor?: string; titleColor?: string; },
    ): Promise<void>;
    showRewardedVideoConfirmationModal(): void;
    showRewardedVideoConfirmModal?: () => void;
    requestAd: (adType: RampAdType) => Promise<void>;
  }
  interface Window {
    ramp?: RampApi;
  }
}
export {};

622-622: Hash changed to #google_sample_tag=1 — verify flow impact.

  • Does this break back/forward behavior or deep links (e.g., rejoining lobbies)?
  • If only tagging, prefer replaceState to avoid extra history entries.
-        history.pushState(null, "", `#google_sample_tag=1`);
+        history.replaceState(null, "", `#google_sample_tag=1`);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34c94d2 and 11c967f.

📒 Files selected for processing (2)
  • src/client/Main.ts (3 hunks)
  • src/client/graphics/layers/WinModal.ts (6 hunks)
🔇 Additional comments (4)
src/client/graphics/layers/WinModal.ts (3)

34-36: State flag added — OK

Good name and default. No concerns.


46-48: Initialize listeners in constructor — OK (but see cleanup fix below)

Setup is fine. Cleanup currently won’t work due to bound handlers; see below.


207-211: Verify unique container ID.

Confirm only one win-modal exists at a time to avoid duplicate #rewarded-ad-container IDs in DOM.

src/client/Main.ts (1)

609-609: Restore SPA pageview call on virtual navigations (confirm Playwire API)

SPA page-change APIs should be called once for every in-app navigation after the new view/DOM is rendered; skipping it can reduce ad fill and break tracking. I couldn't find public Playwire docs for spaNewPage specifically — confirm the exact API/name/timing with Playwire and re-enable the commented pageview call in src/client/Main.ts (around line 609).

Comment on lines +49 to +79
private setupRewardedAdEventListeners() {
// Listen for rewarded ad lifecycle events
window.addEventListener(
"rewardedAdVideoRewardReady",
this.onRewardedAdReady.bind(this),
);
window.addEventListener(
"userAcceptsRewardedAd",
this.onUserAcceptsRewardedAd.bind(this),
);
window.addEventListener(
"rewardedAdCompleted",
this.onRewardedAdCompleted.bind(this),
);
window.addEventListener(
"rewardedAdRewardGranted",
this.onRewardedAdRewardGranted.bind(this),
);
window.addEventListener(
"rewardedCloseButtonTriggered",
this.onRewardedAdClosed.bind(this),
);
window.addEventListener(
"userClosedWithRewardCanResolve",
this.onUserClosedWithReward.bind(this),
);
window.addEventListener(
"rejectAdCloseCta",
this.onRejectAdClose.bind(this),
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Event listeners won’t be removed (bind creates new functions). Store handler refs.

Using .bind(this) in both add/remove creates different function identities. removeEventListener will not detach, causing leaks and duplicate handlers after re‑attach.

Apply this diff to use cached handler refs:

-    window.addEventListener(
-      "rewardedAdVideoRewardReady",
-      this.onRewardedAdReady.bind(this),
-    );
+    window.addEventListener(
+      "rewardedAdVideoRewardReady",
+      this.boundOnRewardedAdReady,
+    );
-    window.addEventListener(
-      "userAcceptsRewardedAd",
-      this.onUserAcceptsRewardedAd.bind(this),
-    );
+    window.addEventListener(
+      "userAcceptsRewardedAd",
+      this.boundOnUserAcceptsRewardedAd,
+    );
-    window.addEventListener(
-      "rewardedAdCompleted",
-      this.onRewardedAdCompleted.bind(this),
-    );
+    window.addEventListener(
+      "rewardedAdCompleted",
+      this.boundOnRewardedAdCompleted,
+    );
-    window.addEventListener(
-      "rewardedAdRewardGranted",
-      this.onRewardedAdRewardGranted.bind(this),
-    );
+    window.addEventListener(
+      "rewardedAdRewardGranted",
+      this.boundOnRewardedAdRewardGranted,
+    );
-    window.addEventListener(
-      "rewardedCloseButtonTriggered",
-      this.onRewardedAdClosed.bind(this),
-    );
+    window.addEventListener(
+      "rewardedCloseButtonTriggered",
+      this.boundOnRewardedAdClosed,
+    );
-    window.addEventListener(
-      "userClosedWithRewardCanResolve",
-      this.onUserClosedWithReward.bind(this),
-    );
+    window.addEventListener(
+      "userClosedWithRewardCanResolve",
+      this.boundOnUserClosedWithReward,
+    );
-    window.addEventListener(
-      "rejectAdCloseCta",
-      this.onRejectAdClose.bind(this),
-    );
+    window.addEventListener(
+      "rejectAdCloseCta",
+      this.boundOnRejectAdClose,
+    );

Add these fields on the class (outside this hunk):

private boundOnRewardedAdReady = this.onRewardedAdReady.bind(this);
private boundOnUserAcceptsRewardedAd = this.onUserAcceptsRewardedAd.bind(this);
private boundOnRewardedAdCompleted = this.onRewardedAdCompleted.bind(this);
private boundOnRewardedAdRewardGranted = this.onRewardedAdRewardGranted.bind(this);
private boundOnRewardedAdClosed = this.onRewardedAdClosed.bind(this);
private boundOnUserClosedWithReward = this.onUserClosedWithReward.bind(this);
private boundOnRejectAdClose = this.onRejectAdClose.bind(this);
🤖 Prompt for AI Agents
In src/client/graphics/layers/WinModal.ts around lines 49 to 79, the event
listeners are added using .bind(this) which creates new function identities
preventing removeEventListener from detaching them; fix by adding class fields
that cache bound handler references (one per handler: boundOnRewardedAdReady,
boundOnUserAcceptsRewardedAd, boundOnRewardedAdCompleted,
boundOnRewardedAdRewardGranted, boundOnRewardedAdClosed,
boundOnUserClosedWithReward, boundOnRejectAdClose) initialized with
this.<handler>.bind(this), then update setupRewardedAdEventListeners to pass
those cached bound handlers to addEventListener (and ensure any teardown/remove
routine uses the same cached fields for removeEventListener).

Comment on lines +95 to +97
private onRewardedAdRewardGranted() {
console.log("🎁 User watched enough to earn a reward");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Reward is never granted — call grantReward() on reward events.

You log but don’t grant. Hook the reward grant points.

  private onRewardedAdRewardGranted() {
-    console.log("🎁 User watched enough to earn a reward");
+    console.log("🎁 User watched enough to earn a reward");
+    this.grantReward();
  }

  private onUserClosedWithReward() {
-    console.log("🎁 User closed the ad after qualifying for the reward");
+    console.log("🎁 User closed the ad after qualifying for the reward");
+    this.grantReward();
  }

Also applies to: 103-105

🤖 Prompt for AI Agents
In src/client/graphics/layers/WinModal.ts around lines 95-97 and 103-105, the
handlers only log that a rewarded ad condition was met but never actually grant
the reward; update both event handlers to call the existing grantReward() method
(e.g., this.grantReward() or grantReward() depending on scope) after the
console.log, ensuring the method is accessible from the handler context and
preserving any existing side effects/state updates.

Comment on lines +111 to 142
disconnectedCallback() {
super.disconnectedCallback();
// Clean up event listeners
window.removeEventListener(
"rewardedAdVideoRewardReady",
this.onRewardedAdReady.bind(this),
);
window.removeEventListener(
"userAcceptsRewardedAd",
this.onUserAcceptsRewardedAd.bind(this),
);
window.removeEventListener(
"rewardedAdCompleted",
this.onRewardedAdCompleted.bind(this),
);
window.removeEventListener(
"rewardedAdRewardGranted",
this.onRewardedAdRewardGranted.bind(this),
);
window.removeEventListener(
"rewardedCloseButtonTriggered",
this.onRewardedAdClosed.bind(this),
);
window.removeEventListener(
"userClosedWithRewardCanResolve",
this.onUserClosedWithReward.bind(this),
);
window.removeEventListener(
"rejectAdCloseCta",
this.onRejectAdClose.bind(this),
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Listener cleanup still broken — use cached refs in removeEventListener.

Mirror the add side with the same cached handler refs.

-    window.removeEventListener(
-      "rewardedAdVideoRewardReady",
-      this.onRewardedAdReady.bind(this),
-    );
+    window.removeEventListener(
+      "rewardedAdVideoRewardReady",
+      this.boundOnRewardedAdReady,
+    );
-    window.removeEventListener(
-      "userAcceptsRewardedAd",
-      this.onUserAcceptsRewardedAd.bind(this),
-    );
+    window.removeEventListener(
+      "userAcceptsRewardedAd",
+      this.boundOnUserAcceptsRewardedAd,
+    );
-    window.removeEventListener(
-      "rewardedAdCompleted",
-      this.onRewardedAdCompleted.bind(this),
-    );
+    window.removeEventListener(
+      "rewardedAdCompleted",
+      this.boundOnRewardedAdCompleted,
+    );
-    window.removeEventListener(
-      "rewardedAdRewardGranted",
-      this.onRewardedAdRewardGranted.bind(this),
-    );
+    window.removeEventListener(
+      "rewardedAdRewardGranted",
+      this.boundOnRewardedAdRewardGranted,
+    );
-    window.removeEventListener(
-      "rewardedCloseButtonTriggered",
-      this.onRewardedAdClosed.bind(this),
-    );
+    window.removeEventListener(
+      "rewardedCloseButtonTriggered",
+      this.boundOnRewardedAdClosed,
+    );
-    window.removeEventListener(
-      "userClosedWithRewardCanResolve",
-      this.onUserClosedWithReward.bind(this),
-    );
+    window.removeEventListener(
+      "userClosedWithRewardCanResolve",
+      this.boundOnUserClosedWithReward,
+    );
-    window.removeEventListener(
-      "rejectAdCloseCta",
-      this.onRejectAdClose.bind(this),
-    );
+    window.removeEventListener(
+      "rejectAdCloseCta",
+      this.boundOnRejectAdClose,
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
disconnectedCallback() {
super.disconnectedCallback();
// Clean up event listeners
window.removeEventListener(
"rewardedAdVideoRewardReady",
this.onRewardedAdReady.bind(this),
);
window.removeEventListener(
"userAcceptsRewardedAd",
this.onUserAcceptsRewardedAd.bind(this),
);
window.removeEventListener(
"rewardedAdCompleted",
this.onRewardedAdCompleted.bind(this),
);
window.removeEventListener(
"rewardedAdRewardGranted",
this.onRewardedAdRewardGranted.bind(this),
);
window.removeEventListener(
"rewardedCloseButtonTriggered",
this.onRewardedAdClosed.bind(this),
);
window.removeEventListener(
"userClosedWithRewardCanResolve",
this.onUserClosedWithReward.bind(this),
);
window.removeEventListener(
"rejectAdCloseCta",
this.onRejectAdClose.bind(this),
);
}
disconnectedCallback() {
super.disconnectedCallback();
// Clean up event listeners
window.removeEventListener(
"rewardedAdVideoRewardReady",
this.boundOnRewardedAdReady,
);
window.removeEventListener(
"userAcceptsRewardedAd",
this.boundOnUserAcceptsRewardedAd,
);
window.removeEventListener(
"rewardedAdCompleted",
this.boundOnRewardedAdCompleted,
);
window.removeEventListener(
"rewardedAdRewardGranted",
this.boundOnRewardedAdRewardGranted,
);
window.removeEventListener(
"rewardedCloseButtonTriggered",
this.boundOnRewardedAdClosed,
);
window.removeEventListener(
"userClosedWithRewardCanResolve",
this.boundOnUserClosedWithReward,
);
window.removeEventListener(
"rejectAdCloseCta",
this.boundOnRejectAdClose,
);
}

Comment on lines +155 to +166
<div class="mb-4">
<button
@click=${this._handleRewardedAd}
class="w-full px-4 py-3 text-base cursor-pointer ${this
.rewardedAdAvailable
? "bg-gradient-to-r from-purple-500/80 to-pink-500/80 hover:from-purple-500 hover:to-pink-500"
: "bg-gray-500/60 cursor-not-allowed"} text-white border-0 rounded transition-all duration-200 hover:-translate-y-px active:translate-y-px font-semibold"
?disabled=${!this.rewardedAdAvailable}
>
${this.rewardedAdAvailable ? "Rewarded Ad" : "Loading Ad..."}
</button>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Localize button copy.

Run through translateText() and add keys to en.json.

-            ${this.rewardedAdAvailable ? "Rewarded Ad" : "Loading Ad..."}
+            ${this.rewardedAdAvailable
+              ? translateText("win_modal.rewarded_ad_btn")
+              : translateText("win_modal.loading_ad")}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="mb-4">
<button
@click=${this._handleRewardedAd}
class="w-full px-4 py-3 text-base cursor-pointer ${this
.rewardedAdAvailable
? "bg-gradient-to-r from-purple-500/80 to-pink-500/80 hover:from-purple-500 hover:to-pink-500"
: "bg-gray-500/60 cursor-not-allowed"} text-white border-0 rounded transition-all duration-200 hover:-translate-y-px active:translate-y-px font-semibold"
?disabled=${!this.rewardedAdAvailable}
>
${this.rewardedAdAvailable ? "Rewarded Ad" : "Loading Ad..."}
</button>
</div>
<div class="mb-4">
<button
@click=${this._handleRewardedAd}
class="w-full px-4 py-3 text-base cursor-pointer ${this
.rewardedAdAvailable
? "bg-gradient-to-r from-purple-500/80 to-pink-500/80 hover:from-purple-500 hover:to-pink-500"
: "bg-gray-500/60 cursor-not-allowed"} text-white border-0 rounded transition-all duration-200 hover:-translate-y-px active:translate-y-px font-semibold"
?disabled=${!this.rewardedAdAvailable}
>
${this.rewardedAdAvailable
? translateText("win_modal.rewarded_ad_btn")
: translateText("win_modal.loading_ad")}
</button>
</div>
🤖 Prompt for AI Agents
In src/client/graphics/layers/WinModal.ts around lines 155 to 166, the button
text is hard-coded; replace the literal labels "Rewarded Ad" and "Loading Ad..."
with calls to translateText() using new keys (e.g. "winModal.rewardedAd" and
"winModal.loadingAd") and keep the conditional logic and disabled binding
intact; then add those keys with appropriate English strings to en.json so
translations load correctly.

Comment on lines +315 to +329
private initializeRewardedAd() {
if (!window.ramp) {
console.warn("Playwire RAMP not available for rewarded ads");
return;
}

// For out-of-the-box approach, we don't need complex initialization
// Just enable the button after a short delay to allow RAMP to load
setTimeout(() => {
console.log("🎬 Enabling rewarded ad button");
this.rewardedAdAvailable = true;
this.requestUpdate();
}, 2000);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Initialize by requesting the ad and wait for readiness; remove setTimeout.

Use window.ramp.requestAd and let the ready event flip the flag.

  private initializeRewardedAd() {
    if (!window.ramp) {
      console.warn("Playwire RAMP not available for rewarded ads");
      return;
    }
-
-    // For out-of-the-box approach, we don't need complex initialization
-    // Just enable the button after a short delay to allow RAMP to load
-    setTimeout(() => {
-      console.log("🎬 Enabling rewarded ad button");
-      this.rewardedAdAvailable = true;
-      this.requestUpdate();
-    }, 2000);
+    try {
+      // Let the rewardedAdVideoRewardReady event set rewardedAdAvailable=true
+      void window.ramp.requestAd("rewarded_ad_video");
+    } catch (e) {
+      console.warn("Failed to request rewarded ad", e);
+    }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private initializeRewardedAd() {
if (!window.ramp) {
console.warn("Playwire RAMP not available for rewarded ads");
return;
}
// For out-of-the-box approach, we don't need complex initialization
// Just enable the button after a short delay to allow RAMP to load
setTimeout(() => {
console.log("🎬 Enabling rewarded ad button");
this.rewardedAdAvailable = true;
this.requestUpdate();
}, 2000);
}
private initializeRewardedAd() {
if (!window.ramp) {
console.warn("Playwire RAMP not available for rewarded ads");
return;
}
try {
// Let the rewardedAdVideoRewardReady event set rewardedAdAvailable=true
void window.ramp.requestAd("rewarded_ad_video");
} catch (e) {
console.warn("Failed to request rewarded ad", e);
}
}
🤖 Prompt for AI Agents
In src/client/graphics/layers/WinModal.ts around lines 315 to 329, replace the
setTimeout-based enable flow with a proper RAMP ad request: call
window.ramp.requestAd() (or the SDK-equivalent request method), attach a
one-time listener for the ad-ready/ready event or promise resolution to set
this.rewardedAdAvailable = true and call this.requestUpdate(), remove the
listener after it fires, and log or handle errors if the request fails; keep the
early return if window.ramp is missing.

Comment on lines +353 to +397
private _handleRewardedAd() {
console.log("🎯 Rewarded Ad button clicked");

if (!window.ramp) {
console.warn("Playwire RAMP not available");
return;
}

// Disable button during playback
this.rewardedAdAvailable = false;
this.requestUpdate();

const modalConfig = {
title: "Earn a reward!",
confirmButtonText: "Watch the Video!",
backgroundOverlay: true,
backgroundColor: "",
confirmButtonColor: undefined,
titleColor: undefined,
logoSrc: "",
nameLogoSrc: "",
};

const confirmModalConfig = {
title: "Thank you for watching",
subTitle: "You have earned 2 coins!",
closeButtonText: "",
backgroundOverlay: false,
backgroundColor: "",
subTitleTextColor: "#FF0000",
buttonColor: undefined,
titleColor: "#FF0000",
logoSrc: "",
};

console.log(
"window.ramp methods:",
Object.getOwnPropertyNames(window.ramp),
);
window.ramp
.showRewardedVideoModal(modalConfig, confirmModalConfig)
.then(() => console.log("✅ Reward granted"))
.then(() => window.ramp.showRewardedVideoConfirmationModal())
.catch((error) => console.error("❌ Rewarded video error:", error));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Harden the playback flow: i18n, single confirmation, and re-enable in finally.

  • Localize all UI text.
  • Remove double confirmation.
  • Always re-enable button in finally.
   private _handleRewardedAd() {
     console.log("🎯 Rewarded Ad button clicked");

     if (!window.ramp) {
       console.warn("Playwire RAMP not available");
       return;
     }

     // Disable button during playback
     this.rewardedAdAvailable = false;
     this.requestUpdate();

     const modalConfig = {
-      title: "Earn a reward!",
-      confirmButtonText: "Watch the Video!",
+      title: translateText("win_modal.reward_title"),
+      confirmButtonText: translateText("win_modal.reward_cta"),
       backgroundOverlay: true,
       backgroundColor: "",
       confirmButtonColor: undefined,
       titleColor: undefined,
       logoSrc: "",
       nameLogoSrc: "",
     };

     const confirmModalConfig = {
-      title: "Thank you for watching",
-      subTitle: "You have earned 2 coins!",
+      title: translateText("win_modal.reward_thanks_title"),
+      subTitle: translateText("win_modal.reward_thanks_subtitle"),
       closeButtonText: "",
       backgroundOverlay: false,
       backgroundColor: "",
-      subTitleTextColor: "#FF0000",
+      subTitleTextColor: "",
       buttonColor: undefined,
-      titleColor: "#FF0000",
+      titleColor: "",
       logoSrc: "",
     };

-    console.log(
-      "window.ramp methods:",
-      Object.getOwnPropertyNames(window.ramp),
-    );
-    window.ramp
-      .showRewardedVideoModal(modalConfig, confirmModalConfig)
-      .then(() => console.log("✅ Reward granted"))
-      .then(() => window.ramp.showRewardedVideoConfirmationModal())
-      .catch((error) => console.error("❌ Rewarded video error:", error));
+    window.ramp
+      .showRewardedVideoModal(modalConfig, confirmModalConfig)
+      .then(() => {
+        console.log("✅ Reward completed");
+        // In case Playwire doesn’t emit reward event in some flows
+        this.grantReward();
+      })
+      .catch((error) => {
+        console.error("❌ Rewarded video error:", error);
+        this.showRewardError();
+      })
+      .finally(() => {
+        this.rewardedAdAvailable = true;
+        this.requestUpdate();
+      });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private _handleRewardedAd() {
console.log("🎯 Rewarded Ad button clicked");
if (!window.ramp) {
console.warn("Playwire RAMP not available");
return;
}
// Disable button during playback
this.rewardedAdAvailable = false;
this.requestUpdate();
const modalConfig = {
title: "Earn a reward!",
confirmButtonText: "Watch the Video!",
backgroundOverlay: true,
backgroundColor: "",
confirmButtonColor: undefined,
titleColor: undefined,
logoSrc: "",
nameLogoSrc: "",
};
const confirmModalConfig = {
title: "Thank you for watching",
subTitle: "You have earned 2 coins!",
closeButtonText: "",
backgroundOverlay: false,
backgroundColor: "",
subTitleTextColor: "#FF0000",
buttonColor: undefined,
titleColor: "#FF0000",
logoSrc: "",
};
console.log(
"window.ramp methods:",
Object.getOwnPropertyNames(window.ramp),
);
window.ramp
.showRewardedVideoModal(modalConfig, confirmModalConfig)
.then(() => console.log("✅ Reward granted"))
.then(() => window.ramp.showRewardedVideoConfirmationModal())
.catch((error) => console.error("❌ Rewarded video error:", error));
}
private _handleRewardedAd() {
console.log("🎯 Rewarded Ad button clicked");
if (!window.ramp) {
console.warn("Playwire RAMP not available");
return;
}
// Disable button during playback
this.rewardedAdAvailable = false;
this.requestUpdate();
const modalConfig = {
title: translateText("win_modal.reward_title"),
confirmButtonText: translateText("win_modal.reward_cta"),
backgroundOverlay: true,
backgroundColor: "",
confirmButtonColor: undefined,
titleColor: undefined,
logoSrc: "",
nameLogoSrc: "",
};
const confirmModalConfig = {
title: translateText("win_modal.reward_thanks_title"),
subTitle: translateText("win_modal.reward_thanks_subtitle"),
closeButtonText: "",
backgroundOverlay: false,
backgroundColor: "",
subTitleTextColor: "",
buttonColor: undefined,
titleColor: "",
logoSrc: "",
};
window.ramp
.showRewardedVideoModal(modalConfig, confirmModalConfig)
.then(() => {
console.log("✅ Reward completed");
// In case Playwire doesn't emit reward event in some flows
this.grantReward();
})
.catch((error) => {
console.error("❌ Rewarded video error:", error);
this.showRewardError();
})
.finally(() => {
this.rewardedAdAvailable = true;
this.requestUpdate();
});
}
🤖 Prompt for AI Agents
In src/client/graphics/layers/WinModal.ts around lines 353 to 397, replace
hard-coded UI strings with i18n lookups (use the component/app i18n utility for
title, button text, confirm modal title/subTitle/subTitleTextColor etc.), remove
the extra .then(() => window.ramp.showRewardedVideoConfirmationModal()) so only
the single confirmation from showRewardedVideoModal(confirmModalConfig) is used,
and ensure rewardedAdAvailable is re-enabled and requestUpdate() is called in a
.finally() handler on the promise chain so the button is always re-enabled
regardless of success or error; keep logging and error handling but move the
state reset into finally.

Comment on lines +424 to +428
private showRewardError() {
// Show user-friendly error message
console.log("❌ Unable to show ad at this time. Please try again later.");
// You could show a toast notification or modal here
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Localize the error message; prefer UI toast.

Run through translateText and route to your toast/modal helper.

-    console.log("❌ Unable to show ad at this time. Please try again later.");
+    console.log("❌", translateText("win_modal.reward_error"));
+    // TODO: toast.show(translateText("win_modal.reward_error"));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private showRewardError() {
// Show user-friendly error message
console.log("❌ Unable to show ad at this time. Please try again later.");
// You could show a toast notification or modal here
}
private showRewardError() {
// Show user-friendly error message
console.log("❌", translateText("win_modal.reward_error"));
// TODO: toast.show(translateText("win_modal.reward_error"));
// You could show a toast notification or modal here
}
🤖 Prompt for AI Agents
In src/client/graphics/layers/WinModal.ts around lines 424 to 428, the
showRewardError function currently logs a hard-coded English message to console;
update it to use the app's localization and UI notification path by calling
translateText with the appropriate key/message and then routing the translated
string into the existing toast/modal helper (e.g., showToast or showModal)
instead of console.log; ensure you import any missing helpers/types and keep
console logging only for debug if necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant