feat: new button near cart#141
Conversation
WalkthroughReplaces top-bar buttons in Navigation with catalog-specific buttons. Adds a new AverageProgressButton component with a donut progress indicator and drawer. Updates CartButton to hide title when the average-progress button is shown. Extends useNavigation to expose isAverageProgressButtonShown. Changes menu call flow to a showPopup confirmation and branching actions. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant MenuPage as Menu Page
participant SDK as SDK showPopup
participant Clipboard as Clipboard
participant Browser as Browser
User->>MenuPage: Tap "Call"
Note right of MenuPage: Trigger vibration (unchanged)
MenuPage->>SDK: showPopup.isAvailable()
alt Available
MenuPage->>SDK: showPopup({ title, message, buttons:[copy, recall, close] })
SDK-->>MenuPage: buttonId
alt buttonId == "copy"
MenuPage->>Clipboard: writeText(formattedNumber)
else buttonId == "recall"
MenuPage->>Browser: window.open("tel:+...")
else close/other
Note over MenuPage: No further action
end
else Not available
Note over MenuPage: Skip popup (no call flow here)
end
sequenceDiagram
autonumber
actor User
participant AvgBtn as AverageProgressButton
participant Nav as useNavigation
participant Drawer as UDrawer
participant Timer as Interval(4s)
Timer-->>AvgBtn: Update progress (0..99)
User->>AvgBtn: Click button
AvgBtn->>AvgBtn: useFeedback().vibrate()
AvgBtn->>Drawer: Toggle open (v-model:open)
AvgBtn->>Nav: isNavigationShown = !isDrawerOpened
Note over AvgBtn,Drawer: Donut + gift overlay rendered when isAverageProgressButtonShown
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Pre-merge checks (2 warnings, 1 inconclusive)❌ Failed checks (2 warnings, 1 inconclusive)
Poem
✨ Finishing Touches
🧪 Generate unit tests
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 (15)
apps/storefront-telegram/app/pages/menu.vue (5)
70-89: Add a non-Telegram fallback and localize strings.
- No fallback when
showPopup.isAvailable()is false; users get no action.- Button texts and message should use i18n, not literals.
Apply:
- if (showPopup.isAvailable()) { - const buttonId = await showPopup({ - title: formatted, - message: 'Выберите действие', - buttons: [ + if (showPopup.isAvailable()) { + const buttonId = await showPopup({ + title: formatted, + message: t('menu.call.chooseAction'), + buttons: [ { - id: 'copy', - type: 'default', - text: 'Скопировать', + id: 'copy', + type: 'default', + text: t('common.copy'), }, { - id: 'recall', - type: 'default', - text: 'Нужен обратный звонок', + id: 'recall', + type: 'default', + text: t('menu.call.requestCallback'), }, { type: 'close', }, ], }) + } else { + // Fallback outside Telegram + window.location.href = formattedToCall }And import/use i18n at top:
+import { useI18n } from 'vue-i18n' +const { t } = useI18n()
91-93: Await clipboard write and handle errors.Unawaited promise can silently fail (permissions/HTTP context). Provide feedback or fallback.
- if (buttonId === 'copy') { - navigator.clipboard.writeText(formatted) - } + if (buttonId === 'copy') { + try { + await navigator.clipboard.writeText(formatted) + } catch (e) { + console.warn('Clipboard copy failed', e) + } + }
94-96: Verify dial action in Telegram WebApp; prefer SDK helper or location redirect.
window.open('tel:')may be blocked or ignored. In TG WebApp,openLinkis usually safer; outside TG usewindow.location.href.- if (buttonId === 'recall') { - window.open(formattedToCall) - } + if (buttonId === 'recall') { + // Prefer Telegram SDK when available; otherwise, redirect + try { + // import { openLink } from '@telegram-apps/sdk-vue' + // await openLink(formattedToCall) + window.location.href = formattedToCall + } catch { + window.location.href = formattedToCall + } + }
63-66: Guard phone formatting against exceptions.
parsePhoneNumberWithErrorthrows on invalid input. Even if constant today, make it robust.-const formatted = parsePhoneNumberWithError(tel, 'RU').format('INTERNATIONAL') +let formatted = `+${tel}` +try { + formatted = parsePhoneNumberWithError(tel, 'RU').format('INTERNATIONAL') +} catch {}
57-57: Minor: co-locate SDK imports.If you add
openLink(previous comment), import alongsideshowPopupfor consistency.apps/storefront-telegram/app/composables/useNavigation.ts (2)
38-38: Avoid duplicated predicates for button visibility.
isAverageProgressButtonShownequalsisCartButtonShown; derive one from the other to prevent drift.-const isAverageProgressButtonShown = computed(() => isCatalogPage.value) +const isAverageProgressButtonShown = computed(() => isCartButtonShown.value)
45-45: Export naming: consider semantic intent.If this flag means “promo/upsell available,” a more domain-specific name (e.g.,
isUpsellButtonShown) improves clarity.apps/storefront-telegram/app/components/catalog/CartButton.vue (3)
6-6: Accessibility: add aria-label when title is hidden.Icon-only state should expose a label.
- <UButton + <UButton + :aria-label="$t('app.cart.title')"
11-11: Micro: consistent typography scale.
text-lg/5differs from nearby components’ line-height; ensure it matches your design system.
26-26: Composables: import only what you use.If the title logic changes back, keep
isAverageProgressButtonShown; otherwise drop it to avoid unused reactive deps.apps/storefront-telegram/app/components/catalog/AverageProgressButton.vue (5)
75-77: Don’t globally override navigation state without preserving prior value.If multiple components touch
isNavigationShown, toggling may clobber genuine state.-const isDrawerOpened = ref(false) +const isDrawerOpened = ref(false) +const prevNavShown = ref<boolean | null>(null) -watch(isDrawerOpened, () => { - isNavigationShown.value = !isDrawerOpened.value -}) +watch(isDrawerOpened, (open) => { + if (open) { + if (prevNavShown.value === null) prevNavShown.value = isNavigationShown.value + isNavigationShown.value = false + } else if (prevNavShown.value !== null) { + isNavigationShown.value = prevNavShown.value + prevNavShown.value = null + } +})
35-38: Localize and format currency.Hard-coded “Еще” and
progress * 10RUB string should use i18n and number formatting.- <div class="pr-2 w-12 text-center text-sm/4 font-medium"> - <p>Еще</p> - <p>{{ progress * 10 }} ₽</p> - </div> + <div class="pr-2 w-12 text-center text-sm/4 font-medium"> + <p>{{ t('average.more') }}</p> + <p>{{ new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 }).format(progress * 10) }}</p> + </div>Add:
+import { useI18n } from 'vue-i18n' +const { t } = useI18n()
45-50: Localize drawer content.Move header/body text to i18n keys.
- <h2 class="text-xl/6 font-semibold"> - Увеличим чек? - </h2> + <h2 class="text-xl/6 font-semibold"> + {{ t('average.drawer.title') }} + </h2> ... - Тут будут подарки и доставка. + {{ t('average.drawer.body') }}
1-7: Smooth collapse: hide overflow on wrapper.Prevents layout artifacts when width toggles.
- <div - class="transition-all duration-200 ease-in-out" + <div + class="transition-all duration-200 ease-in-out overflow-hidden"
31-32: Decorative icon should be hidden from AT.Mark as aria-hidden.
- <UIcon name="i-lucide-gift" class="size-6" /> + <UIcon name="i-lucide-gift" class="size-6" aria-hidden="true" />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/storefront-telegram/app/components/Navigation.vue(1 hunks)apps/storefront-telegram/app/components/catalog/AverageProgressButton.vue(1 hunks)apps/storefront-telegram/app/components/catalog/CartButton.vue(2 hunks)apps/storefront-telegram/app/composables/useNavigation.ts(1 hunks)apps/storefront-telegram/app/pages/menu.vue(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
apps/storefront-telegram/app/components/Navigation.vue (1)
4-6: LGTM: top-bar composition aligns with new UX.Order and usage of
CatalogCartButton,CatalogAverageProgressButton,CatalogCategoriesButtonlook consistent with the composable state.
| setInterval(() => { | ||
| progress.value = Math.floor(Math.random() * 100) | ||
| }, 4000) | ||
| }) |
There was a problem hiding this comment.
Memory leak: interval not cleared on unmount.
Repeated mounts will accumulate timers.
-onMounted(() => {
- setInterval(() => {
- progress.value = Math.floor(Math.random() * 100)
- }, 4000)
-})
+let timer: ReturnType<typeof setInterval> | undefined
+onMounted(() => {
+ timer = setInterval(() => {
+ progress.value = Math.floor(Math.random() * 100)
+ }, 4000)
+})
+onUnmounted(() => {
+ if (timer) clearInterval(timer)
+})📝 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.
| setInterval(() => { | |
| progress.value = Math.floor(Math.random() * 100) | |
| }, 4000) | |
| }) | |
| let timer: ReturnType<typeof setInterval> | undefined | |
| onMounted(() => { | |
| timer = setInterval(() => { | |
| progress.value = Math.floor(Math.random() * 100) | |
| }, 4000) | |
| }) | |
| onUnmounted(() => { | |
| if (timer) clearInterval(timer) | |
| }) |
🤖 Prompt for AI Agents
In apps/storefront-telegram/app/components/catalog/AverageProgressButton.vue
around lines 68 to 71, the setInterval is created but never cleared, causing
timers to accumulate on repeated mounts; store the interval id (const/let timer
= setInterval(...)), and clear it in the component cleanup hook (call
clearInterval(timer) inside onBeforeUnmount or onUnmounted) so the timer is
removed when the component unmounts.
| <p v-if="!isAverageProgressButtonShown"> | ||
| {{ $t('app.cart.title') }} | ||
| </p> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Title will always be hidden with current state logic.
Both isCartButtonShown and isAverageProgressButtonShown are true on catalog page, so the title never renders. Confirm intent.
Example options:
- Adjust
isAverageProgressButtonShownpredicate (e.g., feature flag/threshold). - Or gate by another condition (screen width/scroll), e.g.,
v-if="!isCategoriesButtonShown"if suitable.
🏁 Script executed:
#!/bin/bash
# Verify where and how these computed props are defined in CartButton.vue
rg -n "isAverageProgressButtonShown" -n apps/storefront-telegram/app/components/catalog/CartButton.vue
rg -n "isCartButtonShown" -n apps/storefront-telegram/app/components/catalog/CartButton.vueLength of output: 240
🏁 Script executed:
#!/bin/bash
# Locate the useNavigation implementation
rg -n "export function useNavigation" -C3 apps/storefront-telegram
rg -n "function useNavigation" -C3 apps/storefront-telegram
rg -n "const useNavigation" -C3 apps/storefront-telegramLength of output: 318
🏁 Script executed:
#!/bin/bash
# Inspect the internal definitions of computed props in _useNavigation
rg -n "function _useNavigation" -C50 apps/storefront-telegram/app/composables/useNavigation.tsLength of output: 1523
Fix hidden title logic in CartButton.vue
The <p v-if="!isAverageProgressButtonShown"> block never renders because isAverageProgressButtonShown is always true on the catalog page (router.currentRoute.value.path === '/').
- Adjust
isAverageProgressButtonShownto include the intended gating logic (e.g. only true after scroll threshold) - Or change/remove the
v-if(e.g. use!isCategoriesButtonShownor drop the block if it’s no longer needed)
🤖 Prompt for AI Agents
In apps/storefront-telegram/app/components/catalog/CartButton.vue around lines
14-16, the <p v-if="!isAverageProgressButtonShown"> block never appears because
isAverageProgressButtonShown is always true on the catalog page; update the
gating so the flag reflects the intended visibility (for example make
isAverageProgressButtonShown a computed that returns true only when
router.currentRoute.value.path !== '/' AND the user has scrolled past a
threshold, or otherwise replace the v-if with the intended condition such as
!isCategoriesButtonShown); implement by changing the computed/property logic to
include the scroll threshold check (or route+scroll combination) or by editing
the template to use the correct boolean or remove the block if it is obsolete.


Summary by CodeRabbit
New Features
Style