feat: menu page#136
Conversation
WalkthroughAdds haptic feedback on city and menu interactions, renames the navigation route to “/menu,” introduces a new Menu page, removes the old navigation page template, and sets a default selected kitchen ID in the channel store. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant CitySelector
participant Feedback as useFeedback.vibrate
participant ClientStore as clientStore
User->>CitySelector: Tap city item (id)
CitySelector->>Feedback: vibrate('success')
Feedback-->>CitySelector: done
CitySelector->>ClientStore: updateCity(id)
ClientStore-->>CitySelector: state updated
CitySelector-->>User: UI reflects selected city
sequenceDiagram
actor User
participant MenuPage
participant Feedback as useFeedback.vibrate
participant PhoneFmt as libphonenumber-js
participant Device as Dialer
Note over MenuPage,PhoneFmt: On setup: parse tel → formatted, RFC3966
User->>MenuPage: Tap action item
MenuPage->>Feedback: vibrate()
Feedback-->>MenuPage: done
User->>MenuPage: Tap call button
MenuPage->>Device: Open tel: RFC3966 URL
Device-->>User: Dialer opens
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
apps/storefront-telegram/app/components/Navigation.vue (1)
31-38: Localize static titles for consistency.Home uses i18n; consider the same for “Кабинет” and “Меню”.
{ path: '/client', name: 'client', - title: 'Кабинет', + title: t('app.client'), icon: 'i-lucide-user', }, { path: '/menu', name: 'menu', - title: 'Меню', + title: t('app.menu'), icon: 'i-lucide-menu', },apps/storefront-telegram/app/pages/menu.vue (4)
33-39: Prefer href for tel: links to bypass the router.Some button/link components treat :to as a client-route. Use an anchor/href to ensure dialing works reliably.
- <UButton - :to="formattedToCall" + <UButton + as="a" + :href="formattedToCall" color="neutral" variant="ghost" class="px-0 text-lg font-medium" >If UButton lacks “as”/“href”, wrap an inside the button slot.
8-20: All items rendered “active”.Unless intentional, drop active to avoid persistent pressed styling.
- <UButton + <UButton v-for="item in items" :key="item.label" - active size="xl" color="neutral" variant="ghost" class="px-0 text-xl/5 font-semibold" :label="item.label" :to="item.to" @click="item.onClick" />
22-29: Make address/hours resilient and configurable.
- Provide a fallback when selectedKitchen is absent.
- Consider sourcing hours from config/store and localize the text.
- <p class="font-medium"> - {{ channelStore.selectedKitchen?.address }} - </p> + <p class="font-medium"> + {{ channelStore.selectedKitchen?.address || 'Адрес недоступен' }} + </p> - <p class="text-sm text-muted"> - Ежедневно с 11:00 до 22:00 - </p> + <p class="text-sm text-muted"> + {{ /* t('kitchen.hours', { from: '11:00', to: '22:00' }) or a value from settings */ 'Ежедневно с 11:00 до 22:00' }} + </p>
58-64: Self-link to /menu is redundant.The “Акции” item links to the current page. Remove until a real route exists.
{ label: 'Акции', - to: '/menu', onClick: () => vibrate(), },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
apps/storefront-telegram/app/components/CitySelector.vue(2 hunks)apps/storefront-telegram/app/components/Navigation.vue(1 hunks)apps/storefront-telegram/app/pages/client/index.vue(1 hunks)apps/storefront-telegram/app/pages/menu.vue(1 hunks)apps/storefront-telegram/app/pages/navigation.vue(0 hunks)apps/storefront-telegram/app/stores/channel.ts(1 hunks)
💤 Files with no reviewable changes (1)
- apps/storefront-telegram/app/pages/navigation.vue
⏰ 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 (5)
apps/storefront-telegram/app/components/CitySelector.vue (2)
24-25: Haptic on city selection — nice touch.The wrapper improves UX without bloating the template. LGTM.
33-39: No action needed: thevibratesignature already supports‘light’ | ‘success’ | ‘error’with a default of‘light’, so callingvibrate('success')is valid and consistent.apps/storefront-telegram/app/pages/client/index.vue (1)
30-49: Haptics on profile actions — LGTM.Concise and consistent with the new feedback pattern.
apps/storefront-telegram/app/components/Navigation.vue (2)
35-39: Route rename to /menu matches the new page — LGTM.Navigation entry now aligns with apps/storefront-telegram/app/pages/menu.vue.
35-36: Verified removal of “navigation” route references — searches across all Vue/JS/TS files in apps/ returned no occurrences of the old/navigationpath orname: 'navigation'.
| label: clientStore.selectedCity ? clientStore.selectedCity.name : 'Выбрать город', | ||
| icon: 'i-lucide-locate-fixed', | ||
| onClick: () => clientStore.updateCity(null), | ||
| onClick: () => { | ||
| vibrate('success') | ||
| clientStore.updateCity(null) | ||
| }, | ||
| }, |
There was a problem hiding this comment.
City row label won’t update reactively. Build items via computed.
Label is captured at setup time; it won’t reflect later city changes.
-const items = ref([
+const items = computed(() => [
{
label: 'Мои заказы',
icon: 'i-lucide-shopping-basket',
to: '/client/orders',
onClick: () => vibrate(),
},
{
label: 'Мои адреса',
icon: 'i-lucide-map-pin-house',
onClick: () => vibrate(),
},
{
label: 'Мои данные',
icon: 'i-lucide-user',
onClick: () => vibrate(),
},
{
- label: clientStore.selectedCity ? clientStore.selectedCity.name : 'Выбрать город',
+ label: clientStore.selectedCity ? clientStore.selectedCity.name : 'Выбрать город',
icon: 'i-lucide-locate-fixed',
onClick: () => {
vibrate('success')
clientStore.updateCity(null)
},
},
-])
+])📝 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.
| label: clientStore.selectedCity ? clientStore.selectedCity.name : 'Выбрать город', | |
| icon: 'i-lucide-locate-fixed', | |
| onClick: () => clientStore.updateCity(null), | |
| onClick: () => { | |
| vibrate('success') | |
| clientStore.updateCity(null) | |
| }, | |
| }, | |
| // before: const items = ref([ | |
| import { computed } from 'vue' | |
| // … | |
| const items = computed(() => [ | |
| { | |
| label: 'Мои заказы', | |
| icon: 'i-lucide-shopping-basket', | |
| to: '/client/orders', | |
| onClick: () => vibrate(), | |
| }, | |
| { | |
| label: 'Мои адреса', | |
| icon: 'i-lucide-map-pin-house', | |
| onClick: () => vibrate(), | |
| }, | |
| { | |
| label: 'Мои данные', | |
| icon: 'i-lucide-user', | |
| onClick: () => vibrate(), | |
| }, | |
| { | |
| label: clientStore.selectedCity ? clientStore.selectedCity.name : 'Выбрать город', | |
| icon: 'i-lucide-locate-fixed', | |
| onClick: () => { | |
| vibrate('success') | |
| clientStore.updateCity(null) | |
| }, | |
| }, | |
| ]) |
🤖 Prompt for AI Agents
In apps/storefront-telegram/app/pages/client/index.vue around lines 51 to 57 the
menu item label is created once at setup and captured as a plain value so it
doesn't update when clientStore.selectedCity changes; change this to build the
items (or at least the city label) inside a computed() so it returns an object
whose label references clientStore.selectedCity.name reactively, and use that
computed value in the template/consumer so the UI updates when the city is
changed.
| const tel = '79999999999' | ||
| const formatted = parsePhoneNumberWithError(tel, 'RU').format('INTERNATIONAL') | ||
| const formattedToCall = parsePhoneNumberWithError(tel, 'RU').format('RFC3966') | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Handle phone parsing errors and avoid double parsing.
parsePhoneNumberWithError throws on bad input; doing it twice increases risk without benefit.
-const tel = '79999999999'
-const formatted = parsePhoneNumberWithError(tel, 'RU').format('INTERNATIONAL')
-const formattedToCall = parsePhoneNumberWithError(tel, 'RU').format('RFC3966')
+const tel = '79999999999'
+let formatted = `+${tel}`
+let formattedToCall = `tel:${tel}`
+try {
+ const phone = parsePhoneNumberWithError(tel, 'RU')
+ formatted = phone.format('INTERNATIONAL')
+ formattedToCall = phone.format('RFC3966')
+} catch {
+ // fallback keeps the page functional even if tel becomes invalid
+}📝 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 tel = '79999999999' | |
| const formatted = parsePhoneNumberWithError(tel, 'RU').format('INTERNATIONAL') | |
| const formattedToCall = parsePhoneNumberWithError(tel, 'RU').format('RFC3966') | |
| const tel = '79999999999' | |
| let formatted = `+${tel}` | |
| let formattedToCall = `tel:${tel}` | |
| try { | |
| const phone = parsePhoneNumberWithError(tel, 'RU') | |
| formatted = phone.format('INTERNATIONAL') | |
| formattedToCall = phone.format('RFC3966') | |
| } catch { | |
| // fallback keeps the page functional even if tel becomes invalid | |
| } |
🤖 Prompt for AI Agents
In apps/storefront-telegram/app/pages/menu.vue around lines 54 to 57, the code
calls parsePhoneNumberWithError twice which both may throw and is redundant;
instead call parsePhoneNumberWithError once inside a try/catch to handle parsing
errors, store the returned PhoneNumber object in a variable, then derive both
the INTERNATIONAL and RFC3966 formats from that single object and use a fallback
or return early on error.
| const selectedKitchenId = ref<string | undefined>('m68foq9qtpsxd69eayom7bjn') // Фрунзе | ||
| const selectedKitchen = computed(() => kitchens.value.find((kitchen) => kitchen.id === selectedKitchenId.value)) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Don’t hard‑code selectedKitchenId; add a post-fetch fallback.
The baked-in ID may not exist across environments and can drift. After loading kitchens, validate the selection and fall back to the first available (or leave undefined).
kitchens.value = data.kitchens ?? []
+ // Ensure selectedKitchenId points to an existing kitchen
+ if (!kitchens.value.some(k => k.id === selectedKitchenId.value)) {
+ selectedKitchenId.value = kitchens.value[0]?.id
+ }If this default must be environment-specific, consider sourcing it from the backend payload or runtime config instead of a literal.
🏁 Script executed:
#!/bin/bash
# Find occurrences of selectedKitchenId in channel.ts
rg -n "selectedKitchenId" apps/storefront-telegram/app/stores/channel.ts
# Find occurrences of kitchens.value assignments
rg -n "kitchens\\.value" apps/storefront-telegram/app/stores/channel.ts
# Show the first 200 lines of the file for context
sed -n '1,200p' apps/storefront-telegram/app/stores/channel.tsLength of output: 2078
Add post-fetch fallback for selectedKitchenId
After kitchens.value = data.kitchens ?? [], ensure selectedKitchenId still matches an existing kitchen and reset it if not:
kitchens.value = data.kitchens ?? []
+ // Ensure selectedKitchenId points to an existing kitchen
+ if (!kitchens.value.some(k => k.id === selectedKitchenId.value)) {
+ selectedKitchenId.value = kitchens.value[0]?.id
+ }If you need an environment-specific default, pull that from the backend payload or runtime config instead of hard-coding.
🤖 Prompt for AI Agents
In apps/storefront-telegram/app/stores/channel.ts around lines 15-16, the
selectedKitchenId is hard-coded and may become stale after kitchens.value is
set; after assigning kitchens.value = data.kitchens ?? [], verify that
selectedKitchenId.value corresponds to an existing kitchen
(kitchens.value.find(...)); if it does not, reset selectedKitchenId.value to a
safe fallback pulled from the backend payload or runtime config (e.g.,
data.defaultKitchenId or config.defaultKitchenId) or to undefined rather than
keeping the hard-coded ID.



Summary by CodeRabbit