feat: new sliders#123
Conversation
WalkthroughIntroduces new catalog slider components and a relocated CategoryBlock under catalog/, updates the index page to compose these components, tweaks CitySelector UI, adds a UserPointsCard and updates user page content, and shifts tg-safe-area from nuxt.config rootAttrs to the default layout’s main element. Removes the old CategoryBlock component. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant IndexPage
participant MenuStore as useMenuStore()
participant CatMenu as CategoriesSliderMenu
participant Stories as StoriesSlider
participant Reco as RecommendedSlider
participant CatBlock as CategoryBlock
participant ProductCard
User->>IndexPage: Navigate /
IndexPage->>Stories: Render static stories slider
IndexPage->>Reco: Render static recommended slider
IndexPage->>MenuStore: Read menu?.categories
IndexPage->>CatMenu: Render categories slider (categories)
loop For each category
IndexPage->>CatBlock: Render with categoryId
CatBlock->>MenuStore: Get category by id
CatBlock->>CatBlock: Filter purchasable products with variants
loop For each product
CatBlock->>ProductCard: Render product card
end
end
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: 1
🧹 Nitpick comments (15)
apps/storefront-telegram/app/components/catalog/StoriesSlider.vue (3)
3-3: Prevent wrapping in horizontal sliderUse flex-nowrap to avoid wrapping rows inside a horizontally scrolling container.
- <div class="w-max flex flex-row flex-wrap gap-2"> + <div class="w-max flex flex-row flex-nowrap gap-2">
2-2: Stronger scroll snappingConsider snap-mandatory (or snap-proximity) for a crisper snap behavior.
- <div class="max-w-full overflow-x-scroll snap-x"> + <div class="max-w-full overflow-x-scroll snap-x snap-mandatory">
3-3: Basic a11y semantics for a slider listAdd list/listitem roles and labels; make items focusable for keyboard users.
- <div class="w-max flex flex-row flex-wrap gap-2"> + <div class="w-max flex flex-row flex-nowrap gap-2" role="list" aria-label="Истории"> - <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg"> + <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg" role="listitem" tabindex="0"> @@ - <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg"> + <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg" role="listitem" tabindex="0"> @@ - <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg"> + <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg" role="listitem" tabindex="0"> @@ - <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg"> + <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg" role="listitem" tabindex="0"> @@ - <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg"> + <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg" role="listitem" tabindex="0"> @@ - <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg"> + <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-2/3 rounded-lg" role="listitem" tabindex="0">Also applies to: 4-4, 10-10, 14-14, 18-18, 22-22, 26-26
apps/storefront-telegram/app/pages/user.vue (1)
5-11: Replace placeholder copy or gate behind feature flag"Тут будет меню" reads like a placeholder; consider i18n or hide until ready.
- <h2 class="text-lg/5 font-bold"> - Тут будет меню - </h2> + <h2 class="text-lg/5 font-bold"> + {{ $t('user.menu.soon') }} + </h2>apps/storefront-telegram/app/components/UserPointsCard.vue (2)
7-9: Make percentage dynamic (prop) instead of hard-coded 5%Allows reuse and future wiring to backend/store without touching markup.
- <div class="text-3xl/5 font-bold"> - 5% - </div> + <div class="text-3xl/5 font-bold" aria-label="Скидка пользователя"> + {{ percent }}% + </div>Add in <script setup>:
-const clientStore = useClientStore() +const clientStore = useClientStore() +const props = withDefaults(defineProps<{ percent?: number }>(), { percent: 5 }) +const percent = computed(() => props.percent)
4-6: Name fallback to avoid blank UIGuard against empty fullName to keep layout stable.
- <div class="text-lg/5 font-semibold"> - {{ clientStore.fullName }} - </div> + <div class="text-lg/5 font-semibold"> + {{ clientStore.fullName || 'Гость' }} + </div>apps/storefront-telegram/app/components/CitySelector.vue (2)
26-29: Unify checkbox group UI config via constantKeeps template lean and avoids reactive churn.
- :ui="{ - item: 'items-center', - label: '!text-lg', - }" + :ui="checkboxGroupUi"Add in <script setup>:
const checkboxGroupUi = { item: 'items-center', label: '!text-lg', }
8-11: Factor UI config constant; test overlay padding
- Move the inline
uiobject in CitySelector.vue to a top-level constant in<script setup>to prevent it being re-created each render (e.g.and- :ui="{ - content: 'max-h-10/12 !mt-150', - overlay: 'tg-content-safe-area-top', - }" + :ui="drawerUi"const drawerUi = { content: 'max-h-10/12 !mt-150', overlay: 'tg-content-safe-area-top', }- Verified that
.tg-safe-areais applied in default.vue and.tg-content-safe-area-topis defined in styles.css; manually test the drawer overlay to ensure it isn’t double-padded under the layout’s safe-area wrapper.apps/storefront-telegram/app/pages/index.vue (1)
15-19: Guard v-for source to avoid undefined during initial load.
If menu is still loading, v-for over undefined can warn; coalesce to an empty array.Apply:
- <CatalogCategoryBlock - v-for="category in menuStore.menu?.categories" + <CatalogCategoryBlock + v-for="category in (menuStore.menu?.categories ?? [])" :key="category.id" :category-id="category.id" />apps/storefront-telegram/app/components/catalog/CategoryBlock.vue (2)
19-21: Loosen prop type to match id shape from store.
Ifcategory.idis numeric, the strictstringprop will be awkward.-const { categoryId } = defineProps<{ - categoryId: string -}>() +const { categoryId } = defineProps<{ + categoryId: string | number +}>()
2-2: Add anchors and scroll offset; hide empty sections.
Enables in-page navigation from the categories slider and avoids rendering empty blocks.- <div class="flex flex-col gap-3 mb-10"> + <div + :id="`category-${categoryId}`" + class="flex flex-col gap-3 mb-10 scroll-mt-20" + v-if="products.length" + >apps/storefront-telegram/app/components/catalog/RecommendedSlider.vue (2)
9-61: DRY: generate items with v-for instead of duplicating markup.
Cuts maintenance and DOM size.- <div class="w-max flex flex-row flex-wrap gap-2"> - <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-3/4 rounded-lg"> - <div class="p-2 max-w-24"> - 1 товар - </div> - </div> - ... - <div class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-3/4 rounded-lg"> - <div class="p-2 max-w-24"> - 9 товар - </div> - </div> - </div> + <div class="w-max flex flex-row gap-2"> + <div + v-for="n in 9" + :key="n" + class="min-w-24 scroll-ml-6 snap-start border border-primary aspect-3/4 rounded-lg" + > + <div class="p-2 max-w-24"> + {{ n }} товар + </div> + </div> + </div>
7-9: Slider ergonomics: single row + stronger snapping.
Avoid wrapping and enable mandatory snapping.- <div class="max-w-full overflow-x-scroll snap-x"> - <div class="w-max flex flex-row flex-wrap gap-2"> + <div class="max-w-full overflow-x-auto snap-x snap-mandatory"> + <div class="w-max flex flex-row gap-2">apps/storefront-telegram/app/components/catalog/CategoriesSliderMenu.vue (2)
5-13: Make categories clickable and navigate to sections.
Anchor to the corresponding CategoryBlock ids.- <div - v-for="category in menuStore.menu?.categories" - :key="category.id" - class="scroll-ml-6 snap-start" - > - <div class="p-0"> - {{ category.name }} - </div> - </div> + <div + v-for="category in menuStore.menu?.categories" + :key="category.id" + class="scroll-ml-6 snap-start" + > + <a + :href="`#category-${category.id}`" + class="inline-block px-3 py-2 rounded-full border border-transparent hover:border-primary transition" + > + {{ category.name }} + </a> + </div>
3-5: Slider behavior: prevent wrap and enable mandatory snapping.
Produces a true horizontal slider.- <div class="max-w-full overflow-x-scroll snap-x"> - <div class="w-max flex flex-row flex-wrap gap-5"> + <div class="max-w-full overflow-x-auto snap-x snap-mandatory"> + <div class="w-max flex flex-row gap-5">
📜 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 (11)
apps/storefront-telegram/app/components/CategoryBlock.vue(0 hunks)apps/storefront-telegram/app/components/CitySelector.vue(2 hunks)apps/storefront-telegram/app/components/UserPointsCard.vue(1 hunks)apps/storefront-telegram/app/components/catalog/CategoriesSliderMenu.vue(1 hunks)apps/storefront-telegram/app/components/catalog/CategoryBlock.vue(1 hunks)apps/storefront-telegram/app/components/catalog/RecommendedSlider.vue(1 hunks)apps/storefront-telegram/app/components/catalog/StoriesSlider.vue(1 hunks)apps/storefront-telegram/app/layouts/default.vue(1 hunks)apps/storefront-telegram/app/pages/index.vue(1 hunks)apps/storefront-telegram/app/pages/user.vue(1 hunks)apps/storefront-telegram/nuxt.config.ts(0 hunks)
💤 Files with no reviewable changes (2)
- apps/storefront-telegram/nuxt.config.ts
- apps/storefront-telegram/app/components/CategoryBlock.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/layouts/default.vue (1)
2-2: Safe-area class only on<main>and removed from rootAttrs – Confirmed notg-safe-areainnuxt.config.tsapp.rootAttrs; only applied onlayouts/default.vue::main.apps/storefront-telegram/app/pages/user.vue (1)
3-3: Component inclusion looks goodUserPointsCard placement in PageContainer is straightforward.
apps/storefront-telegram/app/pages/index.vue (1)
10-14: LGTM: new sliders composition fits the home flow.
No issues spotted with the new CatalogStoriesSlider and CatalogRecommendedSlider mounts.apps/storefront-telegram/app/components/catalog/RecommendedSlider.vue (1)
9-61: Confirm CSS utility support (aspect-3/4, min-w-24, max-w-24, etc.)
No Tailwind or UnoCSS config found in the repo—verify your CSS engine defines these classes (and related ones like scroll-ml-6, snap-start, border-primary) via UnoCSS presets or Tailwind theme/plugins.apps/storefront-telegram/app/components/catalog/CategoriesSliderMenu.vue (1)
2-2: Confirm custom utilities exist
Could not locateh-18orbg-primaryin your CSS configuration; please verify these classes are defined in your UnoCSS/Tailwind setup.
| const menuStore = useMenuStore() | ||
| const category = menuStore.menu?.categories.find((c) => c.id === categoryId) | ||
| const products = category?.products.filter((p) => p.isAvailableForPurchase && p.variants.length) | ||
| </script> |
There was a problem hiding this comment.
Fix non-reactive lookups; block won’t update after menu loads.
category/products are computed once; they won’t react to menuStore.menu changes. Also missing optional chaining on categories, and variants.length can throw if variants is undefined.
Use computed + safe checks:
-const menuStore = useMenuStore()
-const category = menuStore.menu?.categories.find((c) => c.id === categoryId)
-const products = category?.products.filter((p) => p.isAvailableForPurchase && p.variants.length)
+const menuStore = useMenuStore()
+const category = computed(() =>
+ menuStore.menu?.categories?.find((c) => String(c.id) === String(categoryId))
+)
+const products = computed(() =>
+ category.value?.products?.filter(
+ (p) => p.isAvailableForPurchase && (p.variants?.length ?? 0) > 0
+ ) ?? []
+)📝 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 menuStore = useMenuStore() | |
| const category = menuStore.menu?.categories.find((c) => c.id === categoryId) | |
| const products = category?.products.filter((p) => p.isAvailableForPurchase && p.variants.length) | |
| </script> | |
| const menuStore = useMenuStore() | |
| const category = computed(() => | |
| menuStore.menu?.categories?.find((c) => String(c.id) === String(categoryId)) | |
| ) | |
| const products = computed(() => | |
| category.value?.products?.filter( | |
| (p) => p.isAvailableForPurchase && (p.variants?.length ?? 0) > 0 | |
| ) ?? [] | |
| ) | |
| </script> |



Summary by CodeRabbit
New Features
UI/Style
Refactor