Files
app-v2/src/App.vue
2026-01-19 12:11:58 +03:00

199 lines
6.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import SpaceList from "@/components/space-list/SpaceList.vue";
import Toolbar from "@/components/Toolbar.vue";
import Toast from "primevue/toast";
import ProgressSpinner from "primevue/progressspinner";
import { useSpaceStore } from "@/stores/spaceStore";
import { useToolbarStore } from "@/stores/toolbar-store";
import router from "@/router";
import { useRoute } from "vue-router";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useToast } from "primevue/usetoast";
import { useUserStore } from "@/stores/userStore";
const spaceStore = useSpaceStore();
const toolbarStore = useToolbarStore();
const route = useRoute();
const platform = ref<string>("unknown")
const toast = useToast();
const userStore = useUserStore();
const isLoading = ref(true);
const tgApp = (window as any)?.Telegram?.WebApp;
const isTelegram = computed(() => !!tgApp);
const isSpaceSelectorVisible = ref(false);
const isSpaceSelected = computed(
() => spaceStore.selectedSpaceId === undefined || isSpaceSelectorVisible.value
);
const menu = [
{ name: "Dashboard", icon: "pi pi-chart-bar", link: "/", navStack: 'dashboard' },
{ name: "Analytics", icon: "pi pi-chart-line", link: "/analytics", navStack: 'analytics' },
{ name: "Transactions", icon: "pi pi-list", link: "/transactions", navStack: 'transactions' },
{ name: "Settings", icon: "pi pi-cog", link: "/settings", navStack: 'settings' },
];
function spaceSelected() {
router.push("/");
isSpaceSelectorVisible.value = false;
}
let backHandler: (() => void) | null = null;
function setupBackButton() {
if (!tgApp.initData) return;
// снять старый обработчик
if (backHandler) {
tgApp.BackButton.offClick(backHandler);
backHandler = null;
}
console.log('history legth:' + window.history.length)
if (window.history.length > 1) {
tgApp.BackButton.show();
backHandler = () => {
if (window.history.length > 1) {
router.back();
} else {
tgApp.close();
}
};
tgApp.BackButton.onClick(backHandler);
} else {
// tgApp.BackButton.show();
backHandler = () => {
tgApp.close();
};
tgApp.BackButton.onClick(backHandler);
}
}
const isNavVisible = ref(true)
const isInputFocused = ref(false)
const handleFocusIn = () => {
isNavVisible.value = false
isInputFocused.value = true
}
const handleFocusOut = () => {
isNavVisible.value = true
isInputFocused.value = false
}
const blurAllInputs = () => {
// Снимаем фокус со всех активных элементов
const activeElement = document.activeElement as HTMLElement
if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
activeElement.blur()
}
}
onMounted(async () => {
// setTimeout(async () => {
// if (!userStore.isAuthorized) await router.push(`/login?back=${route.path}`)
// }, 1000)
//
await userStore.fetchUserProfile()
await spaceStore.getSpace()
// setTimeout(async () => {
// if (!userStore.isAuthorized) await router.push(`/login?back=${route.path}`)
// }, 100)
toolbarStore.registerHandler("openSpacePicker", () => {
isSpaceSelectorVisible.value = true;
});
document.addEventListener('focusin', handleFocusIn)
document.addEventListener('focusout', handleFocusOut)
blurAllInputs()
if (tgApp.initData) {
try {
tgApp.expand?.();
platform.value = tgApp.platform
if (['ios', 'android'].includes(platform.value)) {
tgApp.requestFullscreen?.();
}
tgApp.lockOrientation?.();
tgApp.disableVerticalSwipes()
tgApp.ready();
setupBackButton();
} catch (err) {
console.warn("Telegram WebApp init error:", err);
}
}
isLoading.value = false;
});
// 🔁 следим за изменением маршрута
watch(
() => route.path,
() => setupBackButton()
);
onBeforeUnmount(() => {
toolbarStore.unregisterHandler("openSpacePicker");
document.removeEventListener('focusin', handleFocusIn)
document.removeEventListener('focusout', handleFocusOut)
tgApp?.BackButton?.hide();
if (backHandler) tgApp?.BackButton?.offClick(backHandler);
});
</script>
<template>
<Toast />
<div v-if="isLoading">
<ProgressSpinner />
</div>
<div v-else>
<div v-if="!userStore.isAuthorized">
<router-view />
</div>
<div v-else class="flex flex-col tg " :class="['ios', 'android'].includes(platform) ? '!pt-10' : ''">
<SpaceList v-if="isSpaceSelected && userStore.isAuthorized" @space-selected="spaceSelected" />
<div v-else class="flex flex-col w-full gap-4">
<div class="flex w-full flex flex-row items-end justify-end pt-2 pe-4">
<Toolbar />
</div>
<div class="flex flex-col w-full h-full items-end px-4 gap-4 pb-6">
<router-view class=" w-full" />
</div>
<button v-if="isInputFocused" @click="blurAllInputs"
class="fixed bottom-4 right-4 z-50 bg-blue-500 text-white px-4 py-2 rounded-lg shadow-lg">
Готово
</button>
<nav v-if="isNavVisible" class="fixed bottom-4 left-1/2 -translate-x-1/2 z-50"
style="padding-bottom: var(--tg-content-safe-area-inset-bottom) !important;">
<div class="flex items-center justify-between py-2 bg-white rounded-4xl px-6 shadow">
<router-link v-for="item in menu" :key="item.link" :to="item.link"
class="flex w-fit h-full flex-col items-center gap-2 !py-2 !px-4"
:class="route.meta.navStack === item.navStack ? 'bg-green-100 rounded-2xl ' : ''">
<i class="!text-lg" :class="item.icon" />
<span class="font-medium text-gray-900">{{ item.name }}</span>
</router-link>
</div>
</nav>
<div class="flex h-16" />
</div>
</div>
</div>
</template>
<style scoped>
.tg {
width: 100% !important;
margin: var(--tg-content-safe-area-inset-top) var(--tg-content-safe-area-inset-right) 0 var(--tg-content-safe-area-inset-left) !important;
}
</style>