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
2 changes: 1 addition & 1 deletion apps/atrium-telegram/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default defineAppConfig({
},
modal: {
slots: {
content: 'divide-y-0',
content: 'divide-y-0 !ring-muted/50',
header: 'pb-0 min-h-12',
title: 'text-lg/5 font-semibold',
},
Expand Down
2 changes: 1 addition & 1 deletion apps/atrium-telegram/app/components/Button.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<button
class="p-3 tg-bg-button w-full rounded-2xl font-medium cursor-pointer active:scale-95 duration-200"
@click="vibrate"
@click="vibrate()"
>
<slot />
</button>
Expand Down
2 changes: 1 addition & 1 deletion apps/atrium-telegram/app/components/CreateCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
base: 'px-3 rounded-full',
label: 'font-medium',
}"
@click="vibrate"
@click="vibrate()"
/>
</div>
</template>
Expand Down
2 changes: 1 addition & 1 deletion apps/atrium-telegram/app/components/Loader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
<img
src="/sushi-heart.svg"
alt=""
class="w-12 grayscale-100 opacity-10 motion-preset-pulse motion-duration-1000"
class="w-12 opacity-50 invert-50 motion-preset-pulse motion-duration-1000"
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

Double-check Tailwind class support for invert-50.

Tailwind’s core provides invert (100%) and invert-0; invert-50 isn’t standard unless you’ve extended utilities. Prefer invert or an arbitrary value (invert-[.5]) if partial inversion is desired.

Apply if you’re not extending Tailwind:

-    class="w-12 opacity-50 invert-50 motion-preset-pulse motion-duration-1000"
+    class="w-12 opacity-50 invert-[.5] motion-preset-pulse motion-duration-1000"

Run to verify Tailwind config and usage:


🏁 Script executed:

#!/bin/bash
fd -a 'tailwind.config.*' | xargs -I{} rg -n 'theme|plugins|extend|invert' {}
rg -n 'invert-50|invert-\[\.\d+\]' -S

Length of output: 251


Replace non-standard invert-50 with a supported Tailwind utility

Our codebase doesn’t include a tailwind.config.* file extending invert to 50%, so invert-50 won’t generate any CSS by default. You have two options:

  • Use Tailwind’s built-in full inversion: invert (100%) or invert-0
  • Use an arbitrary value for partial inversion: invert-[.5]

Since there’s no existing extension, please update the class on line 5 of apps/atrium-telegram/app/components/Loader.vue:

-    class="w-12 opacity-50 invert-50 motion-preset-pulse motion-duration-1000"
+    class="w-12 opacity-50 invert-[.5] motion-preset-pulse motion-duration-1000"
📝 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
class="w-12 opacity-50 invert-50 motion-preset-pulse motion-duration-1000"
class="w-12 opacity-50 invert-[.5] motion-preset-pulse motion-duration-1000"
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/components/Loader.vue around line 5, the class list
uses a non-standard Tailwind utility `invert-50` which won't generate CSS;
replace it with a supported utility such as `invert` (100%) or `invert-0`, or
use an arbitrary value for partial inversion like `invert-[.5]` depending on the
intended visual effect, and update the class string accordingly.

>
</template>
4 changes: 2 additions & 2 deletions apps/atrium-telegram/app/components/Navigation.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<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">
<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">
<div class="max-w-[28rem] mx-auto">
<div class="mt-3 grid grid-cols-4">
<button
Expand All @@ -11,7 +11,7 @@
<div
class="relative py-1 w-full rounded-2xl flex flex-row items-center justify-center"
:class="[
router.currentRoute.value.path === route.path && 'tg-bg-button tg-text-button',
router.currentRoute.value.path === route.path && 'tg-bg-button tg-text-button motion-translate-y-in',
]"
>
<UIcon :name="route.icon" class="size-6" />
Expand Down
16 changes: 5 additions & 11 deletions apps/atrium-telegram/app/components/TaskCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
],
}"
class="group/task duration-200 motion-preset-bounce"
@click="vibrate"
@click="vibrate()"
>
<div class="w-full flex flex-col gap-2 items-start">
<div class="flex flex-col gap-1 items-start text-left">
Expand Down Expand Up @@ -90,9 +90,7 @@ const { task } = defineProps<{
task: Task
}>()

const { t } = useI18n()
const { vibrate } = useFeedback()
const actionToast = useActionToast()
const taskStore = useTaskStore()
const userStore = useUserStore()

Expand Down Expand Up @@ -154,32 +152,28 @@ const items = computed<DropdownMenuItem[]>(() => {
})

async function onFocus() {
const toastId = actionToast.start()

try {
await taskStore.setAsFocused(task.id)
await taskStore.update()
await userStore.update()

actionToast.success(toastId, t('toast.task-focused'))
vibrate('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}

async function onUnfocus() {
const toastId = actionToast.start()

try {
await taskStore.setAsUnfocused(task.id)
await taskStore.update()
await userStore.update()

actionToast.success(toastId, t('toast.task-unfocused'))
vibrate('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}

Expand Down
9 changes: 4 additions & 5 deletions apps/atrium-telegram/app/components/TasksTodaySwitch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
v-model="taskStore.isTodayOnly"
size="xl"
color="secondary"
:label="taskStore.isTodayOnly ? 'Сегодня' : 'Все задачи'"
:default-value="taskStore.isTodayOnly"
:label="taskStore.isTodayOnly ? 'Задачи на сегодня' : 'Показаны все задачи'"
:ui="{
root: 'flex-row-reverse items-center',
label: 'mr-3 text-base/5 font-semibold',
root: 'items-center',
label: 'ml-1.5 text-base/5 font-semibold',
}"
@change="vibrate"
@change="vibrate()"
/>
</template>

Expand Down
3 changes: 3 additions & 0 deletions apps/atrium-telegram/app/components/form/CompleteTask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const { taskId } = defineProps<{
const emit = defineEmits(['success', 'submitted'])

const { t } = useI18n()
const { vibrate } = useFeedback()
const actionToast = useActionToast()
const userStore = useUserStore()
const taskStore = useTaskStore()
Expand Down Expand Up @@ -80,10 +81,12 @@ async function onSubmit(event: FormSubmitEvent<CompleteTask>) {
])

actionToast.success(toastId, t('toast.task-completed'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}
</script>
3 changes: 3 additions & 0 deletions apps/atrium-telegram/app/components/form/CreateTask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const { performerId, listId } = defineProps<{
const emit = defineEmits(['success', 'submitted'])

const { t } = useI18n()
const { vibrate } = useFeedback()
const actionToast = useActionToast()

const taskStore = useTaskStore()
Expand Down Expand Up @@ -170,10 +171,12 @@ async function onSubmit(event: FormSubmitEvent<CreateTask>) {
await taskStore.update()

actionToast.success(toastId, t('toast.task-created'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}
</script>
3 changes: 3 additions & 0 deletions apps/atrium-telegram/app/components/form/CreateTaskList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const emit = defineEmits(['success', 'submitted'])
type FormMember = { label: string, value: string, avatar: { src: string | undefined, alt: string } }

const { t } = useI18n()
const { vibrate } = useFeedback()
const actionToast = useActionToast()

const userStore = useUserStore()
Expand Down Expand Up @@ -103,10 +104,12 @@ async function onSubmit(event: FormSubmitEvent<CreateTaskList>) {
])

actionToast.success(toastId, t('toast.task-list-created'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}
</script>
5 changes: 5 additions & 0 deletions apps/atrium-telegram/app/components/form/UpdateTask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const { taskId } = defineProps<{
const emit = defineEmits(['success', 'submitted'])

const { t } = useI18n()
const { vibrate } = useFeedback()
const actionToast = useActionToast()

const userStore = useUserStore()
Expand Down Expand Up @@ -197,10 +198,12 @@ async function onSubmit(event: FormSubmitEvent<UpdateTask>) {
await taskStore.update()

actionToast.success(toastId, t('toast.task-updated'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}

Expand All @@ -219,10 +222,12 @@ async function onDelete() {
await taskStore.update()

actionToast.success(toastId, t('toast.task-deleted'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}
</script>
5 changes: 5 additions & 0 deletions apps/atrium-telegram/app/components/form/UpdateTaskList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const emit = defineEmits(['success', 'submitted'])
type FormMember = { label: string, value: string, avatar: { src: string | undefined, alt: string } }

const { t } = useI18n()
const { vibrate } = useFeedback()
const actionToast = useActionToast()

const userStore = useUserStore()
Expand Down Expand Up @@ -119,10 +120,12 @@ async function onSubmit(event: FormSubmitEvent<UpdateTaskList>) {
await taskStore.update()

actionToast.success(toastId, t('toast.task-list-updated'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}

Expand All @@ -141,10 +144,12 @@ async function onDelete() {
await taskStore.update()

actionToast.success(toastId, t('toast.task-list-deleted'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { uploadMediaSchema } from '#shared/services/media'
const emit = defineEmits(['success', 'submitted'])

const { t } = useI18n()
const { vibrate } = useFeedback()
const actionToast = useActionToast()

const userStore = useUserStore()
Expand Down Expand Up @@ -74,10 +75,12 @@ async function onSubmit(event: FormSubmitEvent<UploadMedia>) {
await userStore.update()

actionToast.success(toastId, t('toast.photo-loaded'))
vibrate('success')
emit('success')
} catch (error) {
console.error(error)
actionToast.error(toastId)
vibrate('error')
}
}
</script>
12 changes: 10 additions & 2 deletions apps/atrium-telegram/app/composables/useFeedback.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { hapticFeedback } from '@telegram-apps/sdk-vue'

function _useFeedback() {
function vibrate() {
if (hapticFeedback.impactOccurred.isAvailable()) {
function vibrate(type: 'light' | 'success' | 'error' = 'light') {
if (type === 'light' && hapticFeedback.impactOccurred.isAvailable()) {
hapticFeedback.impactOccurred('light')
}

if (type === 'success' && hapticFeedback.notificationOccurred.isAvailable()) {
hapticFeedback.notificationOccurred('success')
}

if (type === 'error' && hapticFeedback.notificationOccurred.isAvailable()) {
hapticFeedback.notificationOccurred('error')
}
}
Comment on lines +4 to 16
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden availability checks and add sensible fallbacks.

Calling .isAvailable() without guarding can throw if the API isn’t wired (SSR, older clients). Also add a fallback impact for success/error when notification isn’t available, and consider supporting 'warning'.

Apply:

-  function vibrate(type: 'light' | 'success' | 'error' = 'light') {
-    if (type === 'light' && hapticFeedback.impactOccurred.isAvailable()) {
-      hapticFeedback.impactOccurred('light')
-    }
-
-    if (type === 'success' && hapticFeedback.notificationOccurred.isAvailable()) {
-      hapticFeedback.notificationOccurred('success')
-    }
-
-    if (type === 'error' && hapticFeedback.notificationOccurred.isAvailable()) {
-      hapticFeedback.notificationOccurred('error')
-    }
-  }
+  function vibrate(type: 'light' | 'success' | 'warning' | 'error' = 'light') {
+    const impactAvailable = !!hapticFeedback?.impactOccurred?.isAvailable?.()
+    const notifAvailable = !!hapticFeedback?.notificationOccurred?.isAvailable?.()
+
+    switch (type) {
+      case 'light':
+        if (impactAvailable) hapticFeedback.impactOccurred('light')
+        break
+      case 'success':
+      case 'warning':
+      case 'error':
+        if (notifAvailable) {
+          hapticFeedback.notificationOccurred(type)
+        } else if (impactAvailable) {
+          hapticFeedback.impactOccurred(type === 'error' ? 'heavy' : 'medium')
+        }
+        break
+    }
+  }
📝 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
function vibrate(type: 'light' | 'success' | 'error' = 'light') {
if (type === 'light' && hapticFeedback.impactOccurred.isAvailable()) {
hapticFeedback.impactOccurred('light')
}
if (type === 'success' && hapticFeedback.notificationOccurred.isAvailable()) {
hapticFeedback.notificationOccurred('success')
}
if (type === 'error' && hapticFeedback.notificationOccurred.isAvailable()) {
hapticFeedback.notificationOccurred('error')
}
}
function vibrate(type: 'light' | 'success' | 'warning' | 'error' = 'light') {
const impactAvailable = !!hapticFeedback?.impactOccurred?.isAvailable?.()
const notifAvailable = !!hapticFeedback?.notificationOccurred?.isAvailable?.()
switch (type) {
case 'light':
if (impactAvailable) hapticFeedback.impactOccurred('light')
break
case 'success':
case 'warning':
case 'error':
if (notifAvailable) {
hapticFeedback.notificationOccurred(type)
} else if (impactAvailable) {
hapticFeedback.impactOccurred(type === 'error' ? 'heavy' : 'medium')
}
break
}
}
🤖 Prompt for AI Agents
In apps/atrium-telegram/app/composables/useFeedback.ts around lines 4 to 16, the
vibrate function calls .isAvailable() and API methods directly which can throw
in SSR or older clients and lacks sensible fallbacks; update the function to
first guard that hapticFeedback and the specific API
(impactOccurred/notificationOccurred) exist and that isAvailable is a function
before calling it (wrap availability checks in a try/catch to avoid runtime
throws), add support for a new 'warning' type, and when notificationOccurred
isn’t available fall back to calling impactOccurred with a neutral/medium impact
(and when impactOccurred isn’t available just no-op safely).


return {
Expand Down
71 changes: 38 additions & 33 deletions apps/atrium-telegram/app/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
<template>
<PageContainer :back="false">
<div class="flex flex-col gap-6">
<div class="flex flex-row gap-3.5 items-center">
<UAvatar
:src="userStore?.avatarUrl ?? undefined"
class="size-14 cursor-pointer hover:scale-95 active:scale-90 duration-200"
@click="handleUploadUserAvatar"
/>
<div class="flex flex-row gap-3.5 items-center">
<UAvatar
:src="userStore?.avatarUrl ?? undefined"
class="size-14 cursor-pointer hover:scale-95 active:scale-90 duration-200"
@click="handleUploadUserAvatar"
/>

<div class="flex flex-col gap-1">
<h2 class="text-xl/6 font-bold tracking-tight">
{{ userStore.name }}, привет!
</h2>
<p class="text-base/5">
<template v-if="myTodayTasks.length">
Сегодня по плану еще
<ULink
as="button"
class="font-semibold underline underline-offset-4 decoration-dashed decoration-1 cursor-pointer"
:class="[
taskStore.isTodayOnly ? 'tg-text' : 'text-secondary',
]"
@click="taskStore.isTodayOnly = !taskStore.isTodayOnly"
>
{{ myTodayTasks.length }} {{ pluralizationRu(myTodayTasks.length, ['задача', 'задачи', 'задач']) }}
</ULink>.
</template>
<span>
Чем займемся?
</span>
</p>
</div>
<div class="flex flex-col gap-1">
<h2 class="text-lg/5 font-bold">
{{ userStore.name }}, привет!
</h2>
<p class="text-base/5">
<template v-if="myTodayTasks.length">
Сегодня по плану еще
<ULink
as="button"
class="font-semibold underline underline-offset-4 decoration-dashed decoration-1 cursor-pointer"
:class="[
taskStore.isTodayOnly ? 'tg-text' : 'text-secondary',
]"
@click="taskStore.isTodayOnly = !taskStore.isTodayOnly"
>
{{ myTodayTasks.length }} {{ pluralizationRu(myTodayTasks.length, ['задача', 'задачи', 'задач']) }}
</ULink>.
</template>
<span>
Чем займемся?
</span>
</p>
</div>
</div>

<div class="flex flex-row items-end justify-end">
<template v-if="taskStore.isInitialized">
<div class="p-4 rounded-lg tg-bg-section">
<TasksTodaySwitch />
</div>
</div>

<div class="mb-20 grid grid-cols-1 gap-4">
<TaskList
v-for="taskList in myLists"
:key="taskList.id"
Expand All @@ -51,7 +49,14 @@
icon="i-lucide-list-todo"
@click="modalCreateTaskList.open()"
/>
</template>
<div v-else>
<div class="py-4 w-full flex justify-center">
<Loader />
</div>
</div>

<div class="mb-20" />
</PageContainer>
</template>

Expand Down
Loading