Skip to content

Shop items2#3699

Merged
todwadd merged 44 commits intomainfrom
shop-items2
Mar 5, 2026
Merged

Shop items2#3699
todwadd merged 44 commits intomainfrom
shop-items2

Conversation

@todwadd
Copy link
Collaborator

@todwadd todwadd commented Feb 24, 2026

SQL Migrations (4 files, I need to run them manually on prod still)
2026020501 — Adds metadata jsonb column to user_entitlements
2026020801 — Enables RLS on user_entitlements (public read) and user_bans (no public access)
2026022401 — Index on shop_orders(user_id, item_id) for purchase limit checks
2026022402 — RLS on shop_orders (users see own orders only), index on user_entitlements(entitlement_id), unique partial index enforcing one-time merch limits at DB level

New API Endpoints
shop-purchase-merch — Printful integration, charges mana + calls external API, auto-refunds on Printful failure. Uses PRINTFUL_API_KEY secret.
shop-shipping-rates — Calls Printful for shipping quotes
shop-update-metadata — Updates entitlement metadata (e.g. custom button text). Validated: max 10 keys, max 500 char values.
claim-charity-champion — Trophy claim/toggle for giveaway winners

Notifications (new types)
betting_streak_freeze_used — Fires when streak freeze is consumed
membership_subscription → expiring_soon — New subtype for sub expiry warning
charity_champion_dethroned / charity_champion_eligible — Giveaway trophy notifications

Backend Logic Changes
shop-purchase.ts — Achievement gating (checkItemRequirement) queries DB for loan balance, bet count, league history, etc. Tier downgrade credit calculation.
check-subscription-expiry.ts — New scheduled job, sends notifications when subs are expiring
reset-betting-streaks.ts — Modified to support streak freeze item consumption (changes the streak flame to show as ice if a streak was used, and send notification "freeze saved your x-day streak, y freezes left.)
get-charity-giveaway.ts — Expanded: champion data, leaderboard, trophy holder tracking
buy-charity-giveaway-tickets.ts — Ticket count now validated as .int()

todwadd and others added 30 commits February 4, 2026 21:42
When a streak freeze is used overnight to save a user's streak, they now
receive a notification letting them know:
- Their streak was saved
- How many freezes they have remaining

Also adds lastStreakFreezeTime to user data for tracking when the freeze
was used (will be used for frozen visual in a follow-up commit).
Warns users 2-3 days before their membership expires if:
- Auto-renew is off (they cancelled)
- Auto-renew is on but they don't have enough mana to renew

Runs daily at 10 AM UTC (2 hours after the renewal job).
When a streak freeze is used to save a streak, the UI now shows:
- Ice cube emoji (🧊) instead of fire
- "Frozen" label instead of "Streak"
- Blue/icy color filter on the display
- Explanatory message in the modal

This helps users understand their streak was saved and encourages
them to make a prediction to continue their streak.
…lity systems

Type extensions for shop items:
- Achievement requirements (streak, profit, loss, volume, donations, referrals)
- Team mutual exclusivity (red/green items disable opposite team)
- Seasonal date-gating (items available only during window around event date)

Backend validation in shop-purchase.ts and shop-toggle.ts.
UI support in shop.tsx for badges and disabled states.
New shop items:
- Custom YES Button (M$5000): Choose from PAMPU, BULLISH, LFG, SEND IT, etc.
- Custom NO Button (M$5000): Choose from DUMPU, BEARISH, GUH, NAH, etc.

Implementation:
- Add metadata field to UserEntitlement for storing selections
- New shop-update-metadata API endpoint for changing selections
- Update bet-panel and feed-bet-button to use getCustomYesButtonText/getCustomNoButtonText
- Shop UI shows selector dropdown for owned custom button items
- Legacy PAMPU skin still works (maps to custom YES = PAMPU)
…crophone)

Adds 6 new avatar overlay items with full rendering in avatar component
and shop preview. Includes propeller-spin animation for shop/hovercard
contexts, dark mode support for all hats, and restructured custom button
cycling arrows to prevent layout shift.
Restores the admin-only "Return All Cosmetics" button that was removed
during the subscription refactor. Refund now excludes supporter/subscription
entitlements. Adds metadata jsonb column to user_entitlements for storing
custom data like selected button text.
- Remove 'skin' from EXCLUSIVE_CATEGORIES so YES + NO buttons can be
  enabled simultaneously
- Add 'conflicts' field to ShopItem for explicit mutual exclusivity
  (pampu-skin ↔ custom-yes-button)
- Handle conflicts in shop-purchase, shop-toggle, and frontend
  optimistic updates
- Replace owned-state dropdown with arrow cycling + debounced metadata
  API (800ms) for smooth UX
- Remove useEffect that caused snap-back bug during rapid cycling
- Skip confetti for consumable and skin category purchases
- Document admin refund button disable procedure for prod deployment
- Mana Aura (M$75k): Purple-blue mystical energy glow
- Black Hole (M$200k): Dark swirling void with conic gradient
- Fire Ring (M$150k): Blazing flames, streak-gated (100 days)
- Bad Aura (M$25k): Crimson red glow (dark version of golden glow)
Avatar accessories (new category):
- Monocle (M$35k): Large gold monocle centered on avatar
- Crystal Ball (M$75k): Mystical orb in bottom-right corner
- YES/NO Thought Bubbles (M$25k each): Speech bubbles above avatar
- Stonks Up/Down (M$50k each): Meme-style stock charts, profit/loss gated

Visual improvements:
- Black hole: Gemini-generated swirling accretion disk with particles
- Fire ring: Gemini-generated swirling flames in red/orange/yellow
- Monocle: Bigger, thicker, more centered on profile
- Crystal ball: Positioned in corner, overlapping avatar
- Stonks: Mini stock chart panels instead of plain arrows
- Add avatar-accessory to display-config.ts EntitlementGroup
- Enable accessories in all relevant display contexts
- Improve accessory positioning (all in corners now)
- Update fire ring with dramatic flame tongues
- Update black hole with bright accretion disk
- Simplify stonks to circular badge with arrow
- Update shop previews to match actual appearance
- Rename fire-ring to fire-item across all files (id, types, components)
- Change category from avatar-border to avatar-accessory (combinable with auras)
- Replace grey smoke glow with fiery orange/red radial gradient
- Add fiery light cast overlay on avatar
- Fix z-index so profile expand chevron renders above flames
Adds /shop-preview page for iterating on team cap designs.
Contains two working designs:
- Classic Rally: Front view with rounded brim
- Tilted Rally: 3/4 angle view with visible side panel
…ents

Charity Champion Trophy:
- Add claim-charity-champion API endpoint for #1 ticket buyer to claim trophy
- Update get-charity-giveaway to return champion and trophy holder data
- Add CharityChampionCard UI component with claim/toggle functionality
- Add CharityChampionBadge display in user-link

Hovercard Enhancements:
- Add royal velvet curtain border effect (red velvet + gold trim)
- Add mana printer background SVG asset
- Improve hovercard background rendering

Avatar System:
- Expand avatar decoration rendering with new items
- Add trump-style cap SVG experiments for team caps

Shop Improvements:
- Add new shop items and helpers
- Improve purchase/toggle conflict handling
- Update follow button styling
…ckgrounds

- Charity champion: claim/toggle model with previousHolder tracking,
  former-champion permanent entitlement, mini leaderboard, provably fair nonce
- Hovercard backgrounds: champions-legacy, trading-floor overlays,
  floating trophy for current champion
- Black hole: softer glow (no disc on light mode), subtle purple border
- Shop: halo preview matches live SVG, 6 black hole design variants (E–I)
- Giveaway card: top-3 leaderboard, winner display, charity stats
…lish

- Crown: change to unique slot (combines with everything), add 3 position
  options (Right/Left/Center) with directional cycling UI
- Angel wings: change to unique slot, add hover tilt animation
- Avatar z-index layering: background effects (-3), angel wings (-2),
  halo back half (-1), avatar image (0), halo front half (auto)
- Jester hat: lighter purple flap, gold headband base
- Shop cards: remove pairing labels, keep slot pill badge only
- Profile avatar: clip blur to avatar circle dimensions
Replace the hue-rotate-180/brightness-110 CSS filter for frozen streaks
with an ice cube emoji (🧊) swap instead. Add ?previewFrozen=true query
param for testing the frozen state without an actual freeze.
…cards

- Notify previous trophy holder when dethroned by a new claimant
- Notify new #1 ticket buyer that they're eligible to claim the trophy
- Add UserHovercard + profile link for trophy holder and previous holder
- Crystal ball now requires 3 seasons finished at Platinum or higher
- Add seasonsPlatinum requirement type to shop system
…r thinned

- Add former-charity-champion as toggleable shop card (locked for non-holders)
- Redesign champions legacy overlay: higher quality trophy symbols, laurel
  wreaths, star accents, better spacing
- Reduce royal border inner red thickness (inset -5px → -3px)
…ools

- Add hidden field to ShopItem, mark 24 items as hidden (not ready for release)
- Remove team system entirely (types, helpers, purchase/toggle/UI logic)
- Delete team border items, keep red/green caps as regular hats
- Full price audit of all visible items
- Rename Jester Hat → Coolfold Jester Hat, Fedora → Bowler Hat
- Unhide Disguise (loan-gated), Flames now requires 100-day streak
- Add admin toggle to show/reveal hidden items with amber badges
- Add pink SEASONAL badge for seasonal items
- Update default shop item ordering
- Delete shop-preview.tsx dev page
- Re-enable admin testing tools
- Fix hover jitter with card wrapper padding
- Update Champion's Legacy locked text
- Add Printful merch integration (hidden): AGGC T-Shirt with size selector,
  shipping flow, and multi-step purchase modal
- Add shop-purchase-merch and shop-shipping-rates API endpoints
- Giveaway card now has 3 states: LIVE (active), ENDED (awaiting draw),
  and ENDED+winner (drawn) — each with mini leaderboard
- Update hidden item prices (halo, wings, bowler hat, mic, aura, monocle,
  thought bubbles, arrows, stonks)
- Rename Fedora → Bowler Hat, unhide Disguise item
- Flames now requires 100-day streak
- Add SEASONAL badge for seasonal items
- Champion's Legacy locked text updated
- Merch section in shop with admin hidden visibility
- Add White Logo Cap and Purple Logo Cap (hidden, M$3000 each)
- Add merchImages field for dynamic image carousels per merch item
- Auto-select size for single-variant items (caps), hide size picker
- Dynamic filter tabs: only show tabs with visible items
- Add Merch and Seasonal filter tabs (seasonal gets pink gradient)
- Admin showHidden toggle reveals all filter tabs
- Supporter discount now applies to merch purchases (backend + frontend)
- Shop cards stretch to equal height in grid rows
- Rename header to "Cosmetics & goods", update discount text everywhere
- Fix get-printful-variants script to accept token as CLI argument
…ID collisions

Bull horns: thicker curve with dual-bezier return edge, 4-stop gradient
(tan→golden brown→dark brown→near-black), unique gradient IDs via useId().
Cat ears: squat wide viewBox (24x18), further apart, shorter profile.
Tinfoil hat: conical shape with jagged crinkly brim, fold lines, metallic glint.
Santa hat gradient ID also made unique to prevent collisions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
hasCrown was missing from needsRelativeWrapper check, so the parent
div never got position:relative when crown was the only decoration.
- Green Cap and Black Cap marked hidden
- NEW sidebar badge dismisses on click via localStorage, bump SHOP_NEW_VERSION to re-show
- Merch items changed to one-time limit with backend enforcement
- Limit 1 per customer label with info tooltip on merch cards
- LEGENDARY badge restricted to halo, wings, crown only
- Top Hat: M15k -> M12.5k
- Coolfold Jester Hat: M25k -> M7.5k (shown as 50% off M15k, by Strutheo)
- Royal Velvet Border: M25k -> M12k
- Added originalPrice field to ShopItem type for sale/strikethrough pricing
- SALE badge + strikethrough in shop card and confirm modal
… endpoints

- shop-implementation-plan.md: marked implemented features, added severity-ranked
  audit findings (C1-C4, H1-H5, M1-M5) with fix descriptions and implementation order
- SHOP_SYSTEM.md: added shop-purchase-merch, shop-shipping-rates, claim-charity-champion,
  shop-update-metadata endpoint documentation with known issue cross-references
@IanPhilips
Copy link
Collaborator

Nice! Going to tackle this bit by bit, is there any reason the svgs in avatar and shop can't be shared code rather than duplicated in 2 places?

@todwadd
Copy link
Collaborator Author

todwadd commented Feb 26, 2026

Nice! Going to tackle this bit by bit, is there any reason the svgs in avatar and shop can't be shared code rather than duplicated in 2 places?

There were some quirks to this but I'm looking into it now and consolidating, I'll let you know if there's anything that actually blocks me from consolidating it all but it should be doable. It was mostly an artifact of a lazy method to side-by-side preview the changes as I iterated on the designs

…le bug and halo positioning

Extract 18 duplicated SVG definitions from avatar.tsx and shop.tsx into
shared canonical components in item-svgs.tsx. Both files now import the
same SVG art and wrap it with their own positioning/sizing — eliminating
~1,100 lines of duplicated code while keeping visuals identical.

Shared components: BlackHoleSvg, FireFlamesSvg, AngelWingSvg, MonocleSvg,
CrystalBallSvg, DisguiseSvg, ArrowBadgeSvg, StonksMemeArrowSvg,
BullHornSvg, BearEarSvg, CatEarSvg, SantaHatSvg, BunnyEarSvg,
WizardHatSvg, TinfoilHatSvg, JesterHatSvg, FedoraSvg, DevilHornSvg.

Also consolidates TrophySvg (used by user-hovercard and charity-champion-card).

All gradient IDs use React useId() to prevent SVG ID collisions when
multiple instances render on the same page.

Additional fixes:
- Fix shop-toggle crash: SELECT id → SELECT 1 (user_entitlements has no
  id column, uses composite PK). This broke ALL item toggling.
- Fix halo flying away when equipped alone: add hasHalo to
  needsRelativeWrapper so the absolute-positioned halo always has a
  relative parent container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move halo back half before the avatar image in the render tree so it
naturally layers behind. Previously it rendered after the avatar and
relied on zIndex: -1 which didn't work (wrapper div wasn't positioned).

Also always split halo into back/front halves even when no hat is
equipped, so the back arc goes behind the avatar in all cases.
Copy link
Collaborator

@IanPhilips IanPhilips left a comment

Choose a reason for hiding this comment

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

Mini review: all places where you are debiting the user's mana I think should probably be in a SERIAL_MODE transaction, ie use runTransactionWithRetries. I think this makes sure that the user's balance that we read at the beginning of the transaction is the same at the end. This is what we use in place-bet, etc.

@@ -20,11 +22,12 @@ export const buyCharityGiveawayTickets: APIHandler<

return await pg.tx(async (tx) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this be using SERIAL_MODE?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This makes sense! I'll have to try and remember that as a check when working on mana debits. I believe this is addressed now for the files within the PR.

Addresses PR review: all places debiting user mana now use
runTransactionWithRetries (SERIAL_MODE) to prevent balance race
conditions on concurrent requests.

Files converted from pg.tx() to runTransactionWithRetries():
- shop-purchase.ts (item purchases)
- shop-purchase-merch.ts (merch charge + refund)
- shop-reset-all.ts (admin refund)
- buy-charity-giveaway-tickets.ts (ticket purchases)
- process-membership-renewals.ts (auto-renewal debits)

Also moved side effects (notifications, log counters) outside
retryable callbacks to prevent duplicate sends on tx retry.
Copy link
Collaborator

@IanPhilips IanPhilips left a comment

Choose a reason for hiding this comment

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

Looking good! Just a few changes needed. I only reviewed the backend code, I trust any frontend bugs can get worked out later.

Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need to keep this around? seems like maybe SHOP_SYSTEM.md is enough?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fair enough, I moved the remaining useful stuff across to the shop_system doc. I have double checked to make sure but everything appears to be addressed properly

@IanPhilips
Copy link
Collaborator

Also, you know how to run the migrations once we merge this PR in?

- get-charity-giveaway: Promise.all → pg.multi for single round trip
- shop-purchase-merch: betsQueue.enqueueFn + runTxnOutsideBetQueue
- shop-purchase: same bet queue pattern, remove redundant FOR UPDATE
- shop-reset-all: same bet queue pattern
- shop-shipping-rates: deduplicate PRINTFUL_API_URL to common/shop/items
- shop-update-metadata: add FOR UPDATE (plain pg.tx, no bet queue)
- get-printful-variants: rewrite with runScript pattern
- Delete shop-implementation-plan.md, merge into SHOP_SYSTEM.md
@todwadd
Copy link
Collaborator Author

todwadd commented Mar 4, 2026

Also, you know how to run the migrations once we merge this PR in?

Yes, I ran the SQL on prod after we discussed in discord. Good to go I think!

@IanPhilips
Copy link
Collaborator

I don't see some of the changes yet! maybe haven't pushed?

@todwadd
Copy link
Collaborator Author

todwadd commented Mar 5, 2026

I don't see some of the changes yet! maybe haven't pushed?

all changes were pushed together here dc2ad96, I double checked each thing 1by1 as I resolved them, lmk if anything wasn't addressed

Copy link
Collaborator

@IanPhilips IanPhilips left a comment

Choose a reason for hiding this comment

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

Nice! a tiny number of extra changes and then you're good to go. for some reason the diff inspector on here was showing me old changes so i pulled the newest branch and inspected things from the ide. I did see 2 changes missing and one new change needed.

@IanPhilips
Copy link
Collaborator

Great, looks good to go, then! Yeah still saw some out of date stuff on the online diff viewer

@todwadd todwadd merged commit 16ee8ad into main Mar 5, 2026
6 checks passed
@todwadd todwadd deleted the shop-items2 branch March 5, 2026 23:48
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.

3 participants