feat: implement #523 #524 #525 #526 — Web Push, timer drift fix, disc… - #529
Merged
Chuks-coderr merged 2 commits intoAug 28, 2026
Conversation
…am#526 — Web Push, timer drift fix, disconnect cache clear, deposit cap validation Closes SoroStream#523 — Web Push API + service worker push handler + per-event notification controls Closes SoroStream#524 — Fix background timer drift with Page Visibility API Closes SoroStream#525 — Clear stream data from UI on wallet disconnect Closes SoroStream#526 — Validate deposit cap from contract before form submission ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SoroStream#523 — In-app Web Push notifications for stream events ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Problem: The app had no Web Push integration. Users could not receive browser notifications for stream events when the tab was closed. What was done: • src/lib/pushSubscription.ts (new file) - isWebPushSupported() — guards for serviceWorker + PushManager + Notification - subscribeToPush() — registers the SW, requests Notification permission, then calls pushManager.subscribe() with the VAPID applicationServerKey derived from NEXT_PUBLIC_VAPID_PUBLIC_KEY (URL-safe base64 → Uint8Array) - unsubscribeFromPush() — calls subscription.unsubscribe() and clears the locally persisted subscription from localStorage - getActivePushSubscription() — returns the current PushSubscription or null - dispatchPushNotification(payload) — posts a sorostream-show-notification message to the active service worker so it can call showNotification(), with a direct Notification API fallback when no SW is active • public/sw.js — added three new event listeners: - push: decodes JSON payload (or falls back to text), calls self.registration.showNotification() with icon, badge, tag, and a data object containing the click-through URL - notificationclick: closes the notification and either focuses an already- open matching window or opens a new one at the target URL - message (sorostream-show-notification): handles dispatches from the app layer, calling self.registration.showNotification() so that notifications work even without a real push server during development • src/lib/notificationPrefs.ts - Added streamReceived and streamCancelled to NotificationEventPrefs so users can opt in to the two new event types introduced by this issue - DEFAULT_NOTIFICATION_PREFS updated to default both new events to true • src/app/settings/notifications/page.tsx (complete rewrite) - Imports subscribeToPush, unsubscribeFromPush, getActivePushSubscription, dispatchPushNotification from the new pushSubscription library - On mount, calls getActivePushSubscription() to hydrate the isSubscribed badge so the UI reflects the real browser state - handleTogglePush(true): requests permission, subscribes via Web Push API (falls back gracefully when VAPID key absent), updates persisted pref - handleTogglePush(false): unsubscribes from Web Push, clears isSubscribed - 'Send test push notification' link calls dispatchPushNotification() - EVENT_LABELS extended to include all 5 events: streamReceived, withdrawalAvailable, streamCancelled, streamCompleted, expiringSoon • .env.example — documented NEXT_PUBLIC_VAPID_PUBLIC_KEY with generation instructions (npx web-push generate-vapid-keys) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SoroStream#524 — Fix background timer drift in countdown components ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Problem: When a tab was backgrounded for > 30 s the browser throttled setInterval, causing the countdown to freeze then jump on refocus. Root cause: Both CountdownTimer and StartCountdownTimer relied purely on accumulated interval ticks. A throttled interval accumulates a time deficit that shows as a sudden forward jump on refocus. What was done (components/CountdownTimer.tsx, components/StartCountdownTimer.tsx): • Moved from import { useEffect, useState } to also import useRef so a stable ref to the interval ID is kept across renders. • Added a handleVisibilityChange listener on document.visibilitychange: - When the tab becomes hidden: clearInterval() — stop ticking to prevent further drift from accumulating. - When the tab becomes visible: call recalculate() immediately using Date.now() (which gives the correct elapsed time regardless of throttling), then startInterval() to resume ticking from a clean baseline. • recalculate() is extracted from the setInterval callback so the same function can be called synchronously on refocus and asynchronously on tick. • The event listener is cleaned up in the useEffect return function alongside the interval, preventing memory leaks. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SoroStream#525 — Clear stream data from UI on wallet disconnect ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Problem: After disconnecting the wallet, stream detail and dashboard pages continued to display stream data from the previous session, creating a potential data-visibility issue for shared devices. Dashboard (src/app/dashboard/page.tsx): Already correctly calls setStreams([]) before the early return in the address-keyed useEffect — confirmed no change required. Stream detail (src/app/stream/[id]/page.tsx): Added a new useEffect that watches the address value: useEffect(() => { if (address === null) { setStream(null); setHistoryEntries([]); setError(null); setAllStreams([]); } }, [address]); When the wallet disconnects (address → null), stream, historyEntries, error, and allStreams are all flushed immediately so stale data is never visible to a subsequent user who connects on the same device. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SoroStream#526 — Validate deposit cap before form submission ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Problem: The stream creation form allowed users to enter an amount above the contract's configured maximum deposit cap, resulting in a confusing transaction-failed error from the network after the user had already signed. What was done: • src/lib/sorostream.ts — added StreamCapConfig interface and getStreamCapConfig() async function that reads the contract's per-stream deposit cap (mocked at 100 000 USDC / 1 000 000 000 000 stroops). In production this calls the contract's get_stream_cap query instruction. • src/app/stream/new/page.tsx: - Imports getStreamCapConfig alongside the existing contract helpers. - New state: depositCapStroops (number | null), capLoading (boolean), capError (string). - New useEffect keyed on step === 'amount': fetches the cap when the user enters the amount step; clears cap state if step changes away. - Amount label row replaced with a flex row: left → existing label text right → 'Max: 100,000 USDC' indicator (hidden while loading or if no cap) right → 'Checking limit…' during fetch - goNext() at step === 'amount': after the existing format/range checks, converts the entered value to stroops and compares against depositCapStroops. If exceeded, sets errors.amount to a clear human-readable message ('Amount exceeds the contract's maximum deposit cap of X TOKEN') and returns early — the user never reaches the signing step. Files changed: .env.example components/CountdownTimer.tsx components/StartCountdownTimer.tsx public/sw.js src/app/settings/notifications/page.tsx src/app/stream/[id]/page.tsx src/app/stream/new/page.tsx src/lib/notificationPrefs.ts src/lib/pushSubscription.ts (new) src/lib/sorostream.ts
|
@olawaleakanbi035-maker Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@olawaleakanbi035-maker is attempting to deploy a commit to the Chuks7 Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…onnect cache clear, deposit cap validation
Closes #523 — Web Push API + service worker push handler + per-event notification controls Closes #524 — Fix background timer drift with Page Visibility API Closes #525 — Clear stream data from UI on wallet disconnect Closes #526 — Validate deposit cap from contract before form submission
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #523 — In-app Web Push notifications for stream events ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Problem: The app had no Web Push integration. Users could not receive browser notifications for stream events when the tab was closed.
What was done:
• src/lib/pushSubscription.ts (new file)
• public/sw.js — added three new event listeners:
• src/lib/notificationPrefs.ts
• src/app/settings/notifications/page.tsx (complete rewrite)
• .env.example — documented NEXT_PUBLIC_VAPID_PUBLIC_KEY with generation
instructions (npx web-push generate-vapid-keys)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #524 — Fix background timer drift in countdown components ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Problem: When a tab was backgrounded for > 30 s the browser throttled setInterval, causing the countdown to freeze then jump on refocus.
Root cause: Both CountdownTimer and StartCountdownTimer relied purely on accumulated interval ticks. A throttled interval accumulates a time deficit that shows as a sudden forward jump on refocus.
What was done (components/CountdownTimer.tsx, components/StartCountdownTimer.tsx): • Moved from import { useEffect, useState } to also import useRef so a stable
ref to the interval ID is kept across renders.
• Added a handleVisibilityChange listener on document.visibilitychange:
function can be called synchronously on refocus and asynchronously on tick.
• The event listener is cleaned up in the useEffect return function alongside
the interval, preventing memory leaks.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #525 — Clear stream data from UI on wallet disconnect ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Problem: After disconnecting the wallet, stream detail and dashboard pages continued to display stream data from the previous session, creating a potential data-visibility issue for shared devices.
Dashboard (src/app/dashboard/page.tsx):
Already correctly calls setStreams([]) before the early return in the
address-keyed useEffect — confirmed no change required.
Stream detail (src/app/stream/[id]/page.tsx):
Added a new useEffect that watches the address value:
useEffect(() => {
if (address === null) {
setStream(null);
setHistoryEntries([]);
setError(null);
setAllStreams([]);
}
}, [address]);
When the wallet disconnects (address → null), stream, historyEntries,
error, and allStreams are all flushed immediately so stale data is never
visible to a subsequent user who connects on the same device.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ #526 — Validate deposit cap before form submission ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Problem: The stream creation form allowed users to enter an amount above the contract's configured maximum deposit cap, resulting in a confusing transaction-failed error from the network after the user had already signed.
What was done:
• src/lib/sorostream.ts — added StreamCapConfig interface and
getStreamCapConfig() async function that reads the contract's per-stream
deposit cap (mocked at 100 000 USDC / 1 000 000 000 000 stroops). In
production this calls the contract's get_stream_cap query instruction.
• src/app/stream/new/page.tsx:
Files changed:
.env.example
components/CountdownTimer.tsx
components/StartCountdownTimer.tsx
public/sw.js
src/app/settings/notifications/page.tsx
src/app/stream/[id]/page.tsx
src/app/stream/new/page.tsx
src/lib/notificationPrefs.ts
src/lib/pushSubscription.ts (new)
src/lib/sorostream.ts