Repository navigation
Conversation
…erorg#915) Adds a dedicated read for the connected wallet's buy-cooldown expiry, a shared ticking countdown hook, and a locked-out Buy button state on the creator key detail page. Scope: - KeyBuyCooldown + courseService.getKeyBuyCooldown (GET /keys/:keyId/buy-cooldown) and the useKeyBuyCooldown query hook, disabled until a wallet is connected so the read fires on key page load and again on wallet connect - useBuyCooldownCountdown, the single source of truth for remaining cooldown time, so the timer and the Buy button can never disagree; BuyCooldownCountdown now builds on it - Buy panel shows the countdown in-panel, disables the button, surfaces the remaining time on the button and in an aria-describedby hint, re-enables on the tick that crosses zero, and re-reads the cooldown at expiry - No cooldown (null/404/absent) leaves the normal Buy UI with no timer; a failed read never blocks buying Refs accesslayerorg#915
|
@meem08 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! 🚀 |
Member
|
Conflict |
…rg#915) The merge of dev into this branch left two malformed sections: - CreatorDetailPage: the buy-panel JSX was duplicated by the merge (PR's cooldown-aware button plus dev's TradeCooldownButton), leaving a mangled subtitle ternary and stray closing tags. - course.service.ts: the getKeyBuyCooldown catch block was truncated and getTradeCooldownStatus was spliced into its body. Reconcile the buy panel into a single button that layers the states: deprecated -> whitelist lockout -> per-wallet buy cooldown (accesslayerorg#915) -> TradeCooldownButton (accesslayerorg#998). Restore the getKeyBuyCooldown method body.
…y-cooldown-countdown # Conflicts: # src/pages/CreatorDetailPage.tsx
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.
Closes #915
Summary
The creator key detail page now shows a live per-wallet buy cooldown countdown and locks the Buy Key button until the connected wallet's cooldown expires.
Today the page renders a
BuyCooldownCountdownchip sourced from whatevernextBuyAllowedAthappens to be on the caller's held position, but nothing gates the buy action - the button stays fully enabled during a cooldown, so a user can open the trade dialog and get as far as signing a transaction the contract will reject. The countdown is also rendered outside the buy panel, disconnected from the action it is supposed to gate.This PR makes the cooldown a real, enforced gate:
aria-describedby.Changes
New - cooldown read
src/services/course.service.tsKeyBuyCooldowntype +getKeyBuyCooldown(keyId, wallet)?GET /keys/:keyId/buy-cooldown?wallet=.src/hooks/useKeyBuyCooldown.tssrc/lib/queryKeys.tsqueryKeys.creators.buyCooldown(creatorId, wallet)- wallet-scoped keyThe query is
enabled: Boolean(keyId && wallet), which is what gives both required triggers for free:enabledfrom false to true mounts the query mid-session, so connecting a wallet while the key page is open triggers the read without a reload.Because the cache key includes the wallet, switching wallets cannot leak one wallet's cooldown into another's view.
New - shared countdown clock
src/hooks/useBuyCooldownCountdown.ts{ remainingSeconds, formattedRemaining, isActive }The key design point: the timer and the button read from the same source of truth. Both derive from the same absolute
nextBuyAllowedAt, so the chip and the button can never disagree, andisActiveflips tofalseon the exact tick that crosses zero - which is precisely what re-enables the button.Other properties worth noting:
onExpirefires once per expiry and is held in a ref, so passing an inline callback does not restart the interval.BuyCooldownCountdownwas refactored onto this hook; its props,data-testids,role="status", andaria-labelare unchanged, so it stays usable standalone.Changed - key detail page
src/pages/CreatorDetailPage.tsx:disabledwhile a cooldown is active, switches to theoutlinevariant, and its label becomesBuy in 4m 32sso the remaining time is visible on the action itself (mirroring the existingBuy Disabled (Deprecated)pattern). A supportingUnlocks in .line is rendered as the button'saria-describedbytarget.Buy cooldown active for your wallet.while locked.Behaviour matrix
Buy in <remaining>Buy Keynull/ 404 / absent)Buy KeyBuy Key- a failed read never blocks buyingBuy KeyBuy Disabled (Deprecated)A read failure deliberately does not gate the buy button.
retry: falseplus a 404?nullmapping means an absent endpoint degrades to today's behaviour rather than locking every user out of a key.Tests
23 new tests, all passing:
src/hooks/__tests__/useKeyBuyCooldown.test.tsx- reads for the connected wallet;nullis a successful "no cooldown" rather than an error; does not fetch until a wallet is connected, then fetches on connect; a wallet switch re-reads instead of reusing the previous cache entry; no fetch without a key id.src/hooks/__tests__/useBuyCooldownCountdown.test.tsx- inactive with no data and no timer allocated; accepts seconds/ms epochs; ticks down; deactivates on the tick that crosses zero;onExpirefires exactly once and stops the timer; an already-expired cooldown never firesonExpire; an inline callback does not restart the interval; recomputes on a new expiry.src/pages/__tests__/CreatorDetailPage.buyCooldown.integration.test.tsx- end-to-end coverage of every acceptance criterion, driving the real query layer and only stubbing the service call: fetch on load, fetch on connect, timer + disabled button while locked, disabled button cannot open the trade dialog, auto re-enable at expiry followed by a working buy, re-read at expiry, and the three no-cooldown cases (null, empty, read failure).Verification
pnpm lint- clean.pnpm build(tsc -b+vite build) - clean.CreatorDetailPagesuites and all 5 cooldown-related suites pass in full.Note on the "from contract" wording
The issue asks for the expiry to be read "from contract". This repo has no client-side contract-read infrastructure for creator keys - every key-detail read (
#951config/spread,#967oracle,#975performance bond,#960vesting) goes throughcourseService, with the backend reading the contract. The new endpoint follows that same convention: it reads the creator'sset_buy_cooldownwindow and the caller's last-buy ledger on-chain, and the client never talks to a contract client directly. This is also the shape the dashboard's existing cooldown panel (#889) assumes.Design notes
isActiveoverremaining > 0scattered in the component. The alternative - letting the component own the timer and lifting only a boolean up - was rejected because the two would then be independent tickers. One hook, one clock.refetchIntervalwould keep the endpoint hot for every visitor even when no cooldown exists. Re-reading on the single meaningful transition costs one request per cooldown, and only for wallets that actually have one.aria-describedbyover a tooltip. The lockout reason is state a screen-reader user must not miss; a tooltip is hover-gated and would leave the disabled button unexplained.