Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/atrium-telegram/app/components/EpicCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<ActiveCard>
<UIcon name="i-lucide-crown" class="size-8 text-primary" />

<h3 class="text-lg/5 font-bold">
<h3 class="text-xl/5 font-bold">
{{ epic.title }}
</h3>

<div class="w-full text-sm/4 text-muted font-normal whitespace-pre-wrap break-words line-clamp-4">
<div class="w-full text-base/5 font-normal whitespace-pre-wrap break-words line-clamp-5">
{{ epic.description }}
</div>

Expand Down
19 changes: 17 additions & 2 deletions apps/atrium-telegram/app/components/Navigation.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
<template>
<div class="z-50 touch-pan-x sticky inset-0 h-24">
<div class="z-50 touch-pan-x sticky inset-0 h-38">
<div class="w-full h-14 px-4 py-0 flex flex-row flex-nowrap gap-0 items-start justify-center transition-all duration-200 ease-in-out">
<UButton
v-if="isMainPage"
variant="solid"
color="secondary"
size="xl"
class="transition-all duration-200 ease-in-out motion-preset-slide-down motion-duration-200"
icon="i-lucide-plus"
:ui="{
base: 'size-12 font-bold rounded-full',
leadingIcon: 'size-6 mx-auto',
}"
/>
</div>

<nav
v-if="isNavigationShown"
class="w-full h-24 tg-bg-bottom-bar border-t border-default rounded-t-lg motion-preset-slide-up"
Expand All @@ -16,5 +31,5 @@
</template>

<script setup lang="ts">
const { isNavigationShown, mainRoutes } = useNavigation()
const { isNavigationShown, mainRoutes, isMainPage } = useNavigation()
</script>
3 changes: 2 additions & 1 deletion apps/atrium-telegram/app/components/NavigationButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
<UChip
v-else
size="3xl"
:show="!!route.badge"
color="error"
:show="!!route.badge && route.badge !== '0'"
:text="route.badge"
:ui="{
base: '-right-1 px-1.5 py-2 ring-2 tg-text-button font-bold motion-translate-y-loop-25 motion-duration-3500',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<UIcon name="i-lucide-users-round" class="size-8 text-primary" />

<div class="flex flex-row items-end gap-1 font-bold">
<h3 class="text-3xl/5">
<h3 class="text-2xl/5 font-bold">
{{ averageFeedback }}
</h3>
<p class="text-xl/4">
<p class="text-lg/4">
из 5
</p>
</div>
Expand Down
21 changes: 18 additions & 3 deletions apps/atrium-telegram/app/components/flow/ItemCard.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
<template>
<ActiveCard>
<UIcon name="i-lucide-clipboard-check" class="size-8 text-primary" />
<div class="flex flex-row gap-2 items-center">
<UIcon name="i-lucide-clipboard-check" class="size-8 text-primary" />

<h3 class="text-2xl/5 font-bold">
<div v-if="!isViewed" class="flex flex-row items-center gap-1.5 text-error">
<UIcon
name="i-lucide-pointer"
class="size-8 motion-translate-y-loop-25 motion-preset-seesaw motion-duration-2000"
/>
<p class="max-w-22 text-sm/4 font-bold">
Не просмотрено
</p>
</div>
</div>

<h3 class="text-xl/5 font-bold">
{{ item.title }}
</h3>

Expand Down Expand Up @@ -31,7 +43,10 @@
import { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'

defineProps<{
const { item } = defineProps<{
item: FlowItemWithData
}>()

const userStore = useUserStore()
const isViewed = computed(() => item.views.some((view) => view.userId === userStore?.id))
</script>
Comment on lines +50 to 52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix Ref-vs-string comparison in isViewed.

userStore.id is a Ref; compare against its value.

-const userStore = useUserStore()
-const isViewed = computed(() => item.views.some((view) => view.userId === userStore?.id))
+const userStore = useUserStore()
+const { id: userId } = storeToRefs(userStore)
+const isViewed = computed(() => item.views?.some((view) => view.userId === userId.value))
📝 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.

Suggested change
const userStore = useUserStore()
const isViewed = computed(() => item.views.some((view) => view.userId === userStore?.id))
</script>
const userStore = useUserStore()
const { id: userId } = storeToRefs(userStore)
const isViewed = computed(() => item.views?.some((view) => view.userId === userId.value))
</script>
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/flow/ItemCard.vue around lines 50-52, the
computed isViewed compares item.views' userId to userStore.id which is a Ref;
change the comparison to use the Ref's inner value (e.g. use userStore.id.value
or unref(userStore.id)) so the equality checks the actual id string/number
rather than the Ref object, and guard with optional chaining if userStore or id
may be undefined.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
>
<UIcon name="i-lucide-store" class="size-8 text-primary" />
</UChip>
<h3 class="text-3xl/5 font-bold">
<h3 class="text-2xl/5 font-bold">
{{ kitchensOnline }}
</h3>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
<div class="flex flex-row gap-2 items-center">
<UIcon name="i-lucide-receipt-text" class="size-8 text-primary" />
<div class="flex flex-row items-end gap-1 font-bold">
<h3 class="text-3xl/5">
<h3 class="text-2xl/5 font-bold">
{{ averageToday }}
</h3>
<p class="text-xl/4">
<p class="text-lg/4">
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 items-center">
<UIcon name="i-lucide-shopping-bag" class="size-8 text-primary" />
<h3 class="text-3xl/5 font-bold">
<h3 class="text-2xl/5 font-bold">
{{ kitchenStore.todayData?.ordersForNow }}
</h3>
</div>
Expand Down
2 changes: 2 additions & 0 deletions apps/atrium-telegram/app/composables/useNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ function _useNavigation() {
const { y } = useWindowScroll()

const taskStore = useTaskStore()
const flowStore = useFlowStore()

const mainRoutes = computed<NavigationRoute[]>(() => [
{
Expand All @@ -12,6 +13,7 @@ function _useNavigation() {
title: t('app.flow'),
icon: 'i-lucide-waves',
exact: true,
badge: flowStore.nowViewedItemsCount.toString(),
},
{
path: '/epic',
Expand Down
4 changes: 2 additions & 2 deletions apps/atrium-telegram/app/pages/epic/[epicId]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
/>
</div>

<h2 class="text-xl font-bold">
<h1 class="text-2xl/6 font-bold">
{{ epic?.title }}
</h2>
</h1>

<div class="w-full text-base/5 whitespace-pre-wrap break-words">
{{ epic?.description }}
Expand Down
27 changes: 25 additions & 2 deletions apps/atrium-telegram/app/pages/flow/[itemId]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,28 @@
<UIcon name="i-lucide-clipboard-check" class="size-10 text-primary" />
</div>

<h2 class="text-2xl/6 font-bold">
<h1 class="text-2xl/6 font-bold">
{{ item?.title }}
</h2>
</h1>

<div class="w-full text-base/5 whitespace-pre-wrap break-words">
{{ item?.description }}
</div>
</Section>

<Section class="flex flex-col">
<h3 class="text-muted">
Посмотрели
</h3>

<div class="flex flex-row gap-2">
<UAvatar
v-for="view in item?.views"
:key="view.id"
:src="userStore.getAvatarUrl(view.userId)"
/>
</div>
</Section>
</PageContainer>
</template>

Expand All @@ -24,6 +38,15 @@ definePageMeta({

const { params } = useRoute('flow-itemId')

const userStore = useUserStore()
const flowStore = useFlowStore()
const item = computed(() => flowStore.items.find((item) => item.id === params.itemId))

const isViewed = computed(() => item.value?.views.some((view) => view.userId === userStore?.id))

watch(isViewed, () => {
if (!isViewed.value && item.value?.id) {
flowStore.addView(item.value.id)
}
}, { immediate: true })
</script>
4 changes: 2 additions & 2 deletions apps/atrium-telegram/app/pages/tasks/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
/>

<div class="flex flex-col gap-1">
<h2 class="text-lg/5 font-bold">
<h1 class="text-2xl/6 font-bold tracking-tight">
{{ userStore.name }}, привет!
</h2>
</h1>
<p class="text-base/5">
<template v-if="taskStore.myTodayTasks.length">
Сегодня по плану еще
Expand Down
27 changes: 27 additions & 0 deletions apps/atrium-telegram/app/stores/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { initDataRaw as _initDataRaw, useSignal } from '@telegram-apps/sdk-vue'
export const useFlowStore = defineStore('flow', () => {
const items = ref<FlowItemWithData[]>([])

const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === useUserStore().id)).length)

Comment on lines +6 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: comparing a string to a Ref — count will be wrong for all users.

useUserStore().id is a Ref. Comparing view.userId === useUserStore().id always fails, so every item looks “not viewed”.

Fix by hoisting the store and unwrapping id. Consider renaming the var (typo) to notViewedItemsCount.

-  const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === useUserStore().id)).length)
+  const userStore = useUserStore()
+  const { id: userId } = storeToRefs(userStore)
+  const notViewedItemsCount = computed(() =>
+    items.value.filter((item) => !item.views.some((view) => view.userId === userId.value)).length
+  )

Also export the renamed symbol below.

📝 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.

Suggested change
const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === useUserStore().id)).length)
const userStore = useUserStore()
const { id: userId } = storeToRefs(userStore)
const notViewedItemsCount = computed(() =>
items.value.filter((item) => !item.views.some((view) => view.userId === userId.value)).length
)
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/stores/flow.ts around lines 6-7, the computed uses
useUserStore().id (a Ref) inline so the equality always fails; hoist the store
outside the computed (const userStore = useUserStore()), compare view.userId to
userStore.id.value (unwrap the Ref), rename the variable from
nowViewedItemsCount to notViewedItemsCount, and update the export(s) below to
export the new name.

Comment on lines +6 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix Ref-vs-string comparison in computed (using user id).

useUserStore().id is a Ref; you’re comparing string === Ref, which will fail. Bind the store once and unref the id.

Apply:

-  const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === useUserStore().id)).length)
+  const userStore = useUserStore()
+  const { id: userId } = storeToRefs(userStore)
+  const nowViewedItemsCount = computed(() =>
+    items.value.filter((item) => !item.views?.some((view) => view.userId === userId.value)).length
+  )
📝 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.

Suggested change
const nowViewedItemsCount = computed(() => items.value.filter((item) => !item.views.some((view) => view.userId === useUserStore().id)).length)
const userStore = useUserStore()
const { id: userId } = storeToRefs(userStore)
const nowViewedItemsCount = computed(() =>
items.value.filter((item) => !item.views?.some((view) => view.userId === userId.value)).length
)
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/stores/flow.ts around lines 6-7, the computed
predicate compares a string to a Ref (useUserStore().id) which always fails;
cache the store outside the computed (e.g., const user = useUserStore()) and
inside the computed compare against the unwrapped id (user.id.value or
unref(user.id)) so the filter checks string === string and avoid calling
useUserStore() repeatedly.

const initDataRaw = useSignal(_initDataRaw)

async function update() {
Expand All @@ -29,9 +31,34 @@ export const useFlowStore = defineStore('flow', () => {
}
}

async function addView(itemId: string) {
try {
await $fetch(`/api/flow/id/${itemId}/view`, {
method: 'POST',
headers: {
Authorization: `tma ${initDataRaw.value}`,
},
})

await update()
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('401')) {
// No
}
if (error.message.includes('404')) {
// Not found
}
}
}
}

return {
items,

nowViewedItemsCount,

update,
addView,
}
Comment on lines +59 to 63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Export the renamed counter.

If you adopt notViewedItemsCount, update exports:

-    nowViewedItemsCount,
+    notViewedItemsCount,

Grepping for usages to update call sites:


🏁 Script executed:

#!/bin/bash
rg -n "nowViewedItemsCount|notViewedItemsCount" apps/atrium-telegram

Length of output: 366


Export renamed counter and update usages

If you rename nowViewedItemsCount → notViewedItemsCount, update the export in apps/atrium-telegram/app/stores/flow.ts (def: line 6, export: line 59) and replace all references — e.g. apps/atrium-telegram/app/composables/useNavigation.ts:16 (flowStore.nowViewedItemsCount → flowStore.notViewedItemsCount).

🤖 Prompt for AI Agents
In apps/atrium-telegram/app/stores/flow.ts around lines 59-63 the store is still
exporting nowViewedItemsCount but the variable was renamed to
notViewedItemsCount; update the export to export notViewedItemsCount (replace
nowViewedItemsCount) and then update all usages across the repo to the new name
(for example change flowStore.nowViewedItemsCount to
flowStore.notViewedItemsCount in
apps/atrium-telegram/app/composables/useNavigation.ts line ~16 and any other
references).

})
6 changes: 6 additions & 0 deletions apps/atrium-telegram/app/stores/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ export const useUserStore = defineStore('user', () => {
return users.value.find((user) => user.id === userId)
}

function getAvatarUrl(userId: string): string | undefined {
const user = users.value.find((user) => user.id === userId)
return user?.avatarUrl ?? undefined
}

return {
id,
name,
Expand All @@ -131,5 +136,6 @@ export const useUserStore = defineStore('user', () => {
update,
updateOnline,
find,
getAvatarUrl,
}
})
39 changes: 39 additions & 0 deletions apps/atrium-telegram/server/api/flow/id/[itemId]/view.post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { repository } from '@roll-stack/database'

export default defineEventHandler(async (event) => {
try {
const itemId = getRouterParam(event, 'itemId')
if (!itemId) {
throw createError({
statusCode: 400,
message: 'Id is required',
})
}

Comment on lines +5 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add auth guard to avoid NPE and return 401 when unauthenticated.

event.context.user may be undefined; current code would 500 on property access.

   const itemId = getRouterParam(event, 'itemId')
   if (!itemId) {
     throw createError({
       statusCode: 400,
       message: 'Id is required',
     })
   }
+  if (!event.context.user?.id) {
+    throw createError({
+      statusCode: 401,
+      message: 'Unauthorized',
+    })
+  }
📝 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.

Suggested change
const itemId = getRouterParam(event, 'itemId')
if (!itemId) {
throw createError({
statusCode: 400,
message: 'Id is required',
})
}
const itemId = getRouterParam(event, 'itemId')
if (!itemId) {
throw createError({
statusCode: 400,
message: 'Id is required',
})
}
if (!event.context.user?.id) {
throw createError({
statusCode: 401,
message: 'Unauthorized',
})
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/server/api/flow/id/[itemId]/view.post.ts around lines
5-12, add an authentication guard that checks event.context?.user before any
property access and return a 401 when missing; specifically, before using itemId
or any user properties, if event.context?.user is falsy throw createError({
statusCode: 401, message: 'Unauthorized' }); then proceed with the existing
itemId validation (400) as before so you avoid null/undefined property access
and correctly signal unauthenticated requests.

// Guards:
// If not exist
// If already viewed
const item = await repository.flow.findItem(itemId)
if (!item) {
throw createError({
statusCode: 404,
message: 'Item not found',
})
}
if (item.views.some((view) => view.userId === event.context.user.id)) {
throw createError({
statusCode: 400,
message: 'Already viewed',
})
}

await repository.flow.createItemView({
itemId,
userId: event.context.user.id,
})

return { ok: true }
} catch (error) {
throw errorResolver(error)
}
})
3 changes: 2 additions & 1 deletion apps/atrium-telegram/shared/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FeedbackPoint, FlowItem, FlowItemComment, Kitchen, PartnerAgreement } from '@roll-stack/database'
import type { FeedbackPoint, FlowItem, FlowItemComment, FlowItemView, Kitchen, PartnerAgreement } from '@roll-stack/database'

export type KitchenWithData = Kitchen & {
openTime: number
Expand All @@ -10,6 +10,7 @@ export type KitchenWithData = Kitchen & {

export type FlowItemWithData = FlowItem & {
comments: FlowItemComment[]
views: FlowItemView[]
}

export type NavigationRoute = {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"vitest-browser-vue": "catalog:"
},
"resolutions": {
"rollup": "^4.50.2",
"unimport": "4.1.1"
},
"lint-staged": {
Expand Down
13 changes: 11 additions & 2 deletions packages/database/src/repository/flow.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import type { FlowItemDraft } from '../types'
import type { FlowItemDraft, FlowItemViewDraft } from '../types'
import { eq, sql } from 'drizzle-orm'
import { useDatabase } from '../database'
import { flowItems } from '../tables'
import { flowItems, flowItemViews } from '../tables'

export class Flow {
static async findItem(id: string) {
return useDatabase().query.flowItems.findFirst({
where: (item, { eq }) => eq(item.id, id),
with: {
views: true,
},
})
}

Expand All @@ -18,6 +21,7 @@ export class Flow {
comments: {
orderBy: (comments, { desc }) => desc(comments.createdAt),
},
views: true,
},
})
}
Expand All @@ -27,6 +31,11 @@ export class Flow {
return item
}

static async createItemView(data: FlowItemViewDraft) {
const [view] = await useDatabase().insert(flowItemViews).values(data).returning()
return view
}

static async updateItem(id: string, data: Partial<FlowItemDraft>) {
const [item] = await useDatabase()
.update(flowItems)
Expand Down
Loading