Skip to content

Commit 7dbb6c0

Browse files
committed
feat: new components and stores
1 parent 619a3be commit 7dbb6c0

24 files changed

Lines changed: 585 additions & 28 deletions

File tree

apps/storefront-telegram/app/app.vue

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,33 @@ watch(colorMode, () => {
5555
5656
// Init Stores
5757
const client = useClientStore()
58+
const city = useCityStore()
59+
const channel = useChannelStore()
60+
const menu = useMenuStore()
5861
5962
// Guard
60-
await client.update()
63+
await Promise.all([
64+
client.update(),
65+
city.update(),
66+
channel.update(),
67+
menu.update(),
68+
])
6169
if (!client.id) {
6270
await navigateTo('/no-auth')
6371
}
6472
65-
// Auto Update Online
73+
// Auto Update
6674
let interval: NodeJS.Timeout
6775
6876
onMounted(async () => {
6977
await Promise.all([
70-
// client.updateOnline(),
71-
client.update(),
78+
client.updateOnline(),
7279
])
7380
7481
interval = setInterval(async () => {
7582
await Promise.all([
76-
// client.updateOnline(),
7783
client.update(),
84+
client.updateOnline(),
7885
])
7986
}, 30000)
8087
})
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
<template>
2-
<button class="relative w-full active:scale-95 duration-200 text-left cursor-pointer outline-0" @click="vibrate()">
3-
<Section>
2+
<button
3+
class="relative w-full h-full active:scale-95 duration-200 text-left cursor-pointer outline-0"
4+
@click="vibrate()"
5+
>
6+
<Section :class="props.class">
47
<slot />
58
</Section>
69
</button>
710
</template>
811

912
<script setup lang="ts">
13+
const props = defineProps<{ class?: string }>()
14+
1015
const { vibrate } = useFeedback()
1116
</script>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<template>
2+
<h2 class="text-2xl/5 font-semibold tracking-tight">
3+
{{ category?.name }}
4+
</h2>
5+
6+
<div class="mb-10 grid grid-cols-2 gap-2 items-start justify-between">
7+
<ProductCard
8+
v-for="product in products"
9+
:key="product.id"
10+
:product-id="product.id"
11+
:category-id="categoryId"
12+
/>
13+
</div>
14+
</template>
15+
16+
<script setup lang="ts">
17+
const { categoryId } = defineProps<{
18+
categoryId: string
19+
}>()
20+
21+
const menuStore = useMenuStore()
22+
const category = menuStore.menu?.categories.find((c) => c.id === categoryId)
23+
const products = category?.products.filter((p) => p.isAvailableForPurchase && p.variants.length)
24+
</script>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<template>
2+
<UDrawer
3+
v-if="cityStore.cities.length && !cityStore.selected"
4+
:open="isCitySelectOpened"
5+
:dismissible="false"
6+
should-scale-background
7+
:set-background-color-on-scale="false"
8+
>
9+
<template #content>
10+
<div class="p-4 flex flex-col gap-3 overflow-y-auto">
11+
<h3 class="text-lg font-semibold">
12+
Выберите город
13+
</h3>
14+
15+
<UCheckboxGroup
16+
v-model="selectedCities"
17+
color="primary"
18+
variant="card"
19+
size="lg"
20+
:items="items"
21+
icon="i-lucide-map-pin"
22+
/>
23+
</div>
24+
</template>
25+
</UDrawer>
26+
</template>
27+
28+
<script setup lang="ts">
29+
import type { CheckboxGroupItem } from '@nuxt/ui'
30+
31+
const cityStore = useCityStore()
32+
33+
const items = ref<CheckboxGroupItem[]>(cityStore.cities.map((c) => ({ label: c.name, value: c.id })))
34+
35+
const selectedCities = ref<string[]>([])
36+
const isCitySelectOpened = ref(true)
37+
38+
watch(selectedCities, () => {
39+
if (!selectedCities.value.length) {
40+
isCitySelectOpened.value = true
41+
return
42+
}
43+
44+
cityStore.selected = cityStore.cities.find((c) => c.id === selectedCities.value[0])
45+
isCitySelectOpened.value = false
46+
})
47+
</script>

apps/storefront-telegram/app/components/Navigation.vue

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<nav class="z-50 touch-pan-x fixed bottom-0 left-0 right-0 w-full h-25 tg-bg-bottom-bar border-t border-default rounded-t-lg motion-preset-slide-up">
33
<div class="max-w-[28rem] mx-auto">
4-
<div class="mt-3 grid grid-cols-4">
4+
<div class="mt-3 grid grid-cols-3">
55
<button
66
v-for="route in mainRoutes"
77
:key="route.path"
@@ -44,22 +44,16 @@ const mainRoutes = computed(() => [
4444
exact: true,
4545
},
4646
{
47-
path: '/epic',
48-
name: 'quests',
49-
title: 'Эпики',
50-
icon: 'i-lucide-crown',
47+
path: '/user',
48+
name: 'user',
49+
title: 'Кабинет',
50+
icon: 'i-lucide-user',
5151
},
5252
{
5353
path: '/secret1',
54-
name: 'shop',
55-
title: 'Секрет',
56-
icon: 'i-lucide-lock',
57-
},
58-
{
59-
path: '/secret2',
60-
name: 'top',
61-
title: 'Секрет',
62-
icon: 'i-lucide-lock',
54+
name: 'navigation',
55+
title: 'Меню',
56+
icon: 'i-lucide-menu',
6357
},
6458
])
6559
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<ActiveCard class="!p-0">
3+
<div class="h-full flex flex-col justify-between gap-2">
4+
<div class="relative w-full aspect-3/2">
5+
<ProductImage
6+
:media="product?.media"
7+
:lazy="lazy"
8+
size="md"
9+
/>
10+
</div>
11+
12+
<div class="px-3 py-1 flex-1 flex flex-col gap-1">
13+
<p class="text-sm/4">
14+
{{ product?.name }}
15+
</p>
16+
<div class="font-light text-muted text-sm/4">
17+
<span v-if="!withSingleVariant" class="pr-1 lowercase">{{ $t('app.cart.from') }}</span>
18+
<span>{{ weightValue }}{{ weightUnit }}</span>
19+
</div>
20+
</div>
21+
22+
<div class="px-3 pb-3">
23+
<UButton
24+
variant="soft"
25+
color="neutral"
26+
size="md"
27+
:trailing-icon="withSingleVariant ? 'i-lucide-plus' : 'i-lucide-arrow-right'"
28+
class="w-fit hover:bg-muted active:bg-muted"
29+
>
30+
<div class="text-base font-medium">
31+
<span v-if="!withSingleVariant" class="pr-1 lowercase">{{ $t('app.cart.from') }}</span>
32+
<span>{{ price }}</span>
33+
<span class="pl-1 text-lg">{{ channelStore.currencySign }}</span>
34+
</div>
35+
</UButton>
36+
</div>
37+
</div>
38+
</ActiveCard>
39+
</template>
40+
41+
<script setup lang="ts">
42+
const { productId, categoryId } = defineProps<{
43+
productId: string
44+
categoryId: string
45+
lazy?: boolean
46+
}>()
47+
48+
const { locale } = useI18n()
49+
50+
const channelStore = useChannelStore()
51+
const menuStore = useMenuStore()
52+
const category = computed(() => menuStore.menu?.categories.find((category) => category.id === categoryId))
53+
const product = category.value?.products.find((product) => product.id === productId)
54+
55+
const withSingleVariant = computed<boolean>(() => product?.variants.length === 1)
56+
const smallestVariant = computed(() => product?.variants[0])
57+
58+
const price = computed(() => new Intl.NumberFormat(locale.value).format(smallestVariant.value?.gross ?? 0))
59+
const weightValue = computed(() => smallestVariant.value?.weightValue)
60+
const weightUnit = computed(() => getWeightLocalizedUnit(smallestVariant.value?.weightUnit))
61+
</script>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<template>
2+
<picture v-if="media?.items?.length">
3+
<source type="image/webp" :srcset="srcWebp">
4+
<img
5+
:loading="lazy ? 'lazy' : 'eager'"
6+
:src="src"
7+
alt=""
8+
class="rounded-lg w-full"
9+
>
10+
</picture>
11+
12+
<div v-else class="w-full h-full flex items-center justify-center border-2 border-dashed border-default rounded-lg">
13+
<UIcon
14+
name="i-lucide-camera-off"
15+
class="text-muted/25"
16+
:class="{
17+
'size-6': size === 'xs',
18+
'size-12': size === 'sm',
19+
'size-16': size === 'md',
20+
'size-20': size === 'lg',
21+
'size-24': size === 'xl',
22+
}"
23+
/>
24+
</div>
25+
</template>
26+
27+
<script setup lang="ts">
28+
import type { MediaItem } from '@roll-stack/database'
29+
import type { MediaWithItems } from '~/stores/menu'
30+
31+
const { media, lazy = true, size = 'sm' } = defineProps<{
32+
media?: MediaWithItems | null
33+
lazy?: boolean
34+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
35+
}>()
36+
37+
const sizesMap = {
38+
xs: 120,
39+
sm: 300,
40+
md: 600,
41+
lg: 840,
42+
xl: 1200,
43+
}
44+
45+
const src = computed(() => getNearestImageBySizeAndFormat(sizesMap[size], 'jpg', media?.items ?? [])?.url)
46+
const srcWebp = computed(() => getNearestImageBySizeAndFormat(sizesMap[size], 'webp', media?.items ?? [])?.url)
47+
48+
function getNearestImageBySizeAndFormat(size: number, format: MediaItem['format'], items: MediaItem[]): MediaItem | undefined {
49+
if (!items?.length) {
50+
return
51+
}
52+
53+
const filteredByFormat = items.filter((item) => item.format === format)
54+
55+
return filteredByFormat.reduce((prev, curr) => {
56+
return Math.abs(curr.size - size) < Math.abs(prev.size - size) ? curr : prev
57+
})
58+
}
59+
</script>
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<template>
2-
<div class="relative p-4 tg-bg-section group/section space-y-3.5 rounded-lg">
2+
<div
3+
class="relative w-full h-full p-4 tg-bg-section group/section space-y-3.5 rounded-lg"
4+
:class="[
5+
props.class,
6+
]"
7+
>
38
<slot />
49
</div>
510
</template>
11+
12+
<script setup lang="ts">
13+
const props = defineProps<{ class?: string }>()
14+
</script>

apps/storefront-telegram/app/layouts/default.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<slot />
44
</main>
55

6+
<CitySelector />
67
<Navigation v-if="clientStore.id" />
78
</template>
89

apps/storefront-telegram/app/pages/index.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,25 @@
55
<h2 class="text-lg/5 font-bold">
66
{{ clientStore.name }}, привет!
77
</h2>
8+
9+
<p>{{ cityStore.selected?.name }}</p>
10+
<p>{{ channelStore.name }}</p>
811
</div>
912
</div>
13+
14+
<CategoryBlock
15+
v-for="category in menuStore.menu?.categories"
16+
:key="category.id"
17+
:category-id="category.id"
18+
/>
1019
</PageContainer>
1120
</template>
1221

1322
<script setup lang="ts">
1423
const clientStore = useClientStore()
24+
const cityStore = useCityStore()
25+
const channelStore = useChannelStore()
26+
const menuStore = useMenuStore()
1527
1628
useHead({
1729
title: 'Суши Love',

0 commit comments

Comments
 (0)