feat: emoji picker

- fix: Fatal issued during inbox reload instead return errors.
- rename functions in inbox pkg to reflect purpose.
- rename page titles on router.
- remove iconify.
This commit is contained in:
Abhinav Raut
2025-01-10 03:48:03 +05:30
parent f30a7f07d0
commit 8596807e56
12 changed files with 77 additions and 72 deletions
+1 -1
View File
@@ -567,7 +567,7 @@ func calculateSLA(app *App, conversation *cmodels.Conversation) error {
// sendCSATSurvey sends a CSAT survey if enabled on the inbox.
func sendCSATSurvey(app *App, conversation cmodels.Conversation, user umodels.User) error {
inbox, err := app.inbox.GetByID(conversation.InboxID)
inbox, err := app.inbox.GetDBRecord(conversation.InboxID)
if err != nil {
return err
}
+2 -2
View File
@@ -23,7 +23,7 @@ func handleGetInbox(r *fastglue.Request) error {
app = r.Context.(*App)
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
)
inbox, err := app.inbox.GetByID(id)
inbox, err := app.inbox.GetDBRecord(id)
if err != nil {
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error fetching inbox", nil, envelope.GeneralError)
}
@@ -74,7 +74,7 @@ func handleUpdateInbox(r *fastglue.Request) error {
}
if err := reloadInboxes(app); err != nil {
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error reloading inboxes", nil, envelope.GeneralError)
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error reloading inboxes, Please restart the app if the issue persists", nil, envelope.GeneralError)
}
return r.SendEnvelope(inbox)
+4 -5
View File
@@ -471,11 +471,11 @@ func initEmailInbox(inboxRecord imodels.Inbox, store inbox.MessageStore) (inbox.
// Load JSON data into Koanf.
if err := ko.Load(rawbytes.Provider([]byte(inboxRecord.Config)), kjson.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
return nil, fmt.Errorf("loading config: %w", err)
}
if err := ko.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "json"}); err != nil {
log.Fatalf("error unmarshalling `%s` %s config: %v", inboxRecord.Channel, inboxRecord.Name, err)
return nil, fmt.Errorf("unmarshalling `%s` %s config: %w", inboxRecord.Channel, inboxRecord.Name, err)
}
if len(config.SMTP) == 0 {
@@ -499,11 +499,10 @@ func initEmailInbox(inboxRecord imodels.Inbox, store inbox.MessageStore) (inbox.
})
if err != nil {
log.Fatalf("ERROR: initalizing `%s` inbox: `%s` error : %v", inboxRecord.Channel, inboxRecord.Name, err)
return nil, err
return nil, fmt.Errorf("initializing `%s` inbox: `%s` error : %w", inboxRecord.Channel, inboxRecord.Name, err)
}
log.Printf("`%s` inbox successfully initalized. %d smtp servers. %d imap clients.", inboxRecord.Name, len(config.SMTP), len(config.IMAP))
log.Printf("`%s` inbox successfully initialized. %d SMTP servers. %d IMAP clients.", inboxRecord.Name, len(config.SMTP), len(config.IMAP))
return inbox, nil
}
+1 -1
View File
@@ -61,10 +61,10 @@
"vue-picture-cropper": "^0.7.0",
"vue-router": "^4.2.5",
"vue-sonner": "^1.3.0",
"vue3-emoji-picker": "^1.1.8",
"zod": "^3.23.8"
},
"devDependencies": {
"@iconify/vue": "^4.1.2",
"@rushstack/eslint-patch": "^1.3.3",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^8.0.0",
@@ -2,9 +2,9 @@ import * as z from 'zod'
import { isGoDuration } from '@/utils/strings'
export const formSchema = z.object({
name2: z.string().describe('Name').default(''),
name: z.string().describe('Name').default(''),
from: z.string().describe('From address').default(''),
csat_enabled: z.boolean().describe('Enable CSAT').default(false),
csat_enabled: z.boolean().describe('Enable CSAT'),
imap: z
.object({
host: z.string().describe('Host').default('imap.gmail.com'),
@@ -64,10 +64,11 @@
<!-- Attachments preview -->
<AttachmentsPreview :attachments="attachments" :onDelete="handleOnFileDelete"></AttachmentsPreview>
<!-- Bottom menu bar -->
<ReplyBoxBottomMenuBar :handleFileUpload="handleFileUpload" :handleInlineImageUpload="handleInlineImageUpload"
:isBold="isBold" :isItalic="isItalic" @toggleBold="toggleBold" @toggleItalic="toggleItalic" :hasText="hasText"
:handleSend="handleSend">
:handleSend="handleSend" @emojiSelect="handleEmojiSelect">
</ReplyBoxBottomMenuBar>
</div>
@@ -92,9 +93,7 @@ import AttachmentsPreview from '@/components/attachment/AttachmentsPreview.vue'
import ReplyBoxBottomMenuBar from '@/components/conversation/ReplyBoxMenuBar.vue'
const conversationStore = useConversationStore()
const emitter = useEmitter()
let editorInstance = ref(null)
const isEditorFullscreen = ref(false)
const cursorPosition = ref(0)
@@ -330,4 +329,8 @@ const selectCannedResponse = (content) => {
filteredCannedResponses.value = []
selectedResponseIndex.value = -1
}
const handleEmojiSelect = (emoji) => {
editorInstance.value.chain().focus().insertContent(emoji).run()
}
</script>
@@ -1,22 +1,18 @@
<template>
<div class="flex justify-between items-center border-t h-14 px-2">
<div class="flex justify-between items-center border-t h-14 px-2 relative">
<EmojiPicker v-if="isEmojiPickerVisible" ref="emojiPickerRef" :native="true" @select="onSelectEmoji"
class="absolute bottom-14 left-14" />
<div class="flex justify-items-start gap-2">
<!-- File inputs -->
<input type="file" class="hidden" ref="attachmentInput" multiple @change="handleFileUpload" />
<input type="file" class="hidden" ref="inlineImageInput" accept="image/*" @change="handleInlineImageUpload" />
<!-- Editor buttons -->
<!-- <Toggle class="px-2 py-2 border-0" variant="outline" @click="toggleBold" :pressed="isBold">
<Bold class="h-4 w-4" />
</Toggle>
<Toggle class="px-2 py-2 border-0" variant="outline" @click="toggleItalic" :pressed="isItalic">
<Italic class="h-4 w-4" />
</Toggle> -->
<Toggle class="px-2 py-2 border-0" variant="outline" @click="triggerFileUpload" :pressed="false">
<Paperclip class="h-4 w-4" />
</Toggle>
<!-- <Toggle class="px-2 py-2 border-0" variant="outline" @click="triggerInlineImage" :pressed="false">
<Image class="h-4 w-4" />
</Toggle> -->
<Toggle class="px-2 py-2 border-0" variant="outline" @click="toggleEmojiPicker" :pressed="isEmojiPickerVisible">
<Smile class="h-4 w-4" />
</Toggle>
</div>
<Button class="h-8 w-6 px-8" @click="handleSend" :disabled="!hasText">Send</Button>
</div>
@@ -24,13 +20,19 @@
<script setup>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
import { Button } from '@/components/ui/button'
import { Toggle } from '@/components/ui/toggle'
import { Paperclip } from 'lucide-vue-next'
import { Paperclip, Smile } from 'lucide-vue-next'
import EmojiPicker from 'vue3-emoji-picker'
import 'vue3-emoji-picker/css'
const attachmentInput = ref(null)
const inlineImageInput = ref(null)
// const emit = defineEmits(['toggleBold', 'toggleItalic'])
const isEmojiPickerVisible = ref(false)
const emojiPickerRef = ref(null)
const emit = defineEmits(['toggleBold', 'toggleItalic', 'emojiSelect'])
defineProps({
isBold: Boolean,
isItalic: Boolean,
@@ -40,19 +42,19 @@ defineProps({
handleInlineImageUpload: Function
})
// const toggleBold = () => {
// emit('toggleBold')
// }
// const toggleItalic = () => {
// emit('toggleItalic')
// }
onClickOutside(emojiPickerRef, () => {
isEmojiPickerVisible.value = false
})
const triggerFileUpload = () => {
attachmentInput.value.click()
}
// const triggerInlineImage = () => {
// inlineImageInput.value.click()
// }
const toggleEmojiPicker = () => {
isEmojiPickerVisible.value = !isEmojiPickerVisible.value
}
function onSelectEmoji (emoji) {
emit('emojiSelect', emoji.i)
}
</script>
+1 -1
View File
@@ -338,7 +338,7 @@ const hasConversationOpen = computed(() => {
<template>
<div class="flex flex-row justify-between h-full">
<div class="flex-1">
<SidebarProvider :open="open" @update:open="($event) => emit('update:open', $event)">
<SidebarProvider :open="open" @update:open="($event) => emit('update:open', $event)" style="--sidebar-width: 17rem;">
<!-- Flex Container that holds all the sidebar components -->
<Sidebar collapsible="icon" class="overflow-hidden [&>[data-sidebar=sidebar]]:flex-row !border-r-0">
+2 -2
View File
@@ -230,7 +230,7 @@ const routes = [
{
path: 'teams',
component: () => import('@/components/admin/team/teams/Teams.vue'),
meta: { title: 'Teams Management' },
meta: { title: 'Teams' },
children: [
{
@@ -288,7 +288,7 @@ const routes = [
{
path: 'general',
component: () => import('@/components/admin/general/General.vue'),
meta: { title: 'General Settings' }
meta: { title: 'General' }
},
{
path: 'templates',
+26 -24
View File
@@ -747,18 +747,6 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@iconify/types@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57"
integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==
"@iconify/vue@^4.1.2":
version "4.1.2"
resolved "https://registry.yarnpkg.com/@iconify/vue/-/vue-4.1.2.tgz#b76135785ca366b29bf0736eee9cfefc1c2ef79e"
integrity sha512-CQnYqLiQD5LOAaXhBrmj1mdL2/NCJvwcC4jtW2Z8ukhThiFkLDkutarTOV2trfc9EXqUqRs0KqXOL9pZ/IyysA==
dependencies:
"@iconify/types" "^2.0.0"
"@internationalized/date@^3.5.4":
version "3.5.4"
resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.4.tgz#49ba11634fd4350b7a9308e297032267b4063c44"
@@ -1105,7 +1093,7 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@popperjs/core@^2.9.0":
"@popperjs/core@^2.11.0", "@popperjs/core@^2.9.0":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
@@ -5181,6 +5169,11 @@ iconv-lite@0.6, iconv-lite@^0.6.2:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
idb@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b"
integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==
ieee754@^1.1.12, ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@@ -9628,6 +9621,26 @@ vue-sonner@^1.3.0:
resolved "https://registry.yarnpkg.com/vue-sonner/-/vue-sonner-1.3.0.tgz#da8ab9be995dfea781d57a6ac52d170d02473d86"
integrity sha512-jAodBy4Mri8rQjVZGQAPs4ZYymc1ywPiwfa81qU0fFl+Suk7U8NaOxIDdI1oBGLeQJqRZi/oxNIuhCLqsBmOwg==
vue3-emoji-picker@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/vue3-emoji-picker/-/vue3-emoji-picker-1.1.8.tgz#70f1b56e9adf4b282d122b7c3a6ce8dc0ca3f72d"
integrity sha512-k9tVHeQEBVLzVCLYAkFaI1nib3FJFQwdPhWD5khJkhks3ktg3g12z5wPGOSDpIuSLNtelRGvq1qdmZuJu5khfA==
dependencies:
"@popperjs/core" "^2.11.0"
idb "^7.1.0"
vue "^3.2.23"
vue@^3.2.23, vue@^3.5.13:
version "3.5.13"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
dependencies:
"@vue/compiler-dom" "3.5.13"
"@vue/compiler-sfc" "3.5.13"
"@vue/runtime-dom" "3.5.13"
"@vue/server-renderer" "3.5.13"
"@vue/shared" "3.5.13"
vue@^3.4.37:
version "3.4.37"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.37.tgz#64ce0eeb8de16a29fb74e504777ee8c0c1cf229e"
@@ -9639,17 +9652,6 @@ vue@^3.4.37:
"@vue/server-renderer" "3.4.37"
"@vue/shared" "3.4.37"
vue@^3.5.13:
version "3.5.13"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
dependencies:
"@vue/compiler-dom" "3.5.13"
"@vue/compiler-sfc" "3.5.13"
"@vue/runtime-dom" "3.5.13"
"@vue/server-renderer" "3.5.13"
"@vue/shared" "3.5.13"
w3c-keyname@^2.2.0:
version "2.2.8"
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
+7 -8
View File
@@ -82,7 +82,7 @@ type Manager struct {
// Prepared queries.
type queries struct {
GetByID *sqlx.Stmt `query:"get-by-id"`
GetInbox *sqlx.Stmt `query:"get-inbox"`
GetActive *sqlx.Stmt `query:"get-active-inboxes"`
GetAll *sqlx.Stmt `query:"get-all-inboxes"`
Update *sqlx.Stmt `query:"update"`
@@ -119,11 +119,10 @@ func (m *Manager) Register(i Inbox) {
m.inboxes[i.Identifier()] = i
}
// Get returns the inbox with the given ID.
// Get retrieves the initialized inbox instance with the specified ID from memory.
func (m *Manager) Get(id int) (Inbox, error) {
m.mu.RLock()
defer m.mu.RUnlock()
i, ok := m.inboxes[id]
if !ok {
return nil, ErrInboxNotFound
@@ -131,10 +130,10 @@ func (m *Manager) Get(id int) (Inbox, error) {
return i, nil
}
// GetByID returns an inbox from the DB by the given ID.
func (m *Manager) GetByID(id int) (imodels.Inbox, error) {
// GetDBRecord returns the inbox record from the DB.
func (m *Manager) GetDBRecord(id int) (imodels.Inbox, error) {
var inbox imodels.Inbox
if err := m.queries.GetByID.Get(&inbox, id); err != nil {
if err := m.queries.GetInbox.Get(&inbox, id); err != nil {
m.lo.Error("error fetching inbox", "error", err)
return inbox, envelope.NewError(envelope.GeneralError, "Error fetching inbox", nil)
}
@@ -195,7 +194,7 @@ func (m *Manager) InitInboxes(initFn initFn) error {
return nil
}
// Reload reloads inboxes with the provided initialization function.
// Reload hot reloads the inboxes with the given init function.
func (m *Manager) Reload(ctx context.Context, initFn initFn) error {
m.mu.Lock()
defer m.mu.Unlock()
@@ -248,7 +247,7 @@ func (m *Manager) Reload(ctx context.Context, initFn initFn) error {
// Update updates an inbox in the DB.
func (m *Manager) Update(id int, inbox imodels.Inbox) error {
current, err := m.GetByID(id)
current, err := m.GetDBRecord(id)
if err != nil {
return err
}
+1 -1
View File
@@ -9,7 +9,7 @@ INSERT INTO inboxes
(channel, config, "name", "from")
VALUES($1, $2, $3, $4);
-- name: get-by-id
-- name: get-inbox
SELECT * from inboxes where id = $1 and deleted_at is NULL;
-- name: update