- Click-drag: grab cursor, drag left/right to scroll
- Mouse wheel: vertical scroll converts to horizontal pan
- Arrow buttons (< >) appear at edges when more pills are off-screen,
hide when scrolled to the end
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pay-form: overflow:hidden -> overflow:visible so it no longer blocks
horizontal touch events on the pkg-scroll child
- pay-form-header: gets its own top border-radius to maintain visual
corner clipping without relying on parent overflow:hidden
- pkg-scroll: added touch-action:pan-x so browser hands horizontal
swipe gestures to this element rather than the page scroll
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Platform cards: replaced inline onclick with data attributes + event
listeners to avoid JSON double-quote quoting bug that broke clicks
- Onboarding: reverted to original yes/no -> request accounts flow,
removed alias-entry step that didn't work
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After saving aliases, flow continues to account request step so players
can also request logins for platforms they don't have yet.
'Done' button on alias step still allows early exit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Removed Play Now (▶) button from profile aliases tab — Play Now section
on home page is the one place to launch platforms
- Onboarding: "Yes I have existing logins" now leads to a new alias-entry
step where the player enters their username per active platform; saved
on submit, dismisses onboarding
- "No, I want new logins" still goes to the account-request step
- Back arrow on each step returns to the opening question
- obHideAll/obGoBack helpers keep step transitions clean
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- platforms table gets url_alias_param column (configurable per platform)
- Admin game form has new "Username URL Param" field — leave blank if platform
doesn't support it, or set to e.g. "username" if it does
- Platform cards now use onclick openPlatform() instead of plain href:
copies player's saved alias to clipboard on every click, and if
url_alias_param is set appends ?param=alias to the launch URL
- Toast notification confirms "Alias copied — paste into login"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- payout_methods list now includes admin_enabled flag via JOIN on admin_payout_settings
- Disabled methods appear at 45% opacity with UNAVAILABLE/DISABLED BY ADMIN badge
in both the cashout radio list and profile payout tab; radio is disabled so they
can't be selected, but the delete button remains
- Set Default button hidden on disabled methods
- cashout.php server-side guard rejects submissions using a disabled method type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cashout_method_types list now JOINs admin_payout_settings so the
player's profile payout dropdown only shows types the admin can
actually process.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rocket Loader defers our main script, causing DOMContentLoaded to fire
before our listener is registered — so buildPlatforms(), buildPaymentMethods()
etc. never run. Adding Cache-Control: no-transform tells Cloudflare not to
rewrite the response, disabling Rocket Loader for this page.
Also adds buildPaymentMethods() to showApp() alongside buildPlatforms() so
payment methods are guaranteed to render when the user logs in, regardless
of fetch timing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two issues:
1. Race condition: me.php resolves before the platforms fetch, so
showApp() runs with an empty CFG.platforms and the grid stays
blank until the Promise.all resolves. Fixed by calling
buildPlatforms() inside showApp() when platforms are already
loaded, guaranteeing the grid renders when the app becomes visible.
2. Stale placeholder: 'links' platform (is_active=1) was polluting
the grid with a "Platform Links coming..." card. Disabled in DB
(is_active=0).
Also hardened buildPlatforms/buildCashoutPlatforms: null-guard on
selects, early return when CFG.platforms is empty, and clear select
options before re-populating to prevent duplicates on re-call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Players now paste the URL of their post instead of just clicking a
platform button. The server fetches the URL and looks for the player's
referral code in the page content. If found, the share is auto-approved
and tokens are awarded immediately. If not (login wall, private page,
code missing), it falls into the pending queue with a reason so admins
can click the link directly for manual review.
- api/referrals.php: replace submit_share with URL-accepting version;
add scrapeForReferralCode() (SSRF-guarded cURL, 8s timeout, 512KB cap)
and inferPlatformFromUrl() helpers
- db/schema.sql: add share_url, auto_verified, verify_result columns
- index.php: replace platform buttons with URL input form; show auto-
verify result inline; shares list shows URL and auto-verify badge
- admin/index.php: share cards show clickable URL, auto-check result
label, and auto-verified tag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two concurrent loadAdminReferrals() calls shared the same DOM container,
so whichever fetch resolved last would overwrite the other's result.
Added a request ID counter (_refListReqId) so stale responses are
discarded rather than applied.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Every case that wasn't actually sending email had copy-pasted the
email-send result check, causing all those actions (delete_pending,
payment_settings_update, etc.) to always return 'Failed to send reset
email' even on success.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api/backup.php: list/create/download/delete backups; streams zip directly
for downloads; 7-backup rolling prune on each create
- Each backup is a single zip containing all of public_html + a full
mysqldump of tomt_ttg_db
- Cron at 2 AM daily via /usr/local/bin/ttg-backup.sh (already installed)
- Admin UI: 💾 Backups nav item under System section; shows backup list
with date/size, Download + Delete per row; Create Backup Now button
with live status; auto-loads when section is opened
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
requireAdmin() already guards the whole file; the extra check was
referencing an undefined variable that always evaluated false.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New section below pending purchases/cashouts: one square card per
active platform showing net credit balance, completed purchase count,
and sent cashout count. Loads on page load alongside other dashboard
data. Credits turn yellow below 100 and red at/below 0 with a warning.
Clicking a card jumps to Game Management and opens that platform's
credit modal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a pending purchase is resolved as completed:
- Inserts a debit row into platform_credits for the matching platform
(joins token_purchases.platform_id slug → platforms.id)
- Debit notes include purchase #, player name, username, token count, amount, method
- Total shown in credit modal now subtracts debits from credits (net balance)
Credit history table updates:
- CREDIT/DEBIT type badges, debit rows tinted red with − prefix
- Debit rows show "Purchase #X ↗" button that closes modal, jumps to
the Purchases section (all tab), and highlights that purchase row
- Edit/delete buttons hidden on auto-generated debit rows
Also fixes: resolve_purchase was echoing $sent (undefined variable bug)
Also fixes: purchaseCard div now has id="pr-N" so jump-highlight works
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
saveCreditEntry and deleteCreditEntry were using apiFetch() which routes
to /api/admin.php, but credits_create/update/delete only exist in
/api/platforms.php — causing the Unknown action error on every save.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- resetGameForm() now called every time Game Management section is opened,
preventing stuck edit state (disabled slug, wrong form title)
- Added prominent Add New Game button at top of section (master admin only)
- DOMContentLoaded init ensures form state is correct on page load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB: added is_deleted, deleted_at columns to platforms table
- Soft delete: archive button moves games to archived section instead of hard delete
- Archived section: master admin can restore (reactivates) or permanently delete
- Slug reuse: creating a game with an archived slug reactivates the old record
- New game form: master admin always sees add form + agent info; other admins hidden
- Edit: non-master admins have form card revealed on edit
- Delete/Add buttons: only visible to master admin
- api/platforms.php: public and admin_list queries exclude archived games
- api/admin.php: platforms_archived, platforms_restore, platforms_purge actions added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: saves went through admin.php which still used old console_url column
and had broken response using undefined $sent variable (always returned error).
- api/admin.php: platforms_create/update/delete fully rewritten with all agent
fields, master-admin gating, and correct json_encode responses
- api/admin.php: update now sets updated_at=NOW() on save
- admin/index.php: game cards show last-edited date (✏️ from updated_at)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agent Info: master admin sees full edit form; other admins see view-only panel with Copy and Open URL buttons
- Credit Accounting: master admin can manage entries; other admins see total only (Manage Credits button hidden)
- API: credits_create/update/delete require master admin; platform update strips agent fields for non-master
- Players: suspend/delete buttons disabled when viewing master admin account (UI + JS guards)
- URL fields (Agent Link, Games Link): open-in-new-tab arrow button added in both edit and view modes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB: added sub_agent_login, sub_agent_password, cashier_login, cashier_password to platforms table
- API: create/update handle all 4 new fields
- Admin: Sub-Account and Cashier sections added inside Agent Info box; game list cards display all new fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB: renamed console_url to agent_link, added agent_login, agent_password, games_link, agent_guide to platforms table
- api/platforms.php: create/update now handles all 5 agent fields (admin-only)
- admin/index.php: game form has new Agent Info section (purple, admin-only styling); game list cards show all agent fields inline; JS saveGame/editGame/resetGameForm updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pending_signups stat and list queries now filter username != __reset__
so active password-reset rows no longer inflate the signup counter or
appear in the admin pending-signups list
- send_password_reset now calls sendPasswordResetEmail() from mailer.php
instead of building a plain-text cybermailSend() call inline; the
wrapper sends a branded dark-theme HTML email matching the verification
email style
Previously the endpoint always returned success:true regardless of
whether the email was actually delivered. Now captures the bool return
value and returns success:false with an error message if CyberMail
fails, so the admin knows to retry rather than assuming delivery.
Handles the /reset_password.php?token=... URL generated by the
admin send_password_reset action. Flow:
- GET: validates token against pending_registrations (username=__reset__,
not expired), shows set-new-password form
- POST: re-validates token, enforces 6-char min + confirm match,
bcrypt-hashes the new password, updates users.password by email,
deletes the pending row to prevent reuse
- Invalid/expired token shows a clear error with link back to home
Matches the dark gaming aesthetic of verify.php.
The INSERT had two compounding bugs:
1. ".?" in the VALUES clause — a PHP dot inside a double-quoted string
is a literal character, not concatenation. MySQL saw it as a syntax
error and the INSERT always failed silently (no try/catch).
2. The token column had the literal string __reset__ hardcoded instead
of a ? placeholder, so even if the INSERT had run, the real random
token would never have been stored — the reset link always invalid.
Fix: VALUES ("__reset__","",?,?,?,?) with execute(alias,email,token,exp)
giving 4 placeholders for 4 params, all columns correctly bound.