feat: closed now block#144
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughAdds a timed width-animated text reveal to AverageProgressButton; swaps CategoriesButton icon; introduces ClosedNowHeader and inserts it into default layout; changes menu.vue phone popup to initiate a call (tel:) instead of copying the number; assigns a base levelId when creating new clients in the Telegram order-bot. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant M as menu.vue
participant P as Popup
Note over U,M: Phone action
U->>M: Tap phone action
M->>P: openPopup(title, actions=[call, recall])
P-->>U: Show options
U-->>P: Select "Позвонить" (call)
P->>M: onAction(buttonId="call")
M->>M: build tel:+<number>
M->>Browser: window.open(tel:+<number>)
Note over M: "recall" currently no-op
sequenceDiagram
autonumber
participant C as AverageProgressButton
participant T as 12s Timer
Note over C: onMounted
C->>T: start interval (12s)
loop every 12s
T->>C: tick
C->>C: update progress
C->>C: isTextShown = true
Note over C: Text visible (~4s)
C->>C: after 4s → isTextShown = false
end
sequenceDiagram
autonumber
participant L as default.vue Layout
participant H as ClosedNowHeader
participant N as useNavigation()
L->>H: Render header
H->>N: isCatalogPage?
alt On catalog page
H-->>L: Render banner
else Elsewhere
H-->>L: Do not render
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
✨ 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: 4
🧹 Nitpick comments (2)
apps/storefront-telegram/app/components/catalog/AverageProgressButton.vue (2)
35-49: Hide overflow during width animationContent may peek during w-0 → w-14 transition. Add overflow-hidden to the animated container.
-<div - class="transition-all duration-600 ease-in-out" +<div + class="overflow-hidden transition-all duration-600 ease-in-out"Also verify duration-600 exists in your Tailwind config. If not, use duration-500/700.
41-47: Currency formattingUse Intl.NumberFormat for ₽ to avoid raw math and ensure locale formatting.
- <p class="min-w-12"> - {{ progress * 10 }} ₽ - </p> + <p class="min-w-12"> + {{ new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 }).format(progress * 10) }} + </p>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/storefront-telegram/app/components/catalog/AverageProgressButton.vue(4 hunks)apps/storefront-telegram/app/components/catalog/CategoriesButton.vue(1 hunks)apps/storefront-telegram/app/components/catalog/ClosedNowHeader.vue(1 hunks)apps/storefront-telegram/app/layouts/default.vue(1 hunks)apps/storefront-telegram/app/pages/menu.vue(1 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 (2)
apps/storefront-telegram/app/components/catalog/CategoriesButton.vue (1)
14-14: Icon swap LGTMThe glyph change to i-lucide-text-align-start fits the layout; no logic affected.
apps/storefront-telegram/app/components/catalog/ClosedNowHeader.vue (1)
21-23: Confirm reactivity sourceEnsure useNavigation().isCatalogPage is a ref/computed, not a reactive prop destructured by value. If it’s from a store object, use storeToRefs or return refs from the composable.
| onMounted(() => { | ||
| setInterval(() => { | ||
| progress.value = Math.floor(Math.random() * 100) | ||
| }, 4000) | ||
| isTextShown.value = true | ||
|
|
||
| setTimeout(() => { | ||
| isTextShown.value = false | ||
| }, 4000) | ||
| }, 12000) | ||
| }) |
There was a problem hiding this comment.
Clear intervals/timeouts on unmount
setInterval/setTimeout aren’t cleared → leaks and background updates after navigation.
Apply:
onMounted(() => {
- setInterval(() => {
+ const intervalId = setInterval(() => {
progress.value = Math.floor(Math.random() * 100)
isTextShown.value = true
- setTimeout(() => {
+ const timeoutId = setTimeout(() => {
isTextShown.value = false
}, 4000)
}, 12000)
})
+
+onBeforeUnmount(() => {
+ // @ts-ignore - scope-captured ids if needed
+ if (typeof intervalId !== 'undefined') clearInterval(intervalId)
+ if (typeof timeoutId !== 'undefined') clearTimeout(timeoutId)
+})Alternative: use @vueuse/core useIntervalFn/useTimeoutFn (auto-cleans).
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/storefront-telegram/app/components/catalog/AverageProgressButton.vue
around lines 78–87 the onMounted block creates an interval and nested timeout
but never clears them, causing memory leaks and background updates after
navigation; store the interval and timeout IDs, call clearInterval and
clearTimeout in an onUnmounted hook to cancel them, or replace the manual timers
with @vueuse/core helpers (useIntervalFn/useTimeoutFn) which auto-clean on
unmount; ensure the timeout ID is cleared before assigning a new one inside the
interval to avoid overlapping timers.
| <div | ||
| v-if="isCatalogPage" | ||
| class="tg-safe-area bg-violet-950 text-yellow-100 motion-preset-slide-down" | ||
| > | ||
| <div class="px-4 py-3.5 max-w-[28rem] mx-auto tg-content-safe-area-top"> | ||
| <div class="flex flex-row items-center gap-2"> | ||
| <UIcon name="i-lucide-moon" class="size-12 motion-preset-oscillate-sm motion-preset-seesaw motion-duration-3000" /> | ||
|
|
||
| <div class="flex flex-col"> | ||
| <h3 class="text-lg font-semibold"> | ||
| Мы сейчас закрыты | ||
| </h3> | ||
| <p>Работаем с 10:00 до 22:00</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add real open/closed logic and dynamic hours
Static “Мы сейчас закрыты / 10:00–22:00” will be wrong when open or for kitchens with different schedules.
Apply (illustrative):
- <div
- v-if="isCatalogPage"
+ <div
+ v-if="isCatalogPage && isClosedNow"
class="tg-safe-area bg-violet-950 text-yellow-100 motion-preset-slide-down"
>
<div class="px-4 py-3.5 max-w-[28rem] mx-auto tg-content-safe-area-top">
<div class="flex flex-row items-center gap-2">
<UIcon name="i-lucide-moon" class="size-12 motion-preset-oscillate-sm motion-preset-seesaw motion-duration-3000" />
<div class="flex flex-col">
<h3 class="text-lg font-semibold">
Мы сейчас закрыты
</h3>
- <p>Работаем с 10:00 до 22:00</p>
+ <p>Работаем с {{ hours.open }} до {{ hours.close }}</p>
</div>
</div>
</div>
</div>And in script:
const { isCatalogPage } = useNavigation()
+const channelStore = useChannelStore()
+const hours = computed(() => channelStore.selectedKitchen?.hours ?? { open: '11:00', close: '22:00' })
+const isClosedNow = computed(() => {
+ // naive example; replace with tz-aware check
+ const [oh, om] = hours.value.open.split(':').map(Number)
+ const [ch, cm] = hours.value.close.split(':').map(Number)
+ const now = new Date()
+ const mins = now.getHours() * 60 + now.getMinutes()
+ const openM = oh * 60 + om
+ const closeM = ch * 60 + cm
+ return !(mins >= openM && mins < closeM)
+})Also consider i18n for strings.
🤖 Prompt for AI Agents
In apps/storefront-telegram/app/components/catalog/ClosedNowHeader.vue around
lines 2 to 18, the header currently shows static Russian text and fixed hours;
replace this with dynamic open/closed logic and dynamic hours: accept a schedule
prop or fetch the kitchen's hours (e.g., daily open/close times), compute
current local time vs. today's intervals to derive isOpen and formattedHours,
render different icon/text based on isOpen (open label/time vs closed
label/time) and use the schedule to display hours instead of hardcoded
"10:00–22:00"; also wire these strings through i18n keys and ensure reactivity
(update on minute tick or visibility change) and handle edge cases like
overnight periods and multiple intervals per day.
| @@ -1,5 +1,6 @@ | |||
| <template> | |||
| <BackgroundConfetti /> | |||
| <CatalogClosedNowHeader /> | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
Header shows “closed” unconditionally
CatalogClosedNowHeader currently always renders “Мы сейчас закрыты” on catalog pages. Gate it by actual open/closed state to avoid misinformation.
Example wiring:
- <CatalogClosedNowHeader />
+ <CatalogClosedNowHeader v-if="isClosedNow" />and compute isClosedNow in the layout (or inside the header) from kitchen hours/timezone.
I can wire this to channelStore hours with a robust isClosedNow() helper if you share the hours shape.
📝 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.
| <CatalogClosedNowHeader /> | |
| <CatalogClosedNowHeader v-if="isClosedNow" /> |
| const buttonId = await showPopup({ | ||
| title: formatted, | ||
| message: 'Выберите действие', | ||
| message: 'Телефон кухни. Выберите действие', | ||
| buttons: [ | ||
| { | ||
| id: 'copy', | ||
| id: 'call', | ||
| type: 'default', | ||
| text: 'Скопировать', | ||
| text: 'Позвонить', | ||
| }, | ||
| { | ||
| id: 'recall', | ||
| type: 'default', | ||
| text: 'Нужен обратный звонок', | ||
| text: 'Заказать обратный звонок', | ||
| }, | ||
| { | ||
| type: 'close', | ||
| }, | ||
| ], | ||
| }) | ||
|
|
||
| if (buttonId === 'copy') { | ||
| navigator.clipboard.writeText(formatted) | ||
| if (buttonId === 'call') { | ||
| window.open(formattedToCall) | ||
| } | ||
| if (buttonId === 'recall') { | ||
| window.open(formattedToCall) | ||
| // | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Use location.href for tel links + provide non-popup fallback
window.open('tel:') is often blocked in WKWebView/Telegram; location.href is more reliable. Also, if showPopup isn’t available, the action currently no-ops—fallback to direct dial. Finally, the ‘recall’ button is a no-op; either implement or hide to avoid UX dead-ends.
Apply:
- if (buttonId === 'call') {
- window.open(formattedToCall)
- }
+ if (buttonId === 'call') {
+ window.location.href = formattedToCall
+ }
if (buttonId === 'recall') {
- //
+ // TODO: implement callback request or remove this button until ready
}
}
+ else {
+ // Fallback when Telegram popup API is unavailable
+ window.location.href = formattedToCall
+ }Optional: compute the popup title separately and keep message concise.
🏁 Script executed:
#!/bin/bash
# Locate all showPopup usages and imports in JS/Vue files
rg -n "showPopup" -C3 --type=js --type=vueLength of output: 27
🏁 Script executed:
#!/bin/bash
# Locate the menu.vue file
menu_file=$(find . -type f -name "menu.vue")
echo "Found file: $menu_file"
# Print first 200 lines to inspect imports and context
echo "=== Beginning of file ==="
sed -n '1,200p' "$menu_file"
echo "=== End of snippet ==="
# Search for showPopup usage and window.open/formattedToCall context
echo "=== showPopup usages ==="
rg -n "showPopup" -C3 "$menu_file" || echo "No showPopup calls found"
echo "=== window.open usages ==="
rg -n "window.open" -C3 "$menu_file" || echo "No window.open calls found"
echo "=== formattedToCall references ==="
rg -n "formattedToCall" -C3 "$menu_file" || echo "No formattedToCall found"Length of output: 3674
Use window.location.href for tel: links, add fallback, and handle recall button
- Replace
window.open(formattedToCall)withwindow.location.href = formattedToCallfor reliable tel: handling in WKWebView/Telegram Web Apps (developers.google.com, stackoverflow.com). - After
if (showPopup.isAvailable()), add anelsebranch to fallback towindow.location.hrefwhen the popup API isn’t available. - Remove or implement the
recallaction to avoid a dead-end UX (e.g., show a callback form or hide the button until ready).
Apply:
- if (buttonId === 'call') {
- window.open(formattedToCall)
- }
+ if (buttonId === 'call') {
+ window.location.href = formattedToCall
+ }
if (buttonId === 'recall') {
- //
+ // TODO: implement callback request or remove this button until ready
}
}
+ else {
+ // Fallback if Telegram popup API is unavailable
+ window.location.href = formattedToCall
+ }📝 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 buttonId = await showPopup({ | |
| title: formatted, | |
| message: 'Выберите действие', | |
| message: 'Телефон кухни. Выберите действие', | |
| buttons: [ | |
| { | |
| id: 'copy', | |
| id: 'call', | |
| type: 'default', | |
| text: 'Скопировать', | |
| text: 'Позвонить', | |
| }, | |
| { | |
| id: 'recall', | |
| type: 'default', | |
| text: 'Нужен обратный звонок', | |
| text: 'Заказать обратный звонок', | |
| }, | |
| { | |
| type: 'close', | |
| }, | |
| ], | |
| }) | |
| if (buttonId === 'copy') { | |
| navigator.clipboard.writeText(formatted) | |
| if (buttonId === 'call') { | |
| window.open(formattedToCall) | |
| } | |
| if (buttonId === 'recall') { | |
| window.open(formattedToCall) | |
| // | |
| } | |
| const buttonId = await showPopup({ | |
| title: formatted, | |
| message: 'Телефон кухни. Выберите действие', | |
| buttons: [ | |
| { | |
| id: 'call', | |
| type: 'default', | |
| text: 'Позвонить', | |
| }, | |
| { | |
| id: 'recall', | |
| type: 'default', | |
| text: 'Заказать обратный звонок', | |
| }, | |
| { | |
| type: 'close', | |
| }, | |
| ], | |
| }) | |
| if (buttonId === 'call') { | |
| window.location.href = formattedToCall | |
| } | |
| if (buttonId === 'recall') { | |
| // TODO: implement callback request or remove this button until ready | |
| } | |
| } | |
| else { | |
| // Fallback if Telegram popup API is unavailable | |
| window.location.href = formattedToCall | |
| } |
🤖 Prompt for AI Agents
In apps/storefront-telegram/app/pages/menu.vue around lines 71 to 96, replace
the current tel link handling and fix the dead recall action: use
window.location.href = formattedToCall instead of window.open(formattedToCall)
so tel: links work reliably in WKWebView/Telegram Web Apps; wrap the popup call
with a showPopup.isAvailable() check and provide an else branch that directly
sets window.location.href = formattedToCall as a fallback when popups aren’t
available; and either implement the 'recall' branch (e.g., open a callback
form/modal or trigger a follow-up popup to collect the phone/time) or
remove/hide the 'recall' button until that feature is implemented so the UX has
no dead-end.
|



Summary by CodeRabbit
New Features
Style