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:
Abhinav Raut
2025-09-14 19:36:30 +05:30
parent ec72c5af90
commit 6f300bb073
14 changed files with 93 additions and 27 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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">

View File

@@ -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>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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

View File

@@ -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"`

View File

@@ -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",

View 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
}

View File

@@ -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)
} }

View File

@@ -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"`

View File

@@ -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,

View File

@@ -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)