Commit Graph

29 Commits

Author SHA1 Message Date
c0mrade
d7f7bc7c17 fix(cabinet): Lava top-up return route + Telegram-unavailable card overflow
- Lava returns to a path-based result URL (/balance/top-up/result/:method) because Lava
  Business rejects query strings in success/fail URLs. Add the route and read the method
  from the path param as a fallback (alongside ?method=) so external-browser redirects
  still poll the right payment. Pairs with the backend fix.
- ConnectedAccounts: the "Привязка Telegram временно недоступна" message overflowed the
  card (it sat in the non-shrinking action column and never wrapped). Constrain its width
  and wrap it (max-w + break-words) so it stays inside the card. Verified by rendering.

Both reported in the Bedolaga bug topic.
2026-06-10 16:44:46 +03:00
c0mrade
7383505a12 fix(connected-accounts): stack status + unlink so the full ID shows on mobile
Truncating the Google/Discord sub id with an ellipsis hid it. Stack the
status badge above the unlink button (flex-col) on the right so the right
column is narrow and the identifier gets the room to render in full;
truncate stays only as a safety net for extreme values.
2026-06-04 23:17:57 +03:00
c0mrade
c39c164d51 fix(connected-accounts): keep status/unlink inside the card on long IDs
A long Google sub (numeric, ~21 digits) pushed 'Привязан' and 'Отвязать'
past the right edge of the account card on mobile. Classic flex overflow:
the left identifier column had no min-w-0, the identifier <p> no truncate,
and the right actions no shrink-0. Add min-w-0 + truncate to the text column
and shrink-0 to the actions so the id ellipsizes and the controls stay put.
2026-06-04 22:53:51 +03:00
c0mrade
3224320095 feat(connected-accounts): email-merge confirmation via emailed one-time code
When linking an email that belongs to another account, the backend now mails a
one-time code to it (merge_verification='email_code') instead of asking for that
account's password. Add a 6-digit code step: on the email_code response show the
code input, verify via authApi.verifyEmailMerge → on success redirect to /merge.
Preview/execute already go through apiClient (JWT), so they satisfy the new
initiator-bound merge endpoints. i18n in ru/en/zh/fa.
2026-06-04 19:06:54 +03:00
c0mrade
7d31dc3ffa fix(connected-accounts): guide the user to enter the existing account's password when merging by email
The backend account-takeover fix now requires the existing account's password
before merging an email that already belongs to another account. Show a clear
message (profile.emailMergePasswordRequired) on that response instead of the
generic "already registered" dead-end, so the legitimate owner knows to enter
that account's password (in both password fields) to complete the merge. i18n
in ru/en/zh/fa.
2026-06-04 14:06:45 +03:00
c0mrade
cf469a1a9f chore(types): replace deprecated FormEvent with SyntheticEvent
React 19's @types/react flags FormEvent as deprecated — quote: 'FormEvent
doesn't actually exist. You probably meant to use ChangeEvent, InputEvent,
SubmitEvent, or just SyntheticEvent'. All 17 call sites in this repo
typed form onSubmit handlers and only called e.preventDefault(), so
SyntheticEvent is the correct general replacement.

No behavior change — pure type-level cleanup that clears the chronic
deprecation hint that has been showing up after every edit to any
form-bearing page.
2026-05-26 15:24:01 +03:00
c0mrade
23e4e9b4d1 fix(profile,connected-accounts): drop dead ['user'] invalidate calls
queryClient.invalidateQueries({ queryKey: ['user'] }) appears twice in
the codebase but no useQuery with that key is registered anywhere — the
auth user is a zustand store, not a React Query cache entry. The
invalidations were silent no-ops that suggested wiring that did not
exist.

The actual refresh path (await getMe + setUser) was already in place
right above each invalidate, so the cleanup is purely cosmetic +
documentation: replace with a comment explaining where the user state
actually lives, so the next reader doesn't try to invalidate it again.
2026-05-26 13:58:33 +03:00
c0mrade
207af81c95 fix: support 100% discount display + fix Telegram link widget race condition
- formatPrice shows "Бесплатно/Free" when price is 0 (SubscriptionPurchase, Subscription, RenewSubscription)
- Add subscription.free locale key (ru/en/zh/fa)
- Fix TelegramLinkWidget NS_BINDING_ABORTED: move legacy widget callback to ref pattern, remove unstable deps (showToast, t, navigate) from useEffect — prevents iframe destruction on re-render
- Clean up OIDC effect deps
2026-04-03 17:23:38 +03:00
Fringg
7892630e3b fix: add key prop to AnimatePresence, accessibility labels, remove hardcoded fallback
- Added key="email-link-form" on motion.div inside AnimatePresence
  for proper exit animations
- Added htmlFor/id pairs on all three label+input groups for
  screen reader accessibility
- Replaced hardcoded "your@email.com" placeholder with neutral format
- Removed hardcoded English fallback from t('profile.invalidEmail')
2026-04-02 07:16:00 +03:00
Fringg
6fd0668a24 feat: move email linking form into Connected Accounts page
Users couldn't find the email linking form buried in the Profile page.
Now the Email row in Connected Accounts shows a "Привязать" button
(consistent with Google/Yandex/Discord) that expands an inline form
with email, password, and confirm password fields.

- Added email to isLinkableProvider check
- Inline AnimatePresence form expands under Email card (no popups)
- Removed duplicate registration form from Profile page
- Profile page now shows a redirect button to Connected Accounts
  for users without email (email change/verify stays in Profile)
2026-04-02 07:06:38 +03:00
Fringg
6a1a9f5db7 feat: deep link авторизация при блокировке oauth.telegram.org
Добавлен fallback через бота когда виджет Telegram не загружается:
- Таймаут 8 сек на загрузку скрипта (OIDC + legacy)
- Автоматический переход на deep link auth
- Polling каждые 2.5 сек до подтверждения в боте
- ConnectedAccounts: таймаут + сообщение при недоступности
- Переводы: ru, en, zh, fa
2026-03-17 21:29:21 +03:00
c0mrade
62188b8d2e fix: resolve all 14 ESLint warnings across the codebase
- Add varsIgnorePattern to no-unused-vars for destructuring patterns
- Fix all react-hooks/exhaustive-deps by adding missing dependencies
- Refactor useAnimatedNumber to use ref instead of stale state closure
- Wrap handleLinkResult in useCallback for stable deps
- Extract OAuth utilities from OAuthCallback.tsx to utils/oauth.ts
- Extract background config utilities to utils/backgroundConfig.ts
- Remove unused catch parameter in GiftSubscription
2026-03-13 20:19:09 +03:00
Fringg
880b2d45fe fix: support OIDC mode in TelegramLinkWidget for account linking
TelegramLinkWidget used legacy widget only, ignoring OIDC config.
Now queries widgetConfig from backend, supports OIDC popup flow with
id_token, and falls back to bot_username from server config.
2026-03-09 06:23:02 +03:00
Fringg
32091d3648 fix: replace deprecated Telegram Login Widget redirect with callback
Telegram deprecated the data-auth-url redirect flow, which returns a
blank page with "deprecated" text. Switch both TelegramLinkWidget
(account linking) and TelegramLoginButton (legacy login) to use
data-onauth callback approach that works client-side without redirects.
2026-03-07 16:15:20 +03:00
Fringg
2c65ca8a7f feat: migrate Telegram Login Widget to v23 with admin-configurable settings
- Upgrade telegram-widget.js from v22 to v23 (TelegramLoginButton + ConnectedAccounts)
- TelegramLoginButton fetches widget config from API instead of hardcoded values
- Add TelegramWidgetConfig type and API method with fallback to env defaults
- Remove botUsername prop — now sourced from backend API
- Add useEffect cleanup to prevent orphaned iframes on unmount
- Add TELEGRAM_WIDGET category to admin interface settings menu
- Add i18n translations for widget settings (ru, en, zh, fa)
2026-03-07 01:48:18 +03:00
Fringg
2fc0759f89 feat: account merge flow — merge redirect, error handling, server-complete linking
- Добавлен merge_required + merge_token redirect в OAuthCallback и LinkTelegramCallback
- Server-complete OAuth linking для Mini App (внешний браузер)
- getErrorDetail: единый экстрактор ошибок из axios response
- LinkTelegramCallback: типизированный catch с getErrorDetail
- Удалён мёртвый экспорт getAndClearOAuthState
- CSRF валидация для Telegram и OAuth linking flows
- Типы ServerCompleteResponse, LinkCallbackResponse
2026-03-05 05:25:50 +03:00
Fringg
0d99ea0069 refactor: review round 2 — remove dead code, fix type safety, improve UX
- Remove LinkOAuthCallback.tsx (dead code) and its route from App.tsx
- Move LINK_OAUTH_* constants to OAuthCallback.tsx
- Replace local ServerLinkResult with LinkCallbackResponse from types
- Move merge navigate() from render body to useEffect
- Track errorMode to show correct CTA (Return to Telegram vs Back to Login)
- Remove non-null assertions with proper narrowing
- Extract getErrorDetail helper for error casting
- Add smart polling success detection (stop + toast when linked count increases)
2026-03-05 03:10:22 +03:00
Fringg
da1926f0e1 fix: review findings — polling fallback, sessionStorage cleanup, UX
- Add 5s polling for 90s after opening external browser link (Mini App)
- Show info toast "Continue in browser" when opening external OAuth
- Fix premature sessionStorage cleanup: peek-then-clear pattern
- Use useIsTelegram() hook instead of direct isInTelegramWebApp()
- Add continueInBrowser i18n key (ru, en, zh, fa)
2026-03-05 02:33:33 +03:00
Fringg
7c30a1eab6 feat: open OAuth linking in external browser from Telegram Mini App
- Rewrite OAuthCallback.tsx to handle 3 modes: login, link-browser,
  and link-server (external browser without JWT)
- Add linkServerComplete API method (no JWT, auth via state token)
- Update ConnectedAccounts to use platform.openLink() in Mini App
- Add server-complete endpoint to AUTH_ENDPOINTS (skip Bearer token)
- Enable refetchOnWindowFocus for linked-providers query
- Add returnToTelegram/openTelegram i18n keys (ru, en, zh, fa)
2026-03-05 02:24:28 +03:00
Fringg
a6fabb1d9d feat: add Telegram account linking UI with CSRF protection
- TelegramLinkWidget component with Login Widget for browser users
- One-click initData linking for Mini App users
- LinkTelegramCallback page with CSRF state validation
- URL credential clearing via history.replaceState
- Merge flow support with replace navigation
- i18n keys for all 4 locales (ru, en, zh, fa)
2026-03-04 17:03:48 +03:00
Fringg
8ad0500cc8 fix: double-click guard on link, wall-clock timer, blur cleanup
- Add linkingProvider state to prevent double-click on Link button
- Use wall-clock based countdown timer to prevent drift
- Clean up onBlur setTimeout ref on unmount
2026-03-04 16:05:24 +03:00
Fringg
fba4481799 fix: move useState before useMutation for consistent hook ordering 2026-03-04 15:57:04 +03:00
Fringg
71198ab18a refactor: extract shared ProviderIcon, fix canUnlink hiding all buttons
- Extract TelegramIcon + EmailIcon to shared ProviderIcon component
- Remove duplicated icons from ConnectedAccounts and MergeAccounts
- Remove isPending from canUnlink (buttons already have disabled prop)
- Add onSettled to reset confirmingUnlink state after mutation
2026-03-04 15:46:53 +03:00
Fringg
3418ba9b8d fix: prevent onBlur race cancelling unlink confirmation
Add 150ms delay before resetting confirmation state so the click
event fires before blur resets it.
2026-03-04 15:35:45 +03:00
Fringg
d0c01a0e5c fix: replace window.confirm with inline confirmation for unlink
window.confirm() is silently suppressed in Telegram Mini Apps and iOS
WebView, making unlink completely non-functional on mobile. Replaced
with two-click inline confirmation: first click shows destructive
"Confirm disconnect?" button, second click executes unlink. Button
resets on blur.
2026-03-04 15:30:00 +03:00
Fringg
e447e993cb fix: accessibility, query cache clear, post-merge navigation
- Add aria-hidden="true" to all decorative SVGs (TelegramIcon, EmailIcon
  in MergeAccounts + ConnectedAccounts, all 4 OAuthProviderIcon SVGs)
- Add role="radiogroup" with aria-label to subscription choice container
- Clear react-query cache after merge to prevent stale data
- Wrap checkAdminStatus() in try-catch to prevent throw after token set
- Navigate to /profile/accounts after merge instead of /
- Import useQueryClient for cache management
2026-03-04 15:02:08 +03:00
Fringg
aa26059e00 fix: second round review fixes for merge UI
- Fix false success toast when response.success is false (CRITICAL)
- Remove mergeToken! non-null assertion in queryFn
- Add early return for missing mergeToken param
- Zero-pad minutes in formatCountdown (MM:SS format)
- Clamp negative seconds in formatCountdown
- Block all unlink buttons while any unlink mutation is pending
- Clear OAuth state only after validation succeeds (not before)
- Split getAndClearLinkOAuthState into read + clear functions
2026-03-04 07:56:19 +03:00
Fringg
58cf1e3b50 fix: harden merge UI and improve error handling
- MergeAccounts: guard mutationFn against null token/userId
- MergeAccounts: call checkAdminStatus after successful merge
- MergeAccounts: add fallback when success but tokens null
- MergeAccounts: fix ErrorState showing "expired" for all errors
- MergeAccounts: fix formatCountdown dead ternary and hardcoded English
- MergeAccounts: add staleTime/refetchOnWindowFocus to preview query
- MergeAccounts: remove manual useCallback (React Compiler handles it)
- LinkOAuthCallback: add toast feedback on all error/success paths
- ConnectedAccounts: use invalidateQueries instead of refetch
- ConnectedAccounts: add error state rendering
- ConnectedAccounts/LinkOAuthCallback: share sessionStorage key constants
- i18n: add missing profile.accounts.linking key to zh and fa locales
2026-03-04 07:47:36 +03:00
Fringg
93f97d45be feat: account linking and merge UI for cabinet
Add Connected Accounts page (link/unlink OAuth providers), Link OAuth
Callback handler, and Merge Accounts page with subscription comparison
and user choice. Includes API methods, TypeScript types, routes in
App.tsx, navigation from Profile, and i18n for all 4 locales (ru, en,
zh, fa). Merge page works without JWT auth (validated by merge token).
2026-03-04 07:25:45 +03:00