initial commit
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
<script setup>
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
import { useChat } from './useChat'
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
import { avatarText } from '@core/utils/formatters'
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const store = useChatStore()
|
||||
const { resolveAvatarBadgeVariant } = useChat()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="store.activeChat">
|
||||
<!-- Close Button -->
|
||||
<div
|
||||
class="pt-2 me-2"
|
||||
:class="$vuetify.locale.isRtl ? 'text-left' : 'text-right'"
|
||||
>
|
||||
<IconBtn @click="$emit('close')">
|
||||
<VIcon
|
||||
icon="tabler-x"
|
||||
color="disabled"
|
||||
class="text-medium-emphasis"
|
||||
/>
|
||||
</IconBtn>
|
||||
</div>
|
||||
|
||||
<!-- User Avatar + Name + Role -->
|
||||
<div class="text-center px-6">
|
||||
<VBadge
|
||||
location="bottom right"
|
||||
offset-x="7"
|
||||
offset-y="4"
|
||||
bordered
|
||||
:color="resolveAvatarBadgeVariant(store.activeChat.contact.status)"
|
||||
class="chat-user-profile-badge mb-5"
|
||||
>
|
||||
<VAvatar
|
||||
size="80"
|
||||
:variant="!store.activeChat.contact.avatar ? 'tonal' : undefined"
|
||||
:color="!store.activeChat.contact.avatar ? resolveAvatarBadgeVariant(store.activeChat.contact.status) : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="store.activeChat.contact.avatar"
|
||||
:src="store.activeChat.contact.avatar"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="text-3xl"
|
||||
>{{ avatarText(store.activeChat.contact.fullName) }}</span>
|
||||
</VAvatar>
|
||||
</VBadge>
|
||||
<h5 class="text-h5">
|
||||
{{ store.activeChat.contact.fullName }}
|
||||
</h5>
|
||||
<p class="text-capitalize text-medium-emphasis">
|
||||
{{ store.activeChat.contact.role }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- User Data -->
|
||||
<PerfectScrollbar
|
||||
class="ps-chat-user-profile-sidebar-content text-medium-emphasis pb-5 px-5"
|
||||
:options="{ wheelPropagation: false }"
|
||||
>
|
||||
<!-- About -->
|
||||
<div class="my-8">
|
||||
<span class="text-sm text-disabled">ABOUT</span>
|
||||
<p class="mt-2">
|
||||
{{ store.activeChat.contact.about }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Personal Information -->
|
||||
<div class="mb-8">
|
||||
<span class="d-block text-sm text-disabled mb-3">PERSONAL INFORMATION</span>
|
||||
<div class="d-flex align-center">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-mail"
|
||||
size="24"
|
||||
/>
|
||||
<span>lucifer@email.com</span>
|
||||
</div>
|
||||
<div class="d-flex align-center my-3">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-phone-call"
|
||||
size="24"
|
||||
/>
|
||||
<span>+1(123) 456 - 7890</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-clock"
|
||||
size="24"
|
||||
/>
|
||||
<span>Mon - Fri 10AM - 8PM</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div>
|
||||
<span class="d-block text-sm text-disabled mb-3">OPTIONS</span>
|
||||
<div class="d-flex align-center">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-badge"
|
||||
size="24"
|
||||
/>
|
||||
<span>Add Tag</span>
|
||||
</div>
|
||||
<div class="d-flex align-center my-3">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-star"
|
||||
size="24"
|
||||
/>
|
||||
<span>Important Contact</span>
|
||||
</div>
|
||||
<div class="d-flex align-center mb-3">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-photo"
|
||||
size="24"
|
||||
/>
|
||||
<span>Shared Media</span>
|
||||
</div>
|
||||
<div class="d-flex align-center mb-3">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="tabler-trash"
|
||||
size="24"
|
||||
/>
|
||||
<span>Delete Contact</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<VIcon
|
||||
class="me-2 ms-1"
|
||||
size="18"
|
||||
icon="tabler-ban"
|
||||
/>
|
||||
<span>Block Contact</span>
|
||||
</div>
|
||||
</div>
|
||||
</PerfectScrollbar>
|
||||
</template>
|
||||
</template>
|
91
resources/js/views/apps/chat/ChatContact.vue
Normal file
91
resources/js/views/apps/chat/ChatContact.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup>
|
||||
import { useChat } from '@/views/apps/chat/useChat'
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
import {
|
||||
avatarText,
|
||||
formatDateToMonthShort,
|
||||
} from '@core/utils/formatters'
|
||||
|
||||
const props = defineProps({
|
||||
isChatContact: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
user: {
|
||||
type: null,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const store = useChatStore()
|
||||
const { resolveAvatarBadgeVariant } = useChat()
|
||||
|
||||
const isChatContactActive = computed(() => {
|
||||
const isActive = store.activeChat?.contact.id === props.user.id
|
||||
if (!props.isChatContact)
|
||||
return !store.activeChat?.chat && isActive
|
||||
|
||||
return isActive
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li :key="store.chatsContacts.length" class="chat-contact cursor-pointer d-flex align-center"
|
||||
:class="{ 'chat-contact-active': isChatContactActive }">
|
||||
<VBadge dot location="bottom right" offset-x="3" offset-y="0"
|
||||
:color="resolveAvatarBadgeVariant(props.user.status)" bordered :model-value="props.isChatContact">
|
||||
<VAvatar size="38" :variant="!props.user.avatar ? 'tonal' : undefined"
|
||||
:color="!props.user.avatar ? resolveAvatarBadgeVariant(props.user.status) : undefined">
|
||||
<VImg v-if="props.user.avatar" :src="props.user.avatar" alt="John Doe" />
|
||||
<span v-else>{{ avatarText(user.fullName) }}</span>
|
||||
</VAvatar>
|
||||
</VBadge>
|
||||
<div class="flex-grow-1 ms-4 overflow-hidden">
|
||||
<p class="text-base text-high-emphasis mb-0">
|
||||
{{ props.user.fullName }}
|
||||
</p>
|
||||
<p class="mb-0 text-truncate text-body-2">
|
||||
{{ props.isChatContact && 'chat' in props.user ? props.user.chat.lastMessage.message : props.user.about
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="props.isChatContact && 'chat' in props.user" class="d-flex flex-column align-self-start">
|
||||
<span class="text-body-2 text-disabled whitespace-no-wrap">{{
|
||||
formatDateToMonthShort(props.user.chat.lastMessage.time) }}</span>
|
||||
<VBadge v-if="props.user.chat.unseenMsgs" color="error" inline :content="props.user.chat.unseenMsgs"
|
||||
class="ms-auto" />
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@styles/variables/_vuetify.scss";
|
||||
@use "@core/scss/base/mixins";
|
||||
@use "vuetify/lib/styles/tools/states" as vuetifyStates;
|
||||
|
||||
.chat-contact {
|
||||
border-radius: vuetify.$border-radius-root;
|
||||
padding-block: 8px;
|
||||
padding-inline: 12px;
|
||||
|
||||
@include mixins.before-pseudo;
|
||||
@include vuetifyStates.states($active: false);
|
||||
|
||||
&.chat-contact-active {
|
||||
background: linear-gradient(72.47deg, rgb(var(--v-theme-primary)) 0%, #fff 300%);
|
||||
color: #fff;
|
||||
|
||||
--v-theme-on-background: #fff;
|
||||
|
||||
.v-avatar {
|
||||
background: #fff;
|
||||
outline: 2px solid #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.v-badge--bordered .v-badge__badge::after {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
96
resources/js/views/apps/chat/ChatLeftSidebarContent.vue
Normal file
96
resources/js/views/apps/chat/ChatLeftSidebarContent.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<script setup>
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
import { useChat } from './useChat'
|
||||
import ChatContact from '@/views/apps/chat/ChatContact.vue'
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
import AppTextField from '@/@core/app-form-elements/AppTextField.vue'
|
||||
const props = defineProps({
|
||||
search: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isDrawerOpen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'openChatOfContact',
|
||||
'showUserProfile',
|
||||
'close',
|
||||
])
|
||||
|
||||
const { resolveAvatarBadgeVariant } = useChat()
|
||||
const search = useVModel(props, 'search')
|
||||
const store = useChatStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 👉 Chat list header -->
|
||||
<div v-if="store.profileUser" class="chat-list-header">
|
||||
<VBadge dot location="bottom right" offset-x="3" offset-y="3"
|
||||
:color="resolveAvatarBadgeVariant(store.profileUser.status)" bordered>
|
||||
<VAvatar size="38" class="cursor-pointer" @click="$emit('showUserProfile')">
|
||||
<VImg :src="store.profileUser.avatar" alt="John Doe" />
|
||||
</VAvatar>
|
||||
</VBadge>
|
||||
|
||||
<AppTextField v-model="search" placeholder="Search..." class="app-text-field flex-grow-1 ms-4 chat-list-search"
|
||||
density="compact">
|
||||
<template #prepend-inner>
|
||||
<VIcon size="22" icon="tabler-search" />
|
||||
</template>
|
||||
</AppTextField>
|
||||
|
||||
<IconBtn v-if="$vuetify.display.smAndDown" @click="$emit('close')">
|
||||
<VIcon icon="tabler-x" class="text-medium-emphasis" />
|
||||
</IconBtn>
|
||||
</div>
|
||||
<VDivider />
|
||||
|
||||
<PerfectScrollbar tag="ul" class="d-flex flex-column gap-y-1 chat-contacts-list px-3 list-none"
|
||||
:options="{ wheelPropagation: false }">
|
||||
<li>
|
||||
<span class="chat-contact-header d-block text-primary text-xl font-weight-medium">Chats</span>
|
||||
</li>
|
||||
|
||||
<ChatContact v-for="contact in store.chatsContacts" :key="`chat-${contact.id}`" :user="contact" is-chat-contact
|
||||
@click="$emit('openChatOfContact', contact.id)" />
|
||||
|
||||
<span v-show="!store.chatsContacts.length" class="no-chat-items-text text-disabled">No chats found</span>
|
||||
|
||||
<li>
|
||||
<span class="chat-contact-header d-block text-primary text-xl font-weight-medium">Contacts</span>
|
||||
</li>
|
||||
|
||||
<ChatContact v-for="contact in store.contacts" :key="`chat-${contact.id}`" :user="contact"
|
||||
@click="$emit('openChatOfContact', contact.id)" />
|
||||
|
||||
<span v-show="!store.contacts.length" class="no-chat-items-text text-disabled">No contacts found</span>
|
||||
</PerfectScrollbar>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.chat-contacts-list {
|
||||
--chat-content-spacing-x: 16px;
|
||||
|
||||
padding-block-end: 0.75rem;
|
||||
|
||||
.chat-contact-header {
|
||||
margin-block-end: 0.625rem;
|
||||
margin-block-start: 1.25rem;
|
||||
}
|
||||
|
||||
.chat-contact-header,
|
||||
.no-chat-items-text {
|
||||
margin-inline: var(--chat-content-spacing-x);
|
||||
}
|
||||
}
|
||||
|
||||
.chat-list-search {
|
||||
.v-field--focused {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
125
resources/js/views/apps/chat/ChatLog.vue
Normal file
125
resources/js/views/apps/chat/ChatLog.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<script setup>
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
import { formatDate } from '@core/utils/formatters'
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
const store = useChatStore()
|
||||
|
||||
const contact = computed(() => ({
|
||||
id: store.activeChat?.contact.id,
|
||||
avatar: store.activeChat?.contact.avatar,
|
||||
}))
|
||||
|
||||
const resolveFeedbackIcon = feedback => {
|
||||
if (feedback.isSeen)
|
||||
return {
|
||||
icon: 'tabler-checks',
|
||||
color: 'success',
|
||||
}
|
||||
else if (feedback.isDelivered)
|
||||
return {
|
||||
icon: 'tabler-checks',
|
||||
color: undefined,
|
||||
}
|
||||
else
|
||||
return {
|
||||
icon: 'tabler-check',
|
||||
color: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const msgGroups = computed(() => {
|
||||
let messages = []
|
||||
const _msgGroups = []
|
||||
if (store.activeChat.chat) {
|
||||
messages = store.activeChat.chat.messages
|
||||
let msgSenderId = messages[0].senderId
|
||||
let msgGroup = {
|
||||
senderId: msgSenderId,
|
||||
messages: [],
|
||||
}
|
||||
messages.forEach((msg, index) => {
|
||||
if (msgSenderId === msg.senderId) {
|
||||
msgGroup.messages.push({
|
||||
message: msg.message,
|
||||
time: msg.time,
|
||||
feedback: msg.feedback,
|
||||
})
|
||||
} else {
|
||||
msgSenderId = msg.senderId
|
||||
_msgGroups.push(msgGroup)
|
||||
msgGroup = {
|
||||
senderId: msg.senderId,
|
||||
messages: [{
|
||||
message: msg.message,
|
||||
time: msg.time,
|
||||
feedback: msg.feedback,
|
||||
}],
|
||||
}
|
||||
}
|
||||
if (index === messages.length - 1)
|
||||
_msgGroups.push(msgGroup)
|
||||
})
|
||||
}
|
||||
|
||||
return _msgGroups
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chat-log pa-5">
|
||||
|
||||
<div v-for="(msgGrp, index) in msgGroups" :key="msgGrp.senderId + String(index)"
|
||||
class="chat-group d-flex align-start" :class="[{
|
||||
'flex-row-reverse': msgGrp.senderId !== contact.id,
|
||||
'mb-4': msgGroups.length - 1 !== index,
|
||||
}]">
|
||||
<div class="chat-avatar" :class="msgGrp.senderId !== contact.id ? 'ms-4' : 'me-4'">
|
||||
<VAvatar size="32">
|
||||
<VImg :src="msgGrp.senderId === contact.id ? contact.avatar : store.profileUser?.avatar" />
|
||||
</VAvatar>
|
||||
</div>
|
||||
<div class="chat-body d-inline-flex flex-column"
|
||||
:class="msgGrp.senderId !== contact.id ? 'align-end' : 'align-start'">
|
||||
<div v-for="(msgData, msgIndex) in msgGrp.messages" :key="msgData.time"
|
||||
class="chat-content py-2 px-4 elevation-1" style="background-color: rgb(var(--v-theme-surface));"
|
||||
:class="[
|
||||
msgGrp.senderId === contact.id ? 'chat-left' : 'bg-primary text-white chat-right',
|
||||
msgGrp.messages.length - 1 !== msgIndex ? 'mb-3' : 'mb-1',
|
||||
]">
|
||||
<p class="mb-0 text-base"> {{ msgData.message }}</p>
|
||||
</div>
|
||||
<div :class="{ 'text-right': msgGrp.senderId !== contact.id }">
|
||||
<VIcon v-if="msgGrp.senderId !== contact.id" size="18"
|
||||
:color="resolveFeedbackIcon(msgGrp.messages[msgGrp.messages.length - 1].feedback).color">
|
||||
{{ resolveFeedbackIcon(msgGrp.messages[msgGrp.messages.length - 1].feedback).icon }}
|
||||
</VIcon>
|
||||
<span class="text-sm ms-1 text-disabled">{{ formatDate(msgGrp.messages[msgGrp.messages.length -
|
||||
1].time, { hour: 'numeric', minute: 'numeric' }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang=scss>
|
||||
.chat-log {
|
||||
.chat-content {
|
||||
border-end-end-radius: 6px;
|
||||
border-end-start-radius: 6px;
|
||||
|
||||
&.chat-left {
|
||||
border-start-end-radius: 6px;
|
||||
}
|
||||
|
||||
&.chat-right {
|
||||
border-start-start-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: .9375rem !important;
|
||||
line-height: 1.375rem !important;
|
||||
}
|
||||
</style>
|
111
resources/js/views/apps/chat/ChatUserProfileSidebarContent.vue
Normal file
111
resources/js/views/apps/chat/ChatUserProfileSidebarContent.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<script setup>
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
import { useChat } from './useChat'
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
import { avatarText } from '@core/utils/formatters'
|
||||
import AppTextarea from '@/@core/app-form-elements/AppTextarea.vue'
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
|
||||
// composables
|
||||
const store = useChatStore()
|
||||
const { resolveAvatarBadgeVariant } = useChat()
|
||||
|
||||
const userStatusRadioOptions = [
|
||||
{
|
||||
title: 'Online',
|
||||
value: 'online',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
title: 'Away',
|
||||
value: 'away',
|
||||
color: 'warning',
|
||||
},
|
||||
{
|
||||
title: 'Do not disturb',
|
||||
value: 'busy',
|
||||
color: 'error',
|
||||
},
|
||||
{
|
||||
title: 'Offline',
|
||||
value: 'offline',
|
||||
color: 'secondary',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="store.profileUser">
|
||||
<!-- Close Button -->
|
||||
<div class="pt-2 me-2 text-end">
|
||||
<IconBtn @click="$emit('close')">
|
||||
<VIcon class="text-medium-emphasis" color="disabled" icon="tabler-x" />
|
||||
</IconBtn>
|
||||
</div>
|
||||
|
||||
<!-- User Avatar + Name + Role -->
|
||||
<div class="text-center px-6">
|
||||
<VBadge location="bottom right" offset-x="7" offset-y="4" bordered
|
||||
:color="resolveAvatarBadgeVariant(store.profileUser.status)" class="chat-user-profile-badge mb-3">
|
||||
<VAvatar size="84" :variant="!store.profileUser.avatar ? 'tonal' : undefined"
|
||||
:color="!store.profileUser.avatar ? resolveAvatarBadgeVariant(store.profileUser.status) : undefined">
|
||||
<VImg v-if="store.profileUser.avatar" :src="store.profileUser.avatar" />
|
||||
<span v-else class="text-3xl">{{ avatarText(store.profileUser.fullName) }}</span>
|
||||
</VAvatar>
|
||||
</VBadge>
|
||||
<h5 class="text-h5">
|
||||
{{ store.profileUser.fullName }}
|
||||
</h5>
|
||||
<p class="text-capitalize text-medium-emphasis">
|
||||
{{ store.profileUser.role }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- User Data -->
|
||||
<PerfectScrollbar class="ps-chat-user-profile-sidebar-content pb-5 px-5" :options="{ wheelPropagation: false }">
|
||||
<!-- About -->
|
||||
<div class="my-5 text-medium-emphasis">
|
||||
<span for="textarea-user-about" class="text-sm text-disabled">ABOUT</span>
|
||||
<AppTextarea id="textarea-user-about" v-model="store.profileUser.about" auto-grow class="mt-1"
|
||||
rows="4" />
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="mb-5">
|
||||
<span class="text-sm text-disabled">STATUS</span>
|
||||
<VRadioGroup v-model="store.profileUser.status" class="mt-1">
|
||||
<VRadio v-for="radioOption in userStatusRadioOptions" :key="radioOption.title"
|
||||
:label="radioOption.title" :value="radioOption.value" :color="radioOption.color" />
|
||||
</VRadioGroup>
|
||||
</div>
|
||||
|
||||
<!-- Settings -->
|
||||
<div class="text-medium-emphasis">
|
||||
<span class="text-sm text-disabled">SETTINGS</span>
|
||||
|
||||
<div class="d-flex align-center my-3">
|
||||
<VIcon class="me-2" icon="tabler-message-dots" size="22" />
|
||||
<span class="text-high-emphasis">Two-step Verification</span>
|
||||
</div>
|
||||
<div class="d-flex align-center mb-3">
|
||||
<VIcon class="me-2" icon="tabler-bell" size="22" />
|
||||
<span class="text-high-emphasis">Notification</span>
|
||||
</div>
|
||||
<div class="d-flex align-center mb-3">
|
||||
<VIcon class="me-2" icon="tabler-user-plus" size="22" />
|
||||
<span class="text-high-emphasis">Invite Friends</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<VIcon class="me-2" icon="tabler-trash" size="22" />
|
||||
<span class="text-high-emphasis">Delete Account</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<VBtn color="primary" class="mt-11">
|
||||
Logout
|
||||
</VBtn>
|
||||
</PerfectScrollbar>
|
||||
</template>
|
||||
</template>
|
16
resources/js/views/apps/chat/useChat.js
Normal file
16
resources/js/views/apps/chat/useChat.js
Normal file
@@ -0,0 +1,16 @@
|
||||
export const useChat = () => {
|
||||
const resolveAvatarBadgeVariant = status => {
|
||||
if (status === 'online')
|
||||
return 'success'
|
||||
if (status === 'busy')
|
||||
return 'error'
|
||||
if (status === 'away')
|
||||
return 'warning'
|
||||
|
||||
return 'secondary'
|
||||
}
|
||||
|
||||
return {
|
||||
resolveAvatarBadgeVariant,
|
||||
}
|
||||
}
|
74
resources/js/views/apps/chat/useChatStore.js
Normal file
74
resources/js/views/apps/chat/useChatStore.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import axios from '@axios';
|
||||
|
||||
export const useChatStore = defineStore('chat', {
|
||||
// ℹ️ arrow function recommended for full type inference
|
||||
state: () => ({
|
||||
contacts: [],
|
||||
chatsContacts: [],
|
||||
profileUser: undefined,
|
||||
activeChat: null,
|
||||
}),
|
||||
actions: {
|
||||
async fetchChatsAndContacts(q) {
|
||||
|
||||
|
||||
const { data } = await axios.get('/apps/chat/chats-and-contacts', {
|
||||
params: { q },
|
||||
})
|
||||
|
||||
|
||||
const { chatsContacts, contacts, profileUser } = data
|
||||
|
||||
this.chatsContacts = chatsContacts
|
||||
this.contacts = contacts
|
||||
this.profileUser = profileUser
|
||||
},
|
||||
async getChat(userId) {
|
||||
const { data } = await axios.get(`/apps/chat/chats/${userId}`)
|
||||
|
||||
this.activeChat = data
|
||||
},
|
||||
async sendMsg(message) {
|
||||
const senderId = this.profileUser?.id
|
||||
const { data } = await axios.post(`/apps/chat/chats/${this.activeChat?.contact.id}`, { message, senderId })
|
||||
const { msg, chat } = data
|
||||
console.log(this.activeChat?.contact.id,senderId)
|
||||
console.log(data)
|
||||
// ? If it's not undefined => New chat is created (Contact is not in list of chats)
|
||||
if (chat !== undefined) {
|
||||
const activeChat = this.activeChat
|
||||
|
||||
this.chatsContacts.push({
|
||||
...activeChat.contact,
|
||||
chat: {
|
||||
id: chat.id,
|
||||
lastMessage: [],
|
||||
unseenMsgs: 0,
|
||||
messages: [msg],
|
||||
},
|
||||
})
|
||||
if (this.activeChat) {
|
||||
this.activeChat.chat = {
|
||||
id: chat.id,
|
||||
messages: [msg],
|
||||
unseenMsgs: 0,
|
||||
userId: this.activeChat?.contact.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.activeChat?.chat?.messages.push(msg)
|
||||
}
|
||||
|
||||
// Set Last Message for active contact
|
||||
const contact = this.chatsContacts.find(c => {
|
||||
if (this.activeChat)
|
||||
return c.id === this.activeChat.contact.id
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
contact.chat.lastMessage = msg
|
||||
},
|
||||
},
|
||||
})
|
Reference in New Issue
Block a user