feat: emojis for teams

This commit is contained in:
Abhinav Raut
2025-01-10 04:08:00 +05:30
parent 8596807e56
commit 252c91cb3c
9 changed files with 68 additions and 49 deletions
+4 -2
View File
@@ -55,13 +55,14 @@ func handleCreateTeam(r *fastglue.Request) error {
app = r.Context.(*App)
name = string(r.RequestCtx.PostArgs().Peek("name"))
timezone = string(r.RequestCtx.PostArgs().Peek("timezone"))
emoji = string(r.RequestCtx.PostArgs().Peek("emoji"))
conversationAssignmentType = string(r.RequestCtx.PostArgs().Peek("conversation_assignment_type"))
)
businessHrsID, err := strconv.Atoi(string(r.RequestCtx.PostArgs().Peek("business_hours_id")))
if err != nil || businessHrsID == 0 {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Invalid `business_hours_id`.", nil, envelope.InputError)
}
if err := app.team.Create(name, timezone, conversationAssignmentType, businessHrsID); err != nil {
if err := app.team.Create(name, timezone, conversationAssignmentType, businessHrsID, emoji); err != nil {
return sendErrorEnvelope(r, err)
}
return r.SendEnvelope("Team created successfully.")
@@ -73,6 +74,7 @@ func handleUpdateTeam(r *fastglue.Request) error {
app = r.Context.(*App)
name = string(r.RequestCtx.PostArgs().Peek("name"))
timezone = string(r.RequestCtx.PostArgs().Peek("timezone"))
emoji = string(r.RequestCtx.PostArgs().Peek("emoji"))
conversationAssignmentType = string(r.RequestCtx.PostArgs().Peek("conversation_assignment_type"))
)
id, err := strconv.Atoi(r.RequestCtx.UserValue("id").(string))
@@ -86,7 +88,7 @@ func handleUpdateTeam(r *fastglue.Request) error {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Invalid `business_hours_id`.", nil, envelope.InputError)
}
if err = app.team.Update(id, name, timezone, conversationAssignmentType, businessHrsID); err != nil {
if err = app.team.Update(id, name, timezone, conversationAssignmentType, businessHrsID, emoji); err != nil {
return sendErrorEnvelope(r, err)
}
return r.SendEnvelope(true)
@@ -1,7 +1,22 @@
<template>
<form @submit="onSubmit" class="space-y-6">
<FormField name="emoji" v-slot="{ componentField }">
<FormItem ref="emojiPickerContainer" class="relative">
<FormLabel>Emoji</FormLabel>
<FormControl>
<Input type="text" v-bind="componentField" @click="toggleEmojiPicker" />
<div v-if="isEmojiPickerVisible" class="absolute z-10 mt-2">
<EmojiPicker :native="true" @select="onSelectEmoji" class="w-[300px]" />
</div>
</FormControl>
<FormDescription>Select an emoji.</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="name">
<FormItem v-auto-animate>
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input type="text" placeholder="Name" v-bind="componentField" />
@@ -29,7 +44,7 @@
</FormControl>
<FormDescription>
Round robin: Conversations are assigned to team members in a round-robin fashion. <br>
Manual: Conversations are manually assigned to team members.
Manual: Conversations are to be picked by team members.
</FormDescription>
<FormMessage />
</FormItem>
@@ -84,12 +99,12 @@
</template>
<script setup>
import { watch, computed, ref, onMounted } from 'vue'
import { ref, watch, computed, onMounted } from 'vue'
import { onClickOutside } from '@vueuse/core'
import { Button } from '@/components/ui/button'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { teamFormSchema } from './teamFormSchema.js'
import { vAutoAnimate } from '@formkit/auto-animate/vue'
import {
Select,
SelectContent,
@@ -109,41 +124,35 @@ import {
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
import { useEmitter } from '@/composables/useEmitter'
import { Input } from '@/components/ui/input'
import EmojiPicker from 'vue3-emoji-picker'
import 'vue3-emoji-picker/css'
import { handleHTTPError } from '@/utils/http'
import api from '@/api'
const emitter = useEmitter()
const timezones = computed(() => {
return Intl.supportedValuesOf('timeZone')
})
const timezones = computed(() => Intl.supportedValuesOf('timeZone'))
const assignmentTypes = ['Round robin', 'Manual']
const businessHours = ref([])
const props = defineProps({
initialValues: {
type: Object,
required: false
},
submitForm: {
type: Function,
required: true
},
submitLabel: {
type: String,
required: false,
default: () => 'Submit'
},
isLoading: {
type: Boolean,
required: false,
}
initialValues: { type: Object, required: false },
submitForm: { type: Function, required: true },
submitLabel: { type: String, default: 'Submit' },
isLoading: { type: Boolean }
})
const form = useForm({
validationSchema: toTypedSchema(teamFormSchema)
})
const isEmojiPickerVisible = ref(false)
const emojiPickerContainer = ref(null)
onMounted(() => {
fetchBusinessHours()
onClickOutside(emojiPickerContainer, () => {
isEmojiPickerVisible.value = false
})
})
const fetchBusinessHours = async () => {
@@ -152,33 +161,39 @@ const fetchBusinessHours = async () => {
businessHours.value = response.data.data
} catch (error) {
// If unauthorized (no permission), show a toast message.
if (error.response.status === 403) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
const toastPayload = error.response.status === 403
? {
title: 'Unauthorized',
variant: 'destructive',
description: 'You do not have permission to view business hours.'
})
} else {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
}
: {
title: 'Could not fetch business hours',
variant: 'destructive',
description: handleHTTPError(error).message
})
}
}
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, toastPayload)
}
}
const onSubmit = form.handleSubmit((values) => {
const onSubmit = form.handleSubmit(values => {
props.submitForm(values)
})
// Watch for changes in initialValues and update the form.
watch(
() => props.initialValues,
(newValues) => {
newValues => {
if (Object.keys(newValues).length === 0) return
form.setValues(newValues)
},
{ immediate: true }
)
</script>
function toggleEmojiPicker () {
isEmojiPickerVisible.value = !isEmojiPickerVisible.value
}
function onSelectEmoji (emoji) {
form.setFieldValue('emoji', emoji.i || emoji)
}
</script>
@@ -8,6 +8,7 @@ export const teamFormSchema = z.object({
.min(2, {
message: 'Team name must be at least 2 characters.'
}),
emoji: z.string({ required_error: 'Emoji is required.' }),
conversation_assignment_type: z.string({ required_error: 'Conversation assignment type is required.' }),
business_hours_id : z.number({ required_error: 'Business hours is required.' }),
timezone: z.string().optional(),
@@ -13,8 +13,9 @@
<CommandEmpty>No team found.</CommandEmpty>
<CommandList>
<CommandGroup>
<CommandItem v-for="team in filteredTeams" :key="team.id" :value="team.id" @select="handleSelectTeam(team.id)">
{{ team.name }}
<CommandItem v-for="team in filteredTeams" :key="team.id" :value="team.id"
@select="handleSelectTeam(team.id)">
{{ team.emoji }} {{ team.name }}
<CheckIcon :class="cn(
'ml-auto h-4 w-4',
conversation.assigned_team_id === team.id ? 'opacity-100' : 'opacity-0'
+1 -1
View File
@@ -590,7 +590,7 @@ const hasConversationOpen = computed(() => {
<SidebarMenuSubItem v-for="team in userTeams" :key="team.id">
<SidebarMenuButton :isActive="isActiveParent(`/teams/${team.id}`)" asChild>
<router-link :to="{ name: 'team-inbox', params: { teamID: team.id } }">
<span>{{ team.name }}</span>
{{ team.emoji }}<span>{{ team.name }}</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuSubItem>
+2 -2
View File
@@ -15,8 +15,8 @@ type Team struct {
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Emoji null.String `db:"emoji" json:"emoji"`
Name string `db:"name" json:"name"`
ConversationAssignmentType string `db:"conversation_assignment_type" json:"conversation_assignment_type"`
Timezone string `db:"timezone" json:"timezone"`
ConversationAssignmentType string `db:"conversation_assignment_type" json:"conversation_assignment_type,omitempty"`
Timezone string `db:"timezone" json:"timezone,omitempty"`
BusinessHoursID int `db:"business_hours_id" json:"business_hours_id,omitempty"`
}
+2 -2
View File
@@ -18,10 +18,10 @@ JOIN teams t ON t.id = tm.team_id
WHERE t.id = $1;
-- name: insert-team
INSERT INTO teams (name, timezone, conversation_assignment_type, business_hours_id) VALUES ($1, $2, $3, $4) RETURNING id;
INSERT INTO teams (name, timezone, conversation_assignment_type, business_hours_id, emoji) VALUES ($1, $2, $3, $4, $5) RETURNING id;
-- name: update-team
UPDATE teams set name = $2, timezone = $3, conversation_assignment_type = $4, business_hours_id = $5 where id = $1;
UPDATE teams set name = $2, timezone = $3, conversation_assignment_type = $4, business_hours_id = $5, emoji = $6 where id = $1;
-- name: upsert-user-teams
WITH delete_old_teams AS (
+4 -4
View File
@@ -102,8 +102,8 @@ func (u *Manager) Get(id int) (models.Team, error) {
}
// Create creates a new team.
func (u *Manager) Create(name, timezone, conversationAssignmentType string, businessHrsID int) error {
if _, err := u.q.InsertTeam.Exec(name, timezone, conversationAssignmentType, businessHrsID); err != nil {
func (u *Manager) Create(name, timezone, conversationAssignmentType string, businessHrsID int, emoji string) error {
if _, err := u.q.InsertTeam.Exec(name, timezone, conversationAssignmentType, businessHrsID, emoji); err != nil {
u.lo.Error("error inserting team", "error", err)
return envelope.NewError(envelope.GeneralError, "Error creating team", nil)
}
@@ -111,8 +111,8 @@ func (u *Manager) Create(name, timezone, conversationAssignmentType string, busi
}
// Update updates an existing team.
func (u *Manager) Update(id int, name, timezone, conversationAssignmentType string, businessHrsID int) error {
if _, err := u.q.UpdateTeam.Exec(id, name, timezone, conversationAssignmentType, businessHrsID); err != nil {
func (u *Manager) Update(id int, name, timezone, conversationAssignmentType string, businessHrsID int, emoji string) error {
if _, err := u.q.UpdateTeam.Exec(id, name, timezone, conversationAssignmentType, businessHrsID, emoji); err != nil {
u.lo.Error("error updating team", "error", err)
return envelope.NewError(envelope.GeneralError, "Error updating team", nil)
}
+1 -1
View File
@@ -35,7 +35,7 @@ SELECT
u.last_name,
u.roles,
COALESCE(
(SELECT json_agg(json_build_object('id', t.id, 'name', t.name))
(SELECT json_agg(json_build_object('id', t.id, 'name', t.name, 'emoji', t.emoji))
FROM team_members tm
JOIN teams t ON tm.team_id = t.id
WHERE tm.user_id = u.id),