Files
chatwoot/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactCardForm.vue
T

320 lines
8.9 KiB
Vue

<script setup>
import { computed, reactive, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { required, email as emailValidator } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
import { splitName } from '@chatwoot/utils';
import Input from 'dashboard/components-next/input/Input.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import PhoneNumberInput from 'dashboard/components-next/phonenumberinput/PhoneNumberInput.vue';
import CountryDropdown from 'dashboard/components-next/Countries/CountryDropdown.vue';
import CompaniesDropdown from 'dashboard/components-next/Companies/CompaniesDropdown.vue';
import AddContactNote from './AddContactNote.vue';
import ContactLabels from 'dashboard/components-next/Contacts/ContactLabels/ContactLabels.vue';
const props = defineProps({
contactData: {
type: Object,
default: null,
},
});
const emit = defineEmits(['update']);
const { t } = useI18n();
const SOCIAL_CONFIG = {
LINKEDIN: 'i-woot-linkedin',
FACEBOOK: 'i-woot-facebook',
INSTAGRAM: 'i-woot-instagram',
TWITTER: 'i-woot-x',
GITHUB: 'i-woot-github',
};
const formState = reactive({
firstName: '',
lastName: '',
email: '',
phoneNumber: '',
city: '',
countryCode: '',
bio: '',
companyName: '',
companyId: null,
socialProfiles: {
facebook: '',
github: '',
instagram: '',
linkedin: '',
twitter: '',
},
});
const validationRules = {
firstName: { required },
email: { email: emailValidator },
};
const v$ = useVuelidate(validationRules, formState);
const isFormInvalid = computed(() => v$.value.$invalid);
const prepareStateBasedOnProps = () => {
const {
name = '',
email: emailAddress = '',
phoneNumber: phone = '',
additionalAttributes = {},
} = props.contactData || {};
const { firstName: fName, lastName: lName } = splitName(name || '');
const {
description = '',
countryCode: country = '',
city: cityName = '',
socialProfiles: profiles = {},
companyName: company = '',
} = additionalAttributes || {};
formState.firstName = fName;
formState.lastName = lName;
formState.email = emailAddress;
formState.phoneNumber = phone || '';
formState.city = cityName;
formState.countryCode = country;
formState.bio = description;
formState.companyName = company;
formState.socialProfiles = { ...formState.socialProfiles, ...profiles };
};
const socialProfilesForm = computed(() =>
Object.entries(SOCIAL_CONFIG).map(([key, icon]) => ({
key,
placeholder: t(`CONTACTS_LAYOUT.CARD.SOCIAL_MEDIA.FORM.${key}.PLACEHOLDER`),
icon,
}))
);
const handleCompanyChange = company => {
formState.companyName = company?.name || '';
};
const handleUpdate = async () => {
const isFormValid = await v$.value.$validate();
if (!isFormValid) return;
const contactData = {
name: `${formState.firstName} ${formState.lastName}`.trim(),
email: formState.email,
phoneNumber: formState.phoneNumber,
additionalAttributes: {
description: formState.bio,
city: formState.city,
countryCode: formState.countryCode,
companyName: formState.companyName,
socialProfiles: formState.socialProfiles,
},
};
emit('update', contactData);
};
const resetValidation = () => {
v$.value.$reset();
};
const resetForm = () => {
Object.assign(formState, {
firstName: '',
lastName: '',
email: '',
phoneNumber: '',
city: '',
countryCode: '',
bio: '',
companyName: '',
companyId: null,
socialProfiles: {
facebook: '',
github: '',
instagram: '',
linkedin: '',
twitter: '',
},
});
};
watch(
() => props.contactData?.id,
id => {
if (id) prepareStateBasedOnProps();
},
{ immediate: true }
);
defineExpose({
resetValidation,
isFormInvalid,
resetForm,
handleUpdate,
});
</script>
<template>
<div class="flex flex-col">
<AddContactNote :contact-id="contactData?.id" />
<div class="flex flex-col items-start gap-4 pt-2 pb-3">
<span
class="py-1 text-heading-3 text-n-slate-12 z-10 h-6 bg-n-surface-1 inline-flex items-center gap-2"
>
<Icon
icon="i-lucide-settings-2"
class="size-4 text-n-slate-11 hidden lg:block"
/>
{{ t('CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.TITLE') }}
</span>
<div
class="lg:grid lg:grid-cols-[1fr_auto_1fr] flex flex-col items-start lg:items-center w-full gap-3 lg:px-6"
>
<div class="grid grid-cols-2 gap-2 min-w-0 w-full">
<Input
v-model="formState.firstName"
:placeholder="
t(
'CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.FORM.FIRST_NAME.PLACEHOLDER'
)
"
:message-type="v$.firstName.$error ? 'error' : 'info'"
class="h-8 min-w-0"
@input="v$.firstName.$touch()"
@blur="v$.firstName.$touch()"
/>
<Input
v-model="formState.lastName"
:placeholder="
t(
'CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.FORM.LAST_NAME.PLACEHOLDER'
)
"
class="h-8 min-w-0"
/>
</div>
<div class="w-px h-3 bg-n-strong hidden lg:block" />
<div class="grid grid-cols-3 gap-3 min-w-0 w-full">
<CompaniesDropdown
v-model="formState.companyId"
:placeholder="
t(
'CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.FORM.COMPANY_NAME.PLACEHOLDER'
)
"
:selected-company-name="formState.companyName"
class="min-w-0"
@change="handleCompanyChange"
/>
<Input
v-model="formState.city"
:placeholder="
t('CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.FORM.CITY.PLACEHOLDER')
"
class="h-8 min-w-0"
/>
<CountryDropdown
v-model="formState.countryCode"
:placeholder="
t(
'CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.FORM.COUNTRY.PLACEHOLDER'
)
"
class="min-w-0"
/>
</div>
</div>
<div
class="lg:grid lg:grid-cols-[1fr_auto_1fr] flex flex-col lg:items-center w-full gap-3 lg:px-6"
>
<div class="grid grid-cols-2 gap-2 min-w-0 w-full">
<Input
v-model="formState.email"
type="email"
:placeholder="
t(
'CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.FORM.EMAIL_ADDRESS.PLACEHOLDER'
)
"
:message-type="v$.email.$error ? 'error' : 'info'"
class="h-8 min-w-0 [&>input]:ltr:!pl-8 [&>input]:rtl:!pr-8"
@input="v$.email.$touch()"
@blur="v$.email.$touch()"
>
<template #prefix>
<Icon
icon="i-woot-mail"
class="absolute -translate-y-1/2 text-n-slate-11 size-4 top-1/2 ltr:left-2.5 rtl:right-2.5"
/>
</template>
</Input>
<PhoneNumberInput
v-model="formState.phoneNumber"
:placeholder="
t(
'CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.FORM.PHONE_NUMBER.PLACEHOLDER'
)
"
class="min-w-0"
/>
</div>
<div class="w-px h-3 bg-n-strong hidden lg:block" />
<Input
v-model="formState.bio"
:placeholder="
t('CONTACTS_LAYOUT.CARD.EDIT_DETAILS_FORM.FORM.BIO.PLACEHOLDER')
"
class="h-8 min-w-0"
/>
</div>
<div v-if="contactData?.id" class="lg:px-6">
<ContactLabels :contact-id="contactData.id" />
</div>
</div>
<div class="flex flex-col items-start gap-4 py-2">
<span
class="py-1 text-heading-3 text-n-slate-12 z-10 h-6 bg-n-surface-1 inline-flex items-center gap-2"
>
<Icon
icon="i-lucide-globe"
class="size-4 text-n-slate-11 hidden lg:block"
/>
{{ t('CONTACTS_LAYOUT.CARD.SOCIAL_MEDIA.TITLE') }}
</span>
<div class="flex flex-wrap gap-2 lg:px-6">
<div
v-for="item in socialProfilesForm"
:key="item.key"
class="flex items-center h-8 gap-2 px-2 rounded-lg bg-n-alpha-2 outline-1 outline outline-n-weak -outline-offset-1 dark:bg-n-solid-2 hover:outline-n-slate-6 transition-all duration-150"
>
<Icon
:icon="item.icon"
class="flex-shrink-0 text-n-slate-11 size-5"
/>
<input
v-model="formState.socialProfiles[item.key.toLowerCase()]"
class="w-auto min-w-[100px] text-sm bg-transparent outline-none reset-base text-n-slate-12 dark:text-n-slate-12 placeholder:text-n-slate-10 dark:placeholder:text-n-slate-10"
:placeholder="item.placeholder"
:size="item.placeholder.length"
/>
</div>
</div>
</div>
</div>
</template>