This commit is contained in:
2026-03-09 12:30:52 +03:00
parent f925ea1cf1
commit 24c935df5f
15 changed files with 191 additions and 79 deletions

30
package-lock.json generated
View File

@@ -16,7 +16,8 @@
"primevue": "^4.5.4", "primevue": "^4.5.4",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"tailwindcss-primeui": "^0.6.1", "tailwindcss-primeui": "^0.6.1",
"vue": "^3.5.28" "vue": "^3.5.28",
"vuetify": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
@@ -5841,6 +5842,33 @@
"typescript": ">=5.0.0" "typescript": ">=5.0.0"
} }
}, },
"node_modules/vuetify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-4.0.0.tgz",
"integrity": "sha512-TRyNWd2KlX1KXbKwuHYRfrX24yLHq85AdVKmokfy5llAgVx7MNW4oBPwFmYLeuuSrWvw5ITtDJ5VjdBIKD5WVw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/johnleider"
},
"peerDependencies": {
"typescript": ">=4.7",
"vite-plugin-vuetify": ">=2.1.0",
"vue": "^3.5.0",
"webpack-plugin-vuetify": ">=3.1.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
},
"vite-plugin-vuetify": {
"optional": true
},
"webpack-plugin-vuetify": {
"optional": true
}
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -23,7 +23,8 @@
"primevue": "^4.5.4", "primevue": "^4.5.4",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"tailwindcss-primeui": "^0.6.1", "tailwindcss-primeui": "^0.6.1",
"vue": "^3.5.28" "vue": "^3.5.28",
"vuetify": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",

View File

@@ -2,17 +2,15 @@
import { useAuthStore } from '@/stores/auth.ts' import { useAuthStore } from '@/stores/auth.ts'
import SignInView from '@/views/SignInView.vue' import SignInView from '@/views/SignInView.vue'
import MainView from '@/views/MainView.vue' import MainView from '@/views/MainView.vue'
import TestView from '@/views/TestView.vue'
const authStore = useAuthStore() const authStore = useAuthStore()
</script> </script>
<template> <template>
<div class="h-screen bg-gray-200"> <div class="h-screen bg-gray-500">
<div class="h-full m-auto p-4"> <div class="h-full m-auto py-4 md:w-4/5 sm:w-full w-full">
<!-- <MainView v-if="authStore.isAuth" />--> <MainView v-if="authStore.isAuth" />
<!-- <SignInView v-else />--> <SignInView v-else />
<TestView />
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,14 +1,9 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tailwindcss-primeui";
@import "primeicons/primeicons.css";
html { html {
font-size: 13px; font-size: 14px;
font-weight: 400; font-weight: 400;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
font-optical-sizing: auto; font-optical-sizing: auto;
font-style: normal; font-style: normal;
} }
.p-component, .p-component * {
}

13
src/assets/main_.css Normal file
View File

@@ -0,0 +1,13 @@
@import "tailwindcss";
@import "tailwindcss-primeui";
@import "primeicons/primeicons.css";
html {
font-size: 13px;
font-weight: 400;
font-family: "Montserrat", sans-serif;
font-optical-sizing: auto;
font-style: normal;
}
.p-component, .p-component * {}

View File

@@ -9,32 +9,44 @@ interface Props {
const { chat } = defineProps<Props>() const { chat } = defineProps<Props>()
const authStore = useAuthStore() const authStore = useAuthStore()
const chatsStore = useChatsStore()
const getChatName = computed(() => { const chatName = computed(() => {
if (chat.type_id === 2) { switch (chat.type_id) {
return chat.name case 1:
} else if (chat.type_id === 1) { const otherUsers = chat.users.filter((user) => user.id !== authStore.me?.id)
const otherUsers = chat.users.filter((user) => user.id !== authStore.me?.id) return otherUsers[0]?.name ?? otherUsers[0]?.email ?? 'unknown'
return otherUsers[0]?.name ?? otherUsers[0]?.email ?? 'unknown' case 2:
return chat.name
default:
return 'chat'
} }
return 'unknown ID'
}) })
const isSelected = computed(() => { const avatarText = computed(() => {
return chatsStore.selected === chat.id return chatName.value.slice(0, 1).toUpperCase()
})
const lastMessageCreatedAt = computed(() => {
return new Date().toLocaleTimeString('ru-RU', { timeStyle: 'short' })
}) })
</script> </script>
<template> <template>
<div class="border p-2" :class="{ 'bg-gray-400': isSelected }"> <VListItem :value="chat.id">
<!-- {{ chat }}--> <template v-slot:prepend>
<div>id:{{ chat.id }}</div> <VAvatar color="primary" :text="avatarText" />
<div>name:{{ chat.name }}</div> </template>
<div>type:{{ chat.type_id }}</div>
<div>chatName:{{ getChatName }}</div> <template v-slot:append>
</div> <div class="flex flex-col justify-end">
<div class="text-xs">{{ lastMessageCreatedAt }}</div>
<VChip size="small">12</VChip>
</div>
</template>
<v-list-item-title>{{ chatName }}</v-list-item-title>
<v-list-item-subtitle>subtitle</v-list-item-subtitle>
</VListItem>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -2,12 +2,24 @@
import { useChatsStore } from '@/stores/chats.ts' import { useChatsStore } from '@/stores/chats.ts'
import { SocketDataReq, useSocketsStore } from '@/stores/sockets.ts' import { SocketDataReq, useSocketsStore } from '@/stores/sockets.ts'
import ChatListElement from '@/components/ChatListElement.vue' import ChatListElement from '@/components/ChatListElement.vue'
import { onMounted, watchEffect } from 'vue' import { onMounted, ref, watch, watchEffect } from 'vue'
import { type SelectedMenu, useMenuStore } from '@/stores/menu.ts'
const chatsStore = useChatsStore() const chatsStore = useChatsStore()
const socketsStore = useSocketsStore() const socketsStore = useSocketsStore()
const menuStore = useMenuStore()
const selected = ref<string[]>()
const menu = ref<string[]>()
watchEffect(() => getMessages(chatsStore.selected)) watchEffect(() => getMessages(chatsStore.selected))
watch(selected, (val) => {
if (val) chatsStore.selected = val[0]
})
watch(menu, (val) => {
console.log(val)
if (val) menuStore.selected = val[0] as SelectedMenu
})
function getMessages(chatId?: string) { function getMessages(chatId?: string) {
if (!chatId) return if (!chatId) return
@@ -17,28 +29,36 @@ function getMessages(chatId?: string) {
}) })
} }
function onSelect(id: string) {
chatsStore.selected = id
}
onMounted(() => console.log('CHAT LIST')) onMounted(() => console.log('CHAT LIST'))
</script> </script>
<template> <template>
<div class="flex flex-col gap-2 h-full overflow-hidden px-2"> <div class="flex flex-col h-full">
<div>chats</div> <VToolbar>
<VMenu transition="slide-y-transition">
<!-- <div class="overflow-y-auto">--> <template v-slot:activator="{ props }">
<!-- <div v-for="i in 30" :key="i" class="pa-4 border">bla</div>--> <VBtn icon="mdi-menu" v-bind="props" />
<!-- </div>--> </template>
<div class="flex flex-col gap-2 h-full overflow-y-auto"> <VList class="top-1" density="compact" slim v-model:selected="menu">
<chat-list-element <VListItem density="compact" value="profile" prepend-gap="8" title="Profile">
v-for="chat in chatsStore.chats" <template #prepend>
:key="chat.id" <VAvatar color="primary" text="A" />
v-bind="{ chat }" </template>
@click="onSelect(chat.id)" </VListItem>
/> <VDivider class="my-1" />
</div> <VListItem value="users" title="Contacts" prepend-gap="8" prepend-icon="mdi-account" />
<VListItem value="settings" title="Settings" prepend-gap="8" prepend-icon="mdi-cog" />
<VDivider class="my-1" />
<VListItem value="logout" title="Log Out" />
</VList>
</VMenu>
<VSheet class="w-full mx-2">
<VTextField prepend-inner-icon="mdi-magnify" label="search chat"></VTextField>
</VSheet>
</VToolbar>
<VList v-model:selected="selected" mandatory>
<ChatListElement v-for="chat in chatsStore.chats" :key="chat.id" v-bind="{ chat }" />
</VList>
</div> </div>
</template> </template>

View File

@@ -39,7 +39,7 @@ function getChatName(chat: Chat) {
</script> </script>
<template> <template>
<div class="w-full flex flex-col h-full gap-2"> <div class="flex flex-col h-full gap-2">
<slot /> <slot />
</div> </div>
</template> </template>

View File

@@ -30,7 +30,7 @@ watch(messages, async () => {
</script> </script>
<template> <template>
<div class="w-full h-full bg-blue-200 border"> <div class="h-full">
<!-- <button class="position-absolute scroll-down" @click="scrollToBottom">UP</button>--> <!-- <button class="position-absolute scroll-down" @click="scrollToBottom">UP</button>-->
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<div class="flex h-full flex-col overflow-hidden"> <div class="flex h-full flex-col overflow-hidden">

View File

@@ -30,8 +30,13 @@ function onStartChat(userId: number) {
</script> </script>
<template> <template>
<div class="flex flex-col gap-2 h-full overflow-hidden px-2"> <div class="flex flex-col h-full">
<div>contacts</div> <VToolbar>
<VBtn icon="mdi-arrow-left"></VBtn>
<VSheet class="w-full mx-2">
<VTextField prepend-inner-icon="mdi-magnify" label="search contacts"></VTextField>
</VSheet>
</VToolbar>
<div class="flex flex-col gap-2 h-full overflow-y-auto"> <div class="flex flex-col gap-2 h-full overflow-y-auto">
<UsersListElement <UsersListElement

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"></script> <script setup lang="ts"></script>
<template> <template>
<div class="w-16 flex flex-col gap-2 items-stretch p-1 border"> <div class="flex flex-col gap-2 items-stretch p-1 border">
<slot></slot> <slot></slot>
</div> </div>
</template> </template>

View File

@@ -3,17 +3,39 @@ import './assets/main.css'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config' // import PrimeVue from 'primevue/config'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import App from './App.vue' import App from './App.vue'
import { version } from 'vue' import { version } from 'vue'
import theme from '@/themes/noir.ts' import theme from '@/themes/noir.ts'
const app = createApp(App) const app = createApp(App)
console.log('version', version) console.log('version', version)
app.use(PrimeVue, { theme: theme }) // app.use(PrimeVue, { theme: theme })
app.use(createPinia())
const vuetify = createVuetify({
components,
directives,
theme: { defaultTheme: 'light' },
icons: {
defaultSet: 'mdi',
},
defaults: {
VAvatar: { density: 'compact' },
VChip: { density: 'compact' },
VBtn: { color: 'black', variant: 'flat' },
VSwitch: { color: 'black', hideDetails: true },
VOtpInput: { density: 'compact' },
VTextField: { color: 'black', density: 'compact', variant: 'outlined', hideDetails: true },
},
})
app.use(vuetify)
app.use(createPinia())
app.mount('#app') app.mount('#app')

View File

@@ -58,6 +58,7 @@ export const useSocketsStore = defineStore('sockets', () => {
worker.value.port.onmessage = (event) => { worker.value.port.onmessage = (event) => {
const { type, data, connected, message } = event.data const { type, data, connected, message } = event.data
console.log('Received message', data)
switch (type) { switch (type) {
case SocketDataRes.USERS: case SocketDataRes.USERS:

View File

@@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted } from 'vue' import { computed, onMounted } from 'vue'
import { useSocketsStore } from '@/stores/sockets.ts' import { useSocketsStore } from '@/stores/sockets.ts'
import AppMenu from '@/components/AppMenu.vue'
import LeftPane from '@/components/LeftPane.vue' import LeftPane from '@/components/LeftPane.vue'
import RightPane from '@/components/RightPane.vue' import RightPane from '@/components/RightPane.vue'
import UsersList from '@/components/Users/UsersList.vue' import UsersList from '@/components/Users/UsersList.vue'
@@ -13,6 +12,7 @@ const socketsStore = useSocketsStore()
const menuStore = useMenuStore() const menuStore = useMenuStore()
const component = computed(() => { const component = computed(() => {
console.log(menuStore.selected)
switch (menuStore.selected) { switch (menuStore.selected) {
case 'chats': case 'chats':
return ChatsList return ChatsList
@@ -32,13 +32,14 @@ onMounted(() => {
<template> <template>
<div class="flex h-full"> <div class="flex h-full">
<div class="flex w-full"> <div class="md:w-1/3 sm:w-1/2 w-1/2 bg-white">
<AppMenu />
<LeftPane> <LeftPane>
<component :is="component" /> <component :is="component" />
</LeftPane> </LeftPane>
</div> </div>
<RightPane /> <div class="md:w-2/3 sm:w-1/2 w-1/2">
<RightPane></RightPane>
</div>
</div> </div>
</template> </template>

View File

@@ -1,32 +1,48 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useAuthStore } from '@/stores/auth.ts' import { useAuthStore } from '@/stores/auth.ts'
import { JustButton, JustInput } from '@/components/simple' // import {
// Button,
// InputText,
// Card,
// InputOtp,
// ToggleSwitch,
// Divider,
// IconField,
// InputIcon,
// } from 'primevue'
const authStore = useAuthStore() const authStore = useAuthStore()
const email = ref('vadim.olonin@gmail.com') const email = ref('vadim.olonin@gmail.com')
const name = ref('') const name = ref('Vadim')
const otp = ref('123456')
async function onSubmit() { async function onSubmit() {
await authStore.login(email.value) await authStore.login(email.value)
} }
</script> </script>
<template> <template>
<div class="h-full flex flex-col justify-center items-center"> <div class="h-full flex flex-col justify-center items-center px-4">
<div class="bg-white border border-gray-500 p-4 rounded-md w-64"> <VCard class="w-full md:w-1/2">
<form @submit.prevent="onSubmit"> <template #title>
<div class="flex flex-col gap-2"> <div class="text-2xl">Sign in</div>
<div class="">Sign in</div> </template>
<JustInput v-model="email" placeholder="email" type="email" class="" /> <template #text>
<input v-if="false" v-model="name" placeholder="name" /> <form @submit.prevent="onSubmit" class="flex flex-col gap-4">
<div class="flex justify-end"> <VTextField v-model="email" placeholder="email" />
<input type="checkbox" v-if="false" /> <VTextField v-model="name" placeholder="name" />
<JustButton type="submit">sign in</JustButton> <div class="flex justify-center">
<VOtpInput v-model="otp" />
</div> </div>
</div> <div class="flex items-center justify-between gap-2">
</form> <VSwitch hide-details label="Keep me signed in" />
</div> <VBtn type="submit">Sign In</VBtn>
</div>
</form>
</template>
</VCard>
</div> </div>
</template> </template>