first commit
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
<script setup>
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
import { useChat } from './useChat'
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
|
||||
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="ri-close-line"
|
||||
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-4"
|
||||
>
|
||||
<VAvatar
|
||||
size="84"
|
||||
: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-body-1 mb-0">
|
||||
{{ 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-6">
|
||||
<p
|
||||
for="textarea-user-about"
|
||||
class="text-base text-disabled mb-1"
|
||||
>
|
||||
ABOUT
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{{ store.activeChat.contact.about }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Personal Information -->
|
||||
<div class="mb-6">
|
||||
<p class="text-base text-disabled mb-1">
|
||||
PERSONAL INFORMATION
|
||||
</p>
|
||||
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
icon="ri-mail-line"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
lucifer@email.com
|
||||
</h6>
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
icon="ri-phone-line"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
+1(123) 456 - 7890
|
||||
</h6>
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
size="22"
|
||||
icon="ri-time-line"
|
||||
color="high-emphasis"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Mon - Fri 10AM - 8PM
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div>
|
||||
<p class="text-base text-disabled mb-1">
|
||||
OPTIONS
|
||||
</p>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
icon="ri-bookmark-line"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Add Tag
|
||||
</h6>
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
icon="ri-star-line"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Important Contact
|
||||
</h6>
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
icon="ri-file-image-line"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Shared Media
|
||||
</h6>
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
icon="ri-delete-bin-line"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
class="me-2"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Delete Contact
|
||||
</h6>
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
icon="ri-forbid-line"
|
||||
class="me-2"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Block Contact
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
block
|
||||
color="error"
|
||||
append-icon="ri-delete-bin-7-line"
|
||||
class="mt-12"
|
||||
>
|
||||
Delete Contact
|
||||
</VBtn>
|
||||
</PerfectScrollbar>
|
||||
</template>
|
||||
</template>
|
110
resources/js/views/apps/chat/ChatContact.vue
Normal file
110
resources/js/views/apps/chat/ChatContact.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script setup>
|
||||
import { useChat } from '@/views/apps/chat/useChat'
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
|
||||
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 }"
|
||||
:data-x="store.chatsContacts.length"
|
||||
>
|
||||
<VBadge
|
||||
dot
|
||||
location="bottom right"
|
||||
offset-x="3"
|
||||
offset-y="3"
|
||||
:color="resolveAvatarBadgeVariant(props.user.status)"
|
||||
bordered
|
||||
:model-value="props.isChatContact"
|
||||
>
|
||||
<VAvatar
|
||||
size="40"
|
||||
: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 mb-0">
|
||||
{{ props.user.fullName }}
|
||||
</p>
|
||||
<span class="d-block text-body-2 text-truncate">{{ props.isChatContact && 'chat' in props.user ? props.user.chat.lastMessage.message : props.user.about }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.isChatContact && 'chat' in props.user"
|
||||
class="d-flex flex-column align-self-start"
|
||||
>
|
||||
<span class="d-block text-sm 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: var(--chat-content-spacing-x);
|
||||
|
||||
@include mixins.before-pseudo;
|
||||
@include vuetifyStates.states($active: false);
|
||||
|
||||
&.chat-contact-active {
|
||||
@include mixins.elevation(2);
|
||||
|
||||
background: rgb(var(--v-theme-primary));
|
||||
color: #fff;
|
||||
|
||||
--v-theme-on-background: #fff;
|
||||
|
||||
.v-avatar {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.v-badge--bordered .v-badge__badge::after {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
125
resources/js/views/apps/chat/ChatLeftSidebarContent.vue
Normal file
125
resources/js/views/apps/chat/ChatLeftSidebarContent.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<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'
|
||||
|
||||
const props = defineProps({
|
||||
search: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isDrawerOpen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'openChatOfContact',
|
||||
'showUserProfile',
|
||||
'close',
|
||||
'update:search',
|
||||
])
|
||||
|
||||
const { resolveAvatarBadgeVariant } = useChat()
|
||||
const search = useVModel(props, 'search', emit)
|
||||
const store = useChatStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 👉 Chat list header -->
|
||||
<div
|
||||
v-if="store.profileUser"
|
||||
class="chat-list-header gap-4"
|
||||
>
|
||||
<VBadge
|
||||
dot
|
||||
location="bottom right"
|
||||
offset-x="3"
|
||||
offset-y="3"
|
||||
:color="resolveAvatarBadgeVariant(store.profileUser.status)"
|
||||
bordered
|
||||
>
|
||||
<VAvatar
|
||||
class="cursor-pointer"
|
||||
@click="$emit('showUserProfile')"
|
||||
>
|
||||
<VImg
|
||||
:src="store.profileUser.avatar"
|
||||
alt="John Doe"
|
||||
/>
|
||||
</VAvatar>
|
||||
</VBadge>
|
||||
|
||||
<VTextField
|
||||
v-model="search"
|
||||
placeholder="Search..."
|
||||
prepend-inner-icon="ri-search-line"
|
||||
density="compact"
|
||||
class="chat-list-search"
|
||||
/>
|
||||
|
||||
<IconBtn
|
||||
v-if="$vuetify.display.smAndDown"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<VIcon
|
||||
icon="ri-close-line"
|
||||
class="text-medium-emphasis"
|
||||
/>
|
||||
</IconBtn>
|
||||
</div>
|
||||
<VDivider />
|
||||
|
||||
<PerfectScrollbar
|
||||
tag="ul"
|
||||
class="chat-contacts-list px-3 d-flex flex-column gap-1"
|
||||
:options="{ wheelPropagation: false }"
|
||||
>
|
||||
<li class="list-none">
|
||||
<span class="chat-contact-header d-block text-primary text-lg 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 class="list-none">
|
||||
<span class="chat-contact-header d-block text-primary text-lg 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: 12px;
|
||||
|
||||
padding-block-end: 0.75rem;
|
||||
|
||||
.chat-contact-header {
|
||||
margin-block: 1rem 4px;
|
||||
margin-inline: 1rem;
|
||||
}
|
||||
|
||||
.no-chat-items-text {
|
||||
margin-inline: var(--chat-content-spacing-x);
|
||||
}
|
||||
}
|
||||
</style>
|
149
resources/js/views/apps/chat/ChatLog.vue
Normal file
149
resources/js/views/apps/chat/ChatLog.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<script setup>
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
|
||||
const store = useChatStore()
|
||||
|
||||
const contact = computed(() => ({
|
||||
id: store.activeChat?.contact.id,
|
||||
avatar: store.activeChat?.contact.avatar,
|
||||
}))
|
||||
|
||||
const resolveFeedbackIcon = feedback => {
|
||||
if (feedback.isSeen)
|
||||
return {
|
||||
icon: 'ri-check-double-line',
|
||||
color: 'success',
|
||||
}
|
||||
else if (feedback.isDelivered)
|
||||
return {
|
||||
icon: 'ri-check-double-line',
|
||||
color: undefined,
|
||||
}
|
||||
else
|
||||
return {
|
||||
icon: 'ri-check-line',
|
||||
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-8': 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 text-body-1 py-2 px-4 elevation-2"
|
||||
:class="[
|
||||
msgGrp.senderId === contact.id ? 'bg-surface chat-left' : 'bg-primary text-white chat-right',
|
||||
msgGrp.messages.length - 1 !== msgIndex ? 'mb-2' : 'mb-1',
|
||||
]"
|
||||
>
|
||||
<p class="mb-0">
|
||||
{{ msgData.message }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'text-right': msgGrp.senderId !== contact.id }"
|
||||
class="d-flex align-center gap-2"
|
||||
>
|
||||
<VIcon
|
||||
v-if="msgGrp.senderId !== contact.id"
|
||||
size="16"
|
||||
:color="resolveFeedbackIcon(msgGrp.messages[msgGrp.messages.length - 1].feedback).color"
|
||||
>
|
||||
{{ resolveFeedbackIcon(msgGrp.messages[msgGrp.messages.length - 1].feedback).icon }}
|
||||
</VIcon>
|
||||
<p
|
||||
class="text-sm text-disabled mb-0"
|
||||
style="letter-spacing: 0.4px;"
|
||||
>
|
||||
{{ formatDate(msgGrp.messages[msgGrp.messages.length - 1].time, { hour: 'numeric', minute: 'numeric' }) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang=scss>
|
||||
.chat-log {
|
||||
.chat-content {
|
||||
border-end-end-radius: 6px;
|
||||
border-end-start-radius: 6px;
|
||||
|
||||
p {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
&.bg-surface{
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)) !important;
|
||||
}
|
||||
|
||||
&.chat-left {
|
||||
border-start-end-radius: 6px;
|
||||
}
|
||||
|
||||
&.chat-right {
|
||||
border-start-start-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
196
resources/js/views/apps/chat/ChatUserProfileSidebarContent.vue
Normal file
196
resources/js/views/apps/chat/ChatUserProfileSidebarContent.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<script setup>
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
import { useChat } from './useChat'
|
||||
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
||||
|
||||
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',
|
||||
},
|
||||
]
|
||||
|
||||
const isTwoStepVerified = ref(true)
|
||||
const isNotificationEnabled = ref(false)
|
||||
</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"
|
||||
icon="ri-close-line"
|
||||
/>
|
||||
</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-4"
|
||||
>
|
||||
<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-body-1 text-capitalize mb-0">
|
||||
{{ 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-6 text-medium-emphasis">
|
||||
<p
|
||||
for="textarea-user-about"
|
||||
class="text-base text-disabled mb-0"
|
||||
>
|
||||
ABOUT
|
||||
</p>
|
||||
<VTextarea
|
||||
id="textarea-user-about"
|
||||
v-model="store.profileUser.about"
|
||||
auto-grow
|
||||
class="mt-1"
|
||||
rows="3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="mb-6">
|
||||
<p class="text-base text-disabled mb-0">
|
||||
STATUS
|
||||
</p>
|
||||
<VRadioGroup
|
||||
v-model="store.profileUser.status"
|
||||
class="ms-2 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">
|
||||
<p class="text-base text-disabled mb-0">
|
||||
SETTINGS
|
||||
</p>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="ri-lock-password-line"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Two-step Verification
|
||||
</h6>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<VSwitch v-model="isTwoStepVerified" />
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="ri-notification-line"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Notification
|
||||
</h6>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<VSwitch v-model="isNotificationEnabled" />
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="ri-user-add-line"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Invite Friends
|
||||
</h6>
|
||||
</div>
|
||||
<div class="d-flex align-center pa-2">
|
||||
<VIcon
|
||||
class="me-2"
|
||||
icon="ri-delete-bin-7-line"
|
||||
size="22"
|
||||
color="high-emphasis"
|
||||
/>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Delete Account
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<VBtn
|
||||
block
|
||||
color="primary"
|
||||
class="mt-11"
|
||||
append-icon="ri-logout-box-r-line"
|
||||
>
|
||||
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,
|
||||
}
|
||||
}
|
80
resources/js/views/apps/chat/useChatStore.js
Normal file
80
resources/js/views/apps/chat/useChatStore.js
Normal file
@@ -0,0 +1,80 @@
|
||||
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, error } = await useApi(createUrl('/apps/chat/chats-and-contacts', {
|
||||
query: {
|
||||
q,
|
||||
},
|
||||
}))
|
||||
|
||||
if (error.value) {
|
||||
console.log(error.value)
|
||||
}
|
||||
else {
|
||||
const { chatsContacts, contacts, profileUser } = data.value
|
||||
|
||||
this.chatsContacts = chatsContacts
|
||||
this.contacts = contacts
|
||||
this.profileUser = profileUser
|
||||
}
|
||||
},
|
||||
async getChat(userId) {
|
||||
const res = await $api(`/apps/chat/chats/${userId}`)
|
||||
|
||||
this.activeChat = res
|
||||
},
|
||||
async sendMsg(message) {
|
||||
const senderId = this.profileUser?.id
|
||||
|
||||
const response = await $api(`apps/chat/chats/${this.activeChat?.contact.id}`, {
|
||||
method: 'POST',
|
||||
body: { message, senderId },
|
||||
})
|
||||
|
||||
const { msg, chat } = response
|
||||
|
||||
// ? 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