Conversation
…in various components
…dd background overlay to PlanHeroCard and TotalValueCard
…enhance UI with skeleton loaders
…hart period handling with transitions
…atting across components
…rmanceGrid, and TotalValueCard; remove unnecessary elements for cleaner UI
…skeleton loaders for improved UI experience
…loops by falling back to wallet page
…d user interaction
…d components for improved user experience
… improved user experience
…ce across various components
…r improved visual consistency
…ints for improved user experience
…e transaction data handling
… performance metrics
#83) * feat: implement Telegram authentication flow with widget support for browser sign-in * feat: refactor Telegram platform hooks to use non-throwing functions for improved error handling * feat: implement Telegram OIDC authentication flow and update related components * feat: implement redirection for non-Telegram users after browser authentication and remove BrowserAuthenticatedScreen component * feat: enhance user data handling by integrating first and last name in useUser hook and updating verification components * feat: refactor DashboardTopBar to improve layout and integrate logout functionality for non-Telegram users
* feat: refactor wallet metrics and improve UI components for better performance and user experience * feat: update caching strategy and revalidation for news articles to enhance performance * feat: update favicon and add metadata for improved site identity * feat: implement PWA
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR adds PWA offline support, cache revalidation, shared wallet metrics, bot transaction activity, returning-user redirects, animated value rendering, and dashboard UI updates. ChangesApplication updates
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ActivityPage
participant BotHistoryHook
participant BotHistoryRoute
participant BotHistoryAPI
participant ActivityMapper
ActivityPage->>BotHistoryHook: Request bot transaction pages
BotHistoryHook->>BotHistoryRoute: Send bearer token and pagination
BotHistoryRoute->>BotHistoryAPI: Forward authenticated request
BotHistoryAPI-->>BotHistoryRoute: Return transaction data
BotHistoryRoute-->>BotHistoryHook: Return JSON response
BotHistoryHook->>ActivityMapper: Map transactions
ActivityMapper-->>ActivityPage: Return activity items
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
public/sw.js (1)
1-5: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy liftVersion precache entries for each release.
A deployment that changes
offline.htmlor an icon without changing/sw.jsdoes not install a new service worker. Existing clients then keep the oldbd-static-v1asset indefinitely.Generate precache revisions from the build output, or update
CACHE_VERSIONwhenever a precached or unversioned icon asset changes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@public/sw.js` around lines 1 - 5, Update CACHE_VERSION in the service worker whenever any PRECACHE_URLS asset changes, including offline.html or the icon files, so each release installs a new cache and refreshes existing clients; alternatively, generate CACHE_VERSION from the build output while preserving the existing static cache naming flow.src/lib/ease.ts (1)
5-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDerive
EASE_OUT_CSSfromEASE_OUT.The control points appear twice. If one changes, the other can drift. Build the CSS string from the tuple.
♻️ Proposed refactor
-export const EASE_OUT_CSS = "cubic-bezier(0.16, 1, 0.3, 1)"; +export const EASE_OUT_CSS = `cubic-bezier(${EASE_OUT.join(", ")})`;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/ease.ts` around lines 5 - 10, Update EASE_OUT_CSS to derive its cubic-bezier values from the EASE_OUT tuple instead of duplicating the control points, keeping the resulting CSS string unchanged.src/components/motion/number-ticker.tsx (2)
168-172: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBind the digit row height to
DIGIT_HEIGHT_EM.Line 169 hardcodes
h-[1.1em], but the container height (Line 159) and the translate offset (Line 164) useDIGIT_HEIGHT_EM. IfDIGIT_HEIGHT_EMchanges, the row height no longer matches the offset step, and the column shows the wrong digit. Tailwind cannot read the TypeScript constant, so use an inline style.♻️ Proposed refactor
{DIGITS.map((n) => ( - <span key={n} className="flex h-[1.1em] items-center justify-center leading-none"> + <span + key={n} + className="flex items-center justify-center leading-none" + style={{ height: `${DIGIT_HEIGHT_EM}em` }} + > {n} </span> ))}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/motion/number-ticker.tsx` around lines 168 - 172, Update the digit row span in the DIGITS.map rendering to use an inline height style derived from DIGIT_HEIGHT_EM instead of the hardcoded h-[1.1em] class. Keep the existing flex, alignment, and leading classes unchanged so the row height stays synchronized with the container and translate offset.
48-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive
armedinstead of setting state in an effect.The effect only copies
inViewinto state. It causes an extra render and triggers thereact-hooks/set-state-in-effecterror reported by ESLint.useInViewalready returns a render-safe value, andonce: truekeeps it latched.♻️ Proposed refactor
const containerRef = useRef<HTMLSpanElement>(null); const inView = useInView(containerRef, { once: true, amount: 0.6 }); - const [armed, setArmed] = useState(!startOnView); - - useEffect(() => { - if (startOnView && inView) setArmed(true); - }, [startOnView, inView]); + const armed = !startOnView || inView;Remove
useStatefrom the import if it becomes unused after both effects are reviewed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/motion/number-ticker.tsx` around lines 48 - 54, Update the armed value in the number ticker component to derive directly from startOnView and the latched inView result instead of storing it with useState or updating it in an effect. Remove the now-unnecessary armed state effect and remove useState from the import if no other state usage remains; preserve immediate activation when startOnView is false.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/dashboard/tasks/ManageTasksSection.tsx`:
- Around line 20-23: Update the action logic in ManageTasksSection around
isTelegramEnv and shareStory to treat null as a pending state: disable or show a
pending state until detection resolves, show the unavailable snackbar only when
isTelegramEnv === false, and do not attempt sharing while pending. Wrap the
shareStory invocation in error handling so failures from an unavailable WebApp
handle are caught and surfaced through the existing error state.
In `@src/lib/ease.ts`:
- Around line 51-52: Update the documentation comment for the dragged handles
and fills slider spring configuration to replace “butterily” with clear wording
and describe the configuration as overdamped rather than critically damped,
while retaining that the value follows the pointer and does not rebound at an
end.
---
Nitpick comments:
In `@public/sw.js`:
- Around line 1-5: Update CACHE_VERSION in the service worker whenever any
PRECACHE_URLS asset changes, including offline.html or the icon files, so each
release installs a new cache and refreshes existing clients; alternatively,
generate CACHE_VERSION from the build output while preserving the existing
static cache naming flow.
In `@src/components/motion/number-ticker.tsx`:
- Around line 168-172: Update the digit row span in the DIGITS.map rendering to
use an inline height style derived from DIGIT_HEIGHT_EM instead of the hardcoded
h-[1.1em] class. Keep the existing flex, alignment, and leading classes
unchanged so the row height stays synchronized with the container and translate
offset.
- Around line 48-54: Update the armed value in the number ticker component to
derive directly from startOnView and the latched inView result instead of
storing it with useState or updating it in an effect. Remove the now-unnecessary
armed state effect and remove useState from the import if no other state usage
remains; preserve immediate activation when startOnView is false.
In `@src/lib/ease.ts`:
- Around line 5-10: Update EASE_OUT_CSS to derive its cubic-bezier values from
the EASE_OUT tuple instead of duplicating the control points, keeping the
resulting CSS string unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ae7b367e-1556-4694-a6d4-cfd5fdd90771
⛔ Files ignored due to path filters (6)
public/apple-touch-icon.pngis excluded by!**/*.pngpublic/icons/icon-192.pngis excluded by!**/*.pngpublic/icons/icon-512.pngis excluded by!**/*.pngpublic/icons/icon-maskable-192.pngis excluded by!**/*.pngpublic/icons/icon-maskable-512.pngis excluded by!**/*.pngsrc/app/favicon.icois excluded by!**/*.ico
📒 Files selected for processing (19)
next.config.mjspublic/offline.htmlpublic/sw.jssrc/app/dashboard/news/[id]/page.tsxsrc/app/dashboard/news/page.tsxsrc/app/dashboard/page.tsxsrc/app/dashboard/plans/page.tsxsrc/app/layout.tsxsrc/app/manifest.tssrc/app/plans/choose/page.tsxsrc/app/register-sw.tsxsrc/components/dashboard/activity/ActivityRow.tsxsrc/components/dashboard/tasks/ManageTasksSection.tsxsrc/components/dashboard/wallet/TotalValueCard.tsxsrc/components/motion/number-ticker.tsxsrc/components/ui/plan-card.tsxsrc/hooks/query/useWalletMetrics.tssrc/lib/ease.tssrc/lib/news.ts
| const [snackbarError, setSnackbarError] = useState<string | null>(null); | ||
| const { openTelegramLink, shareStory } = useTMA(); | ||
| const { userID, count } = useStore(); | ||
| const isTelegramEnv = useIsTelegramEnv(); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Handle the pending Telegram-environment state separately.
useIsTelegramEnv() starts with null and resolves asynchronously in src/hooks/useIsTelegramEnv.ts, Lines 20-46. The !isTelegramEnv check treats the pending state as unavailable. A Telegram user can tap before detection completes and receive the error instead of sharing.
Show a pending state or disable the action until detection completes. Only show the unavailable snackbar when isTelegramEnv === false. Also catch shareStory, because useTMA can throw while its WebApp handle is still unavailable.
Suggested condition fix
- if (!isTelegramEnv) {
+ if (isTelegramEnv === null) {
+ setSnackbarError("Checking Telegram availability. Try again.");
+ return;
+ }
+ if (isTelegramEnv === false) {
setSnackbarError("Open this app inside Telegram to share a story.");
return;
}Also applies to: 41-44
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/dashboard/tasks/ManageTasksSection.tsx` around lines 20 - 23,
Update the action logic in ManageTasksSection around isTelegramEnv and
shareStory to treat null as a pending state: disable or show a pending state
until detection resolves, show the unavailable snackbar only when isTelegramEnv
=== false, and do not attempt sharing while pending. Wrap the shareStory
invocation in error handling so failures from an unavailable WebApp handle are
caught and surfaced through the existing error state.
| /** Dragged handles and fills (sliders) — critically damped `useSpring` config, | ||
| * so the value follows the pointer butterily and never rebounds off an end. */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the typo and the damping description.
"butterily" is not a word. Also, the config is overdamped, not critically damped: the damping ratio is 50 / (2 * sqrt(700 * 0.5)) ≈ 1.34. The no-rebound behavior still holds.
📝 Proposed wording
-/** Dragged handles and fills (sliders) — critically damped `useSpring` config,
- * so the value follows the pointer butterily and never rebounds off an end. */
+/** Dragged handles and fills (sliders) — overdamped `useSpring` config,
+ * so the value follows the pointer smoothly and never rebounds off an end. */📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** Dragged handles and fills (sliders) — critically damped `useSpring` config, | |
| * so the value follows the pointer butterily and never rebounds off an end. */ | |
| /** Dragged handles and fills (sliders) — overdamped `useSpring` config, | |
| * so the value follows the pointer smoothly and never rebounds off an end. */ |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/ease.ts` around lines 51 - 52, Update the documentation comment for
the dragged handles and fills slider spring configuration to replace “butterily”
with clear wording and describe the configuration as overdamped rather than
critically damped, while retaining that the value follows the pointer and does
not rebound at an end.
…87) * feat: add bot transaction history API and integrate into activity dashboard * refactor: streamline user registration flow and improve returning user handling * feat: add KYC status and subscription current hooks to dashboard layout * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/api/transaction/bot-history/route.ts`:
- Around line 34-41: Replace the direct fetch call in the bot-history route with
the repository fetchy wrapper imported from `@/lib/fetchy`, preserving the GET
method, Authorization and content-type headers, pagination query parameters in
apiUrl, and no-cache behavior.
In `@src/app/dashboard/activity/page.tsx`:
- Around line 56-59: Update the filtering predicate using itemCategory in the
activity list so category === "all" bypasses category filtering and retains
every item. Only compare itemCategory against category when category !== "all",
preserving the existing exclusion behavior for specific categories.
- Around line 39-48: The activity pagination logic currently excludes bot
history for the "all" category. Update showBotHistory and the related
hasNextPage/fetchNextPage flow so category "all" includes bot history while
preserving transactions-only behavior for "transactions".
In `@src/app/page.tsx`:
- Around line 193-215: Scope the returning-user marker to the authenticated
Telegram user and require authoritative validation before redirecting. In
src/app/page.tsx lines 193-215, match the cached identity to authData.user.id
and do not let the cached boolean alone trigger router.replace or block the
registration response; in src/lib/auth.ts lines 217-236, store or migrate the
marker as a user-scoped record containing the Telegram ID and remove the legacy
global boolean; in src/hooks/query/useRegisterTelegramUser.ts lines 31-34, pass
the authenticated Telegram user ID when updating or clearing that marker.
In `@src/components/dashboard/news/NewsListSection.tsx`:
- Line 43: Update the category span in NewsListSection to include the min-w-0
utility alongside truncate, allowing long article.category values to shrink and
truncate within the metadata flex row.
In `@src/hooks/query/useBotTransactionHistory.ts`:
- Around line 9-22: Move the BotTransaction interface from
useBotTransactionHistory.ts into src/lib/types.ts as the shared domain type.
Import BotTransaction from src/lib/types.ts in both useBotTransactionHistory and
activity.ts, removing the lib-to-hook dependency while preserving the existing
fields and contract.
- Around line 40-63: Update the useInfiniteQuery configuration in
useBotTransactionHistory to include a non-secret authenticated account
identifier in queryKeys.botTransactions, never the bearer token, so caches are
isolated per user. Also invalidate or remove this query during sign-out and
sign-in authentication transitions using the existing auth lifecycle handling.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ec8a8d22-4c89-4502-b827-63204f98dfaa
📒 Files selected for processing (13)
src/app/api/transaction/bot-history/route.tssrc/app/dashboard/activity/page.tsxsrc/app/dashboard/layout.tsxsrc/app/page.tsxsrc/components/dashboard/activity/ActivityRow.tsxsrc/components/dashboard/news/NewsListSection.tsxsrc/components/dashboard/plans/PlanHeroCard.tsxsrc/hooks/query/useBotTransactionHistory.tssrc/hooks/query/useRegisterTelegramUser.tssrc/lib/activity.tssrc/lib/auth.tssrc/lib/query-keys.tssrc/lib/types.ts
| const response = await fetch(apiUrl.toString(), { | ||
| method: "GET", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${token}`, | ||
| }, | ||
| cache: "no-cache", | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Use fetchy for the upstream request.
Line 34 bypasses the repository HTTP wrapper. Migrate this request to @/lib/fetchy and preserve the authorization header, pagination parameters, and cache behavior.
As per coding guidelines, “All HTTP requests must use the custom fetchy wrapper from @/lib/fetchy.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/api/transaction/bot-history/route.ts` around lines 34 - 41, Replace
the direct fetch call in the bot-history route with the repository fetchy
wrapper imported from `@/lib/fetchy`, preserving the GET method, Authorization and
content-type headers, pagination query parameters in apiUrl, and no-cache
behavior.
Source: Coding guidelines
| const showDca = category !== "transactions"; | ||
| const showBotHistory = category === "transactions"; | ||
| const isLoading = | ||
| (showDca && dca.isLoading) || (showBotHistory && botHistory.isLoading); | ||
| const hasNextPage = (showDca && dca.hasNextPage) || (showBotHistory && botHistory.hasNextPage); | ||
| const isFetchingNextPage = dca.isFetchingNextPage || botHistory.isFetchingNextPage; | ||
| const fetchNextPage = () => { | ||
| if (showDca && dca.hasNextPage) dca.fetchNextPage(); | ||
| if (showBotHistory && botHistory.hasNextPage) botHistory.fetchNextPage(); | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Load both histories in the all category.
showBotHistory is false for all. Therefore, hasNextPage and fetchNextPage never load bot pages after the initial page. Include bot history when category === "all".
Proposed fix
- const showDca = category !== "transactions";
- const showBotHistory = category === "transactions";
+ const showDca = category === "all" || category === "plans";
+ const showBotHistory = category === "all" || category === "transactions";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const showDca = category !== "transactions"; | |
| const showBotHistory = category === "transactions"; | |
| const isLoading = | |
| (showDca && dca.isLoading) || (showBotHistory && botHistory.isLoading); | |
| const hasNextPage = (showDca && dca.hasNextPage) || (showBotHistory && botHistory.hasNextPage); | |
| const isFetchingNextPage = dca.isFetchingNextPage || botHistory.isFetchingNextPage; | |
| const fetchNextPage = () => { | |
| if (showDca && dca.hasNextPage) dca.fetchNextPage(); | |
| if (showBotHistory && botHistory.hasNextPage) botHistory.fetchNextPage(); | |
| }; | |
| const showDca = category === "all" || category === "plans"; | |
| const showBotHistory = category === "all" || category === "transactions"; | |
| const isLoading = | |
| (showDca && dca.isLoading) || (showBotHistory && botHistory.isLoading); | |
| const hasNextPage = (showDca && dca.hasNextPage) || (showBotHistory && botHistory.hasNextPage); | |
| const isFetchingNextPage = dca.isFetchingNextPage || botHistory.isFetchingNextPage; | |
| const fetchNextPage = () => { | |
| if (showDca && dca.hasNextPage) dca.fetchNextPage(); | |
| if (showBotHistory && botHistory.hasNextPage) botHistory.fetchNextPage(); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/dashboard/activity/page.tsx` around lines 39 - 48, The activity
pagination logic currently excludes bot history for the "all" category. Update
showBotHistory and the related hasNextPage/fetchNextPage flow so category "all"
includes bot history while preserving transactions-only behavior for
"transactions".
| const itemCategory = getActivityCategory(item.type); | ||
| if (category === "all" ? itemCategory === "transactions" : itemCategory !== category) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Correct the all category predicate.
When category === "all", Line 57 returns false for every "transactions" item. This excludes all mapped bot transfers from the all-activity view. Only apply the category comparison when category !== "all".
Proposed fix
- if (category === "all" ? itemCategory === "transactions" : itemCategory !== category) {
+ if (category !== "all" && itemCategory !== category) {
return false;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const itemCategory = getActivityCategory(item.type); | |
| if (category === "all" ? itemCategory === "transactions" : itemCategory !== category) { | |
| return false; | |
| } | |
| const itemCategory = getActivityCategory(item.type); | |
| if (category !== "all" && itemCategory !== category) { | |
| return false; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/dashboard/activity/page.tsx` around lines 56 - 59, Update the
filtering predicate using itemCategory in the activity list so category ===
"all" bypasses category filtering and retains every item. Only compare
itemCategory against category when category !== "all", preserving the existing
exclusion behavior for specific categories.
| const [cachedReturningUser] = useState(() => getIsExistingUserFromStorage()); | ||
|
|
||
| // /api/user (Mongo-backed) already fires on every launch — read its | ||
| // result instead of ignoring it. status 409 means this Telegram id was | ||
| // already registered, i.e. a returning user, who skips straight to the | ||
| // dashboard. It only fires when the profile has a username; without one, | ||
| // treat the visitor as new rather than waiting on a call that never runs. | ||
| const canCheckExisting = !!(authData?.user?.id && authData?.user?.username); | ||
| const isReturningUser = cachedReturningUser || registerUser.data?.status === 409; | ||
| const checkPending = | ||
| !cachedReturningUser && | ||
| canCheckExisting && | ||
| registerUser.data === undefined && | ||
| !registerUser.isError; | ||
|
|
||
| useEffect(() => { | ||
| if (isReturningUser) { | ||
| router.replace("/dashboard?tab=wallet"); | ||
| } | ||
| }, [isReturningUser, router]); | ||
|
|
||
| if (checkPending || isReturningUser) { | ||
| return <PageShell>{null}</PageShell>; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Do not redirect from an unscoped and unvalidated cached flag.
A flag written for one Telegram account redirects any later account on the same device. A stale flag also redirects before a 201 response can clear it. cachedReturningUser remains true for the current mount, so clearing localStorage does not stop router.replace().
src/app/page.tsx#L193-L215: Redirect only after the cached identity matches the current Telegram ID and an authoritative check remains valid. Do not treat the boolean alone as proof.src/lib/auth.ts#L217-L236: Store the Telegram user ID with the marker, or replace the marker with a user-scoped record. Remove or migrate the legacy global boolean.src/hooks/query/useRegisterTelegramUser.ts#L31-L34: Pass the authenticated Telegram user ID when updating or clearing the user-scoped marker.
📍 Affects 3 files
src/app/page.tsx#L193-L215(this comment)src/lib/auth.ts#L217-L236src/hooks/query/useRegisterTelegramUser.ts#L31-L34
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/page.tsx` around lines 193 - 215, Scope the returning-user marker to
the authenticated Telegram user and require authoritative validation before
redirecting. In src/app/page.tsx lines 193-215, match the cached identity to
authData.user.id and do not let the cached boolean alone trigger router.replace
or block the registration response; in src/lib/auth.ts lines 217-236, store or
migrate the marker as a user-scoped record containing the Telegram ID and remove
the legacy global boolean; in src/hooks/query/useRegisterTelegramUser.ts lines
31-34, pass the authenticated Telegram user ID when updating or clearing that
marker.
| <Clock size={12} className="shrink-0" /> | ||
| <span className="shrink-0">{article.readTimeMinutes} min read</span> | ||
| <span className="shrink-0">·</span> | ||
| <span className="truncate">{article.category}</span> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
fd -a 'NewsListSection\.tsx|news\.ts|tailwind\.config|global\.css|index\.css' . | sed 's#^\./##'
echo "== NewsListSection outline =="
ast-grep outline src/components/dashboard/news/NewsListSection.tsx --view expanded 2>/dev/null || true
echo "== NewsListSection relevant content =="
cat -n src/components/dashboard/news/NewsListSection.tsx | sed -n '1,120p'
echo "== news.ts relevant content =="
if [ -f src/lib/news.ts ]; then
cat -n src/lib/news.ts | sed -n '1,220p'
fi
echo "== CSS flex/truncate definitions if Tailwind classes present =="
rg -n "\.truncate|flex-shrink|flex-grow|overflow-hidden|text-ellipsis|min-w-0|flex-1" -S . -g '!node_modules' -g '!dist' -g '!build' | sed -n '1,200p'
echo "== Tailwind/PostCSS config search =="
for f in tailwind.config.js tailwind.config.ts postcss.config.js postcss.config.mjs vite.config.ts next.config.js next.config.mjs; do
[ -f "$f" ] && { echo "--- $f"; sed -n '1,220p' "$f"; }
doneRepository: CeyLabs/BitcoinDeepaBot-TMA
Length of output: 18839
🌐 Web query:
Tailwind CSS truncate class flex item min-w-0 text-overflow-ellipsis overflow-hidden documentation
💡 Result:
In Tailwind CSS, the truncate class is a utility used to truncate overflowing text with an ellipsis [1][2]. It is a shorthand that applies the following CSS properties to an element [1][3]: overflow: hidden; text-overflow: ellipsis; white-space: nowrap; When using truncate within a flex container, you may encounter an issue where the flex item does not shrink as expected, causing the layout to break or the ellipsis to fail to appear [4][5][6]. This occurs because flex items have a default min-width of auto (similar to min-content), which prevents them from shrinking smaller than their internal content size [5][6]. To resolve this and allow the element to truncate properly, you must apply the min-w-0 class to the flex item [7][5]. The min-w-0 class sets min-width: 0px;, overriding the default flex behavior and allowing the item to shrink to accommodate the truncation [7][5]. Example of correct usage:
Citations:
- 1: https://tailwindcss.com/docs/text-overflow
- 2: https://windframe.dev/tailwind/classes/tailwind-truncate
- 3: https://dusktools.app/tailwind/truncate
- 4: Truncate not working tailwindlabs/tailwindcss#12468
- 5: .flex-1 (and possibly others) requires min-width: 0 tailwindlabs/tailwindcss#809
- 6: https://css-tricks.com/flexbox-truncated-text/
- 7: https://dusktools.app/tailwind/min-w-0
🌐 Web query:
Tailwind CSS v4 truncate flex item min-w-0 min-w-0 flex-1 truncate documentation
💡 Result:
To truncate text within a flex container in Tailwind CSS, you must override the default flex item behavior that prevents content from shrinking below its natural size. The Core Problem By default, flex items have a min-width of auto [1][2]. This forces them to be at least as wide as their content, which prevents truncation from working even when the truncate utility (which sets overflow: hidden, text-overflow: ellipsis, and white-space: nowrap) is applied [3][1][2]. The Standard Solution: min-w-0 To allow a flex item to shrink and properly truncate, you must explicitly set its minimum width to 0 [4][1][2]. Example implementation:
This very long text will now truncate properly instead of overflowing the flex container.
Citations:
- 1: https://samyar.me/posts/truncate-properly/
- 2: https://whats.win/blog/use-truncate-to-hidden-overflow-text
- 3: https://tailwindcss.com/docs/text-overflow
- 4: https://dusktools.app/tailwind/min-w-0
- 5: https://stackoverflow.com/questions/71093772/how-to-truncate-text-in-tailwindcss
- 6: https://tailwindcss.com/blog/tailwindcss-v4-1
Make the category flex item shrinkable.
Feed categories are assigned from item.categories?.[0], so long category values can make the metadata row overflow. Add min-w-0 to the category <span> so truncate can work in the flex container.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/dashboard/news/NewsListSection.tsx` at line 43, Update the
category span in NewsListSection to include the min-w-0 utility alongside
truncate, allowing long article.category values to shrink and truncate within
the metadata flex row.
| export interface BotTransaction { | ||
| id: number; | ||
| time: string; | ||
| direction: "incoming" | "outgoing"; | ||
| from_id: number; | ||
| to_id: number; | ||
| from_user?: string; | ||
| to_user?: string; | ||
| type: string; | ||
| amount: number; | ||
| amount_lkr: string; | ||
| memo?: string; | ||
| success: boolean; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Move BotTransaction to src/lib/types.ts.
BotTransaction is a domain API contract. src/lib/activity.ts now imports it from a query hook. Define it in src/lib/types.ts, then import it into both modules. This removes the lib-to-hook dependency.
As per coding guidelines, “src/lib/types.ts: Use the core domain types defined in src/lib/types.ts.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/hooks/query/useBotTransactionHistory.ts` around lines 9 - 22, Move the
BotTransaction interface from useBotTransactionHistory.ts into src/lib/types.ts
as the shared domain type. Import BotTransaction from src/lib/types.ts in both
useBotTransactionHistory and activity.ts, removing the lib-to-hook dependency
while preserving the existing fields and contract.
Source: Coding guidelines
| const query = useInfiniteQuery({ | ||
| queryKey: queryKeys.botTransactions, | ||
| queryFn: async ({ pageParam }) => { | ||
| const data = await fetchy.get<BotTransactionsResponse>( | ||
| `/api/transaction/bot-history?limit=${PAGE_SIZE}&offset=${pageParam}`, | ||
| { | ||
| headers: { Authorization: `Bearer ${authToken}` }, | ||
| shouldCache: false, | ||
| } | ||
| ); | ||
|
|
||
| const transactions = data.success ? data.transactions : []; | ||
| const hasMore = data.success && data.offset + transactions.length < data.count; | ||
|
|
||
| return { | ||
| transactions, | ||
| nextOffset: data.offset + PAGE_SIZE, | ||
| hasMore, | ||
| }; | ||
| }, | ||
| initialPageParam: 0, | ||
| getNextPageParam: (lastPage) => (lastPage.hasMore ? lastPage.nextOffset : undefined), | ||
| enabled: !!authToken, | ||
| staleTime: 1000 * 60 * 5, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Scope the query cache to the authenticated user.
queryKeys.botTransactions has no user identity. If one user signs out and another user signs in, TanStack Query can return the first user’s cached transfers during the five-minute stale window. Add a non-secret account identifier to the query key and clear this query during authentication transitions. Do not use the bearer token as a query-key value.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/hooks/query/useBotTransactionHistory.ts` around lines 40 - 63, Update the
useInfiniteQuery configuration in useBotTransactionHistory to include a
non-secret authenticated account identifier in queryKeys.botTransactions, never
the bearer token, so caches are isolated per user. Also invalidate or remove
this query during sign-out and sign-in authentication transitions using the
existing auth lifecycle handling.
Summary by CodeRabbit