mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-10-23 05:11:57 +00:00
Fix: Contact form displays countries with the same calling code incorrectly.
For example, when a user selects the USA, the form also shows Canada, as both share the +1 calling code. Rename column from `phone_number_calling_code` to `phone_number_country_code`. Feat: Show the calling code alongside the country flag in the contact form for the selected country. Previously, only the flag was displayed.
This commit is contained in:
@@ -103,9 +103,9 @@ func handleUpdateContact(r *fastglue.Request) error {
|
|||||||
if v, ok := form.Value["phone_number"]; ok && len(v) > 0 {
|
if v, ok := form.Value["phone_number"]; ok && len(v) > 0 {
|
||||||
phoneNumber = string(v[0])
|
phoneNumber = string(v[0])
|
||||||
}
|
}
|
||||||
phoneNumberCallingCode := ""
|
phoneNumberCountryCode := ""
|
||||||
if v, ok := form.Value["phone_number_calling_code"]; ok && len(v) > 0 {
|
if v, ok := form.Value["phone_number_country_code"]; ok && len(v) > 0 {
|
||||||
phoneNumberCallingCode = string(v[0])
|
phoneNumberCountryCode = string(v[0])
|
||||||
}
|
}
|
||||||
avatarURL := ""
|
avatarURL := ""
|
||||||
if v, ok := form.Value["avatar_url"]; ok && len(v) > 0 {
|
if v, ok := form.Value["avatar_url"]; ok && len(v) > 0 {
|
||||||
@@ -116,8 +116,8 @@ func handleUpdateContact(r *fastglue.Request) error {
|
|||||||
if avatarURL == "null" {
|
if avatarURL == "null" {
|
||||||
avatarURL = ""
|
avatarURL = ""
|
||||||
}
|
}
|
||||||
if phoneNumberCallingCode == "null" {
|
if phoneNumberCountryCode == "null" {
|
||||||
phoneNumberCallingCode = ""
|
phoneNumberCountryCode = ""
|
||||||
}
|
}
|
||||||
if phoneNumber == "null" {
|
if phoneNumber == "null" {
|
||||||
phoneNumber = ""
|
phoneNumber = ""
|
||||||
@@ -146,7 +146,7 @@ func handleUpdateContact(r *fastglue.Request) error {
|
|||||||
Email: null.StringFrom(email),
|
Email: null.StringFrom(email),
|
||||||
AvatarURL: null.NewString(avatarURL, avatarURL != ""),
|
AvatarURL: null.NewString(avatarURL, avatarURL != ""),
|
||||||
PhoneNumber: null.NewString(phoneNumber, phoneNumber != ""),
|
PhoneNumber: null.NewString(phoneNumber, phoneNumber != ""),
|
||||||
PhoneNumberCallingCode: null.NewString(phoneNumberCallingCode, phoneNumberCallingCode != ""),
|
PhoneNumberCountryCode: null.NewString(phoneNumberCountryCode, phoneNumberCountryCode != ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.user.UpdateContact(id, contactToUpdate); err != nil {
|
if err := app.user.UpdateContact(id, contactToUpdate); err != nil {
|
||||||
|
@@ -35,6 +35,7 @@ var migList = []migFunc{
|
|||||||
{"v0.5.0", migrations.V0_5_0},
|
{"v0.5.0", migrations.V0_5_0},
|
||||||
{"v0.6.0", migrations.V0_6_0},
|
{"v0.6.0", migrations.V0_6_0},
|
||||||
{"v0.7.0", migrations.V0_7_0},
|
{"v0.7.0", migrations.V0_7_0},
|
||||||
|
{"v0.7.4", migrations.V0_7_4},
|
||||||
}
|
}
|
||||||
|
|
||||||
// upgrade upgrades the database to the current version by running SQL migration files
|
// upgrade upgrades the database to the current version by running SQL migration files
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
:class="['w-full justify-between', buttonClass]"
|
:class="['w-full justify-between', buttonClass]"
|
||||||
>
|
>
|
||||||
<slot name="selected" :selected="selectedItem">{{ selectedLabel }}</slot>
|
<slot name="selected" :selected="selectedItem">{{ selectedLabel }}</slot>
|
||||||
<CaretSortIcon class="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
<CaretSortIcon class="h-4 w-4 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="p-0">
|
||||||
|
@@ -41,8 +41,8 @@
|
|||||||
|
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1">
|
||||||
<div class="flex items-end">
|
<div class="flex items-end">
|
||||||
<FormField v-slot="{ componentField }" name="phone_number_calling_code">
|
<FormField v-slot="{ componentField }" name="phone_number_country_code">
|
||||||
<FormItem class="w-20">
|
<FormItem class="w-max-content">
|
||||||
<FormLabel class="flex items-center whitespace-nowrap">
|
<FormLabel class="flex items-center whitespace-nowrap">
|
||||||
{{ t('globals.terms.phoneNumber') }}
|
{{ t('globals.terms.phoneNumber') }}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
@@ -58,13 +58,18 @@
|
|||||||
<div class="w-7 h-7 flex items-center justify-center">
|
<div class="w-7 h-7 flex items-center justify-center">
|
||||||
<span v-if="item.emoji">{{ item.emoji }}</span>
|
<span v-if="item.emoji">{{ item.emoji }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm">{{ item.label }} ({{ item.value }})</span>
|
<span class="text-sm">{{ item.label }} ({{ item.calling_code }})</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #selected="{ selected }">
|
<template #selected="{ selected }">
|
||||||
<div class="flex items-center mb-1">
|
<div class="flex items-center gap-1">
|
||||||
<span v-if="selected" class="text-xl leading-none">{{ selected.emoji }}</span>
|
<span v-if="selected" class="text-lg">{{ selected.emoji }}</span>
|
||||||
|
<span
|
||||||
|
v-if="selected && selected.calling_code"
|
||||||
|
class="text-xs text-muted-foreground"
|
||||||
|
>({{ selected.calling_code }})</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
@@ -116,7 +121,8 @@ const userStore = useUserStore()
|
|||||||
|
|
||||||
const allCountries = countries.map((country) => ({
|
const allCountries = countries.map((country) => ({
|
||||||
label: country.name,
|
label: country.name,
|
||||||
value: country.calling_code,
|
value: country.iso_2,
|
||||||
emoji: country.emoji
|
emoji: country.emoji,
|
||||||
|
calling_code: country.calling_code
|
||||||
}))
|
}))
|
||||||
</script>
|
</script>
|
||||||
|
@@ -29,7 +29,7 @@ export const createFormSchema = (t) => z.object({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.nullable(),
|
.nullable(),
|
||||||
phone_number_calling_code: z.string().optional().nullable(),
|
phone_number_country_code: z.string().optional().nullable(),
|
||||||
avatar_url: z.string().optional().nullable(),
|
avatar_url: z.string().optional().nullable(),
|
||||||
email: z
|
email: z
|
||||||
.string({
|
.string({
|
||||||
|
@@ -58,6 +58,7 @@ import { ViewVerticalIcon } from '@radix-icons/vue'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
import { Mail, Phone, ExternalLink } from 'lucide-vue-next'
|
import { Mail, Phone, ExternalLink } from 'lucide-vue-next'
|
||||||
|
import countries from '@/constants/countries.js'
|
||||||
import { useEmitter } from '@/composables/useEmitter'
|
import { useEmitter } from '@/composables/useEmitter'
|
||||||
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
|
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
|
||||||
import { useConversationStore } from '@/stores/conversation'
|
import { useConversationStore } from '@/stores/conversation'
|
||||||
@@ -72,8 +73,13 @@ const { t } = useI18n()
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const phoneNumber = computed(() => {
|
const phoneNumber = computed(() => {
|
||||||
const callingCode = conversation.value?.contact?.phone_number_calling_code || ''
|
const countryCodeValue = conversation.value?.contact?.phone_number_country_code || ''
|
||||||
const number = conversation.value?.contact?.phone_number || t('conversation.sidebar.notAvailable')
|
const number = conversation.value?.contact?.phone_number || t('conversation.sidebar.notAvailable')
|
||||||
return callingCode ? `${callingCode} ${number}` : number
|
if (!countryCodeValue) return number
|
||||||
|
|
||||||
|
// Lookup calling code
|
||||||
|
const country = countries.find((c) => c.iso_2 === countryCodeValue)
|
||||||
|
const callingCode = country ? country.calling_code : countryCodeValue
|
||||||
|
return `${callingCode} ${number}`
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@@ -189,7 +189,7 @@ async function onUpload(file) {
|
|||||||
formData.append('last_name', form.values.last_name)
|
formData.append('last_name', form.values.last_name)
|
||||||
formData.append('email', form.values.email)
|
formData.append('email', form.values.email)
|
||||||
formData.append('phone_number', form.values.phone_number)
|
formData.append('phone_number', form.values.phone_number)
|
||||||
formData.append('phone_number_calling_code', form.values.phone_number_calling_code)
|
formData.append('phone_number_country_code', form.values.phone_number_country_code)
|
||||||
formData.append('enabled', form.values.enabled)
|
formData.append('enabled', form.values.enabled)
|
||||||
const { data } = await api.updateContact(contact.value.id, formData)
|
const { data } = await api.updateContact(contact.value.id, formData)
|
||||||
contact.value.avatar_url = data.avatar_url
|
contact.value.avatar_url = data.avatar_url
|
||||||
|
@@ -145,7 +145,7 @@ type ConversationContact struct {
|
|||||||
AvailabilityStatus string `db:"availability_status" json:"availability_status"`
|
AvailabilityStatus string `db:"availability_status" json:"availability_status"`
|
||||||
AvatarURL null.String `db:"avatar_url" json:"avatar_url"`
|
AvatarURL null.String `db:"avatar_url" json:"avatar_url"`
|
||||||
PhoneNumber null.String `db:"phone_number" json:"phone_number"`
|
PhoneNumber null.String `db:"phone_number" json:"phone_number"`
|
||||||
PhoneNumberCallingCode null.String `db:"phone_number_calling_code" json:"phone_number_calling_code"`
|
PhoneNumberCountryCode null.String `db:"phone_number_country_code" json:"phone_number_country_code"`
|
||||||
CustomAttributes json.RawMessage `db:"custom_attributes" json:"custom_attributes"`
|
CustomAttributes json.RawMessage `db:"custom_attributes" json:"custom_attributes"`
|
||||||
Enabled bool `db:"enabled" json:"enabled"`
|
Enabled bool `db:"enabled" json:"enabled"`
|
||||||
LastActiveAt null.Time `db:"last_active_at" json:"last_active_at"`
|
LastActiveAt null.Time `db:"last_active_at" json:"last_active_at"`
|
||||||
|
@@ -140,7 +140,7 @@ SELECT
|
|||||||
ct.availability_status as "contact.availability_status",
|
ct.availability_status as "contact.availability_status",
|
||||||
ct.avatar_url as "contact.avatar_url",
|
ct.avatar_url as "contact.avatar_url",
|
||||||
ct.phone_number as "contact.phone_number",
|
ct.phone_number as "contact.phone_number",
|
||||||
ct.phone_number_calling_code as "contact.phone_number_calling_code",
|
ct.phone_number_country_code as "contact.phone_number_country_code",
|
||||||
ct.custom_attributes as "contact.custom_attributes",
|
ct.custom_attributes as "contact.custom_attributes",
|
||||||
ct.enabled as "contact.enabled",
|
ct.enabled as "contact.enabled",
|
||||||
ct.last_active_at as "contact.last_active_at",
|
ct.last_active_at as "contact.last_active_at",
|
||||||
|
53
internal/migrations/v0.7.4.go
Normal file
53
internal/migrations/v0.7.4.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/knadh/koanf/v2"
|
||||||
|
"github.com/knadh/stuffbin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V0_7_4 updates the database schema to v0.7.4.
|
||||||
|
func V0_7_4(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
|
||||||
|
// Rename phone_number_calling_code to phone_number_country_code
|
||||||
|
// This column will now store country codes (US, CA, GB) instead of calling codes (+1, +44)
|
||||||
|
_, err := db.Exec(`
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_name = 'users' AND column_name = 'phone_number_country_code'
|
||||||
|
) AND EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.columns
|
||||||
|
WHERE table_name = 'users' AND column_name = 'phone_number_calling_code'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE users
|
||||||
|
RENAME COLUMN phone_number_calling_code TO phone_number_country_code;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename the constraint to match the new column name
|
||||||
|
_, err = db.Exec(`
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.constraint_column_usage
|
||||||
|
WHERE constraint_name = 'constraint_users_on_phone_number_country_code'
|
||||||
|
) AND EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.constraint_column_usage
|
||||||
|
WHERE constraint_name = 'constraint_users_on_phone_number_calling_code'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE users
|
||||||
|
RENAME CONSTRAINT constraint_users_on_phone_number_calling_code TO constraint_users_on_phone_number_country_code;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -29,7 +29,7 @@ func (u *Manager) CreateContact(user *models.User) error {
|
|||||||
|
|
||||||
// UpdateContact updates a contact in the database.
|
// UpdateContact updates a contact in the database.
|
||||||
func (u *Manager) UpdateContact(id int, user models.User) error {
|
func (u *Manager) UpdateContact(id int, user models.User) error {
|
||||||
if _, err := u.q.UpdateContact.Exec(id, user.FirstName, user.LastName, user.Email, user.AvatarURL, user.PhoneNumber, user.PhoneNumberCallingCode); err != nil {
|
if _, err := u.q.UpdateContact.Exec(id, user.FirstName, user.LastName, user.Email, user.AvatarURL, user.PhoneNumber, user.PhoneNumberCountryCode); err != nil {
|
||||||
u.lo.Error("error updating user", "error", err)
|
u.lo.Error("error updating user", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.contact}"), nil)
|
return envelope.NewError(envelope.GeneralError, u.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.contact}"), nil)
|
||||||
}
|
}
|
||||||
|
@@ -53,7 +53,7 @@ type User struct {
|
|||||||
Email null.String `db:"email" json:"email"`
|
Email null.String `db:"email" json:"email"`
|
||||||
Type string `db:"type" json:"type"`
|
Type string `db:"type" json:"type"`
|
||||||
AvailabilityStatus string `db:"availability_status" json:"availability_status"`
|
AvailabilityStatus string `db:"availability_status" json:"availability_status"`
|
||||||
PhoneNumberCallingCode null.String `db:"phone_number_calling_code" json:"phone_number_calling_code"`
|
PhoneNumberCountryCode null.String `db:"phone_number_country_code" json:"phone_number_country_code"`
|
||||||
PhoneNumber null.String `db:"phone_number" json:"phone_number"`
|
PhoneNumber null.String `db:"phone_number" json:"phone_number"`
|
||||||
AvatarURL null.String `db:"avatar_url" json:"avatar_url"`
|
AvatarURL null.String `db:"avatar_url" json:"avatar_url"`
|
||||||
Enabled bool `db:"enabled" json:"enabled"`
|
Enabled bool `db:"enabled" json:"enabled"`
|
||||||
|
@@ -39,7 +39,7 @@ SELECT
|
|||||||
u.availability_status,
|
u.availability_status,
|
||||||
u.last_active_at,
|
u.last_active_at,
|
||||||
u.last_login_at,
|
u.last_login_at,
|
||||||
u.phone_number_calling_code,
|
u.phone_number_country_code,
|
||||||
u.phone_number,
|
u.phone_number,
|
||||||
u.api_key,
|
u.api_key,
|
||||||
u.api_key_last_used_at,
|
u.api_key_last_used_at,
|
||||||
@@ -174,7 +174,7 @@ SET first_name = COALESCE($2, first_name),
|
|||||||
email = COALESCE($4, email),
|
email = COALESCE($4, email),
|
||||||
avatar_url = $5,
|
avatar_url = $5,
|
||||||
phone_number = $6,
|
phone_number = $6,
|
||||||
phone_number_calling_code = $7,
|
phone_number_country_code = $7,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE id = $1 and type = 'contact';
|
WHERE id = $1 and type = 'contact';
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ SELECT
|
|||||||
u.availability_status,
|
u.availability_status,
|
||||||
u.last_active_at,
|
u.last_active_at,
|
||||||
u.last_login_at,
|
u.last_login_at,
|
||||||
u.phone_number_calling_code,
|
u.phone_number_country_code,
|
||||||
u.phone_number,
|
u.phone_number,
|
||||||
u.api_key,
|
u.api_key,
|
||||||
u.api_key_last_used_at,
|
u.api_key_last_used_at,
|
||||||
|
@@ -129,7 +129,7 @@ CREATE TABLE users (
|
|||||||
email TEXT NULL,
|
email TEXT NULL,
|
||||||
first_name TEXT NOT NULL,
|
first_name TEXT NOT NULL,
|
||||||
last_name TEXT NULL,
|
last_name TEXT NULL,
|
||||||
phone_number_calling_code TEXT NULL,
|
phone_number_country_code TEXT NULL,
|
||||||
phone_number TEXT NULL,
|
phone_number TEXT NULL,
|
||||||
country TEXT NULL,
|
country TEXT NULL,
|
||||||
"password" VARCHAR(150) NULL,
|
"password" VARCHAR(150) NULL,
|
||||||
@@ -146,7 +146,7 @@ CREATE TABLE users (
|
|||||||
api_key_last_used_at TIMESTAMPTZ NULL,
|
api_key_last_used_at TIMESTAMPTZ NULL,
|
||||||
CONSTRAINT constraint_users_on_country CHECK (LENGTH(country) <= 140),
|
CONSTRAINT constraint_users_on_country CHECK (LENGTH(country) <= 140),
|
||||||
CONSTRAINT constraint_users_on_phone_number CHECK (LENGTH(phone_number) <= 20),
|
CONSTRAINT constraint_users_on_phone_number CHECK (LENGTH(phone_number) <= 20),
|
||||||
CONSTRAINT constraint_users_on_phone_number_calling_code CHECK (LENGTH(phone_number_calling_code) <= 10),
|
CONSTRAINT constraint_users_on_phone_number_country_code CHECK (LENGTH(phone_number_country_code) <= 10),
|
||||||
CONSTRAINT constraint_users_on_email_length CHECK (LENGTH(email) <= 320),
|
CONSTRAINT constraint_users_on_email_length CHECK (LENGTH(email) <= 320),
|
||||||
CONSTRAINT constraint_users_on_first_name CHECK (LENGTH(first_name) <= 140),
|
CONSTRAINT constraint_users_on_first_name CHECK (LENGTH(first_name) <= 140),
|
||||||
CONSTRAINT constraint_users_on_last_name CHECK (LENGTH(last_name) <= 140)
|
CONSTRAINT constraint_users_on_last_name CHECK (LENGTH(last_name) <= 140)
|
||||||
|
Reference in New Issue
Block a user