feat: Ability to edit a contact (#1092)
Ability to edit contact information in conversation sidebar Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
@@ -1,63 +1,9 @@
|
||||
<template>
|
||||
<div class="medium-3 bg-white contact--panel">
|
||||
<div class="contact--profile">
|
||||
<span class="close-button" @click="onPanelToggle">
|
||||
<i class="ion-chevron-right" />
|
||||
</span>
|
||||
<div class="contact--info">
|
||||
<thumbnail
|
||||
:src="contact.thumbnail"
|
||||
size="64px"
|
||||
:badge="channelType"
|
||||
:username="contact.name"
|
||||
:status="contact.availability_status"
|
||||
/>
|
||||
<div class="contact--details">
|
||||
<div class="contact--name">
|
||||
{{ contact.name }}
|
||||
</div>
|
||||
<a
|
||||
v-if="contact.email"
|
||||
:href="`mailto:${contact.email}`"
|
||||
class="contact--email"
|
||||
>
|
||||
{{ contact.email }}
|
||||
</a>
|
||||
<a
|
||||
v-if="contact.phone_number"
|
||||
:href="`tel:${contact.phone_number}`"
|
||||
class="contact--email"
|
||||
>
|
||||
{{ contact.phone_number }}
|
||||
</a>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
contact.additional_attributes &&
|
||||
contact.additional_attributes.screen_name
|
||||
"
|
||||
class="contact--location"
|
||||
>
|
||||
{{ `@${contact.additional_attributes.screen_name}` }}
|
||||
</div>
|
||||
<div class="contact--location">
|
||||
{{ contact.location }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="contact.bio" class="contact--bio">
|
||||
{{ contact.bio }}
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
contact.additional_attributes &&
|
||||
contact.additional_attributes.description
|
||||
"
|
||||
class="contact--bio"
|
||||
>
|
||||
{{ contact.additional_attributes.description }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="close-button" @click="onPanelToggle">
|
||||
<i class="ion-chevron-right" />
|
||||
</span>
|
||||
<contact-info :contact="contact" :channel-type="channelType" />
|
||||
<div v-if="browser.browser_name" class="conversation--details">
|
||||
<contact-details-item
|
||||
v-if="browser.browser_name"
|
||||
@@ -99,9 +45,9 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import ContactConversations from './ContactConversations.vue';
|
||||
import ContactDetailsItem from './ContactDetailsItem.vue';
|
||||
import ContactInfo from './contact/ContactInfo';
|
||||
import ConversationLabels from './labels/LabelBox.vue';
|
||||
import ContactCustomAttributes from './ContactCustomAttributes';
|
||||
|
||||
@@ -110,8 +56,8 @@ export default {
|
||||
ContactCustomAttributes,
|
||||
ContactConversations,
|
||||
ContactDetailsItem,
|
||||
ContactInfo,
|
||||
ConversationLabels,
|
||||
Thumbnail,
|
||||
},
|
||||
props: {
|
||||
conversationId: {
|
||||
@@ -210,7 +156,11 @@ export default {
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
padding: $space-normal;
|
||||
padding: $space-one;
|
||||
|
||||
i {
|
||||
margin-right: $space-smaller;
|
||||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
@@ -221,57 +171,9 @@ export default {
|
||||
color: $color-heading;
|
||||
}
|
||||
|
||||
.contact--profile {
|
||||
align-items: center;
|
||||
padding: $space-medium 0 $space-one;
|
||||
|
||||
.user-thumbnail-box {
|
||||
margin-right: $space-normal;
|
||||
}
|
||||
}
|
||||
|
||||
.contact--details {
|
||||
margin-top: $space-small;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.contact--info {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.contact--name {
|
||||
@include text-ellipsis;
|
||||
text-transform: capitalize;
|
||||
|
||||
font-weight: $font-weight-bold;
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
|
||||
.contact--email {
|
||||
@include text-ellipsis;
|
||||
|
||||
color: $color-gray;
|
||||
display: block;
|
||||
line-height: $space-medium;
|
||||
|
||||
&:hover {
|
||||
color: $color-woot;
|
||||
}
|
||||
}
|
||||
|
||||
.contact--bio {
|
||||
margin-top: $space-normal;
|
||||
}
|
||||
|
||||
.conversation--details {
|
||||
border-top: 1px solid $color-border-light;
|
||||
padding: $space-large $space-normal;
|
||||
padding: $space-normal;
|
||||
}
|
||||
|
||||
.conversation--labels {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="contact--profile">
|
||||
<div class="contact--info">
|
||||
<thumbnail
|
||||
:src="contact.thumbnail"
|
||||
size="48px"
|
||||
:badge="channelType"
|
||||
:username="contact.name"
|
||||
:status="contact.availability_status"
|
||||
/>
|
||||
|
||||
<div class="contact--details">
|
||||
<div class="contact--name">
|
||||
{{ contact.name }}
|
||||
</div>
|
||||
<div v-if="additionalAttibutes.description" class="contact--bio">
|
||||
{{ additionalAttibutes.description }}
|
||||
</div>
|
||||
<social-icons :social-profiles="socialProfiles" />
|
||||
<div class="contact--metadata">
|
||||
<contact-info-row
|
||||
:href="contact.email ? `mailto:${contact.email}` : ''"
|
||||
:value="contact.email"
|
||||
icon="ion-email"
|
||||
:title="$t('CONTACT_PANEL.EMAIL_ADDRESS')"
|
||||
/>
|
||||
<contact-info-row
|
||||
:href="contact.phone_number ? `tel:${contact.phone_number}` : ''"
|
||||
:value="contact.phone_number"
|
||||
icon="ion-ios-telephone"
|
||||
:title="$t('CONTACT_PANEL.PHONE_NUMBER')"
|
||||
/>
|
||||
<contact-info-row
|
||||
:value="additionalAttibutes.location"
|
||||
icon="ion-map"
|
||||
:title="$t('CONTACT_PANEL.LOCATION')"
|
||||
/>
|
||||
<contact-info-row
|
||||
:value="additionalAttibutes.company_name"
|
||||
icon="ion-briefcase"
|
||||
:title="$t('CONTACT_PANEL.COMPANY')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<woot-button
|
||||
class="expanded"
|
||||
variant="hollow primary small"
|
||||
@click="toggleEditModal"
|
||||
>
|
||||
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
<edit-contact
|
||||
:show="showEditModal"
|
||||
:contact="contact"
|
||||
@cancel="toggleEditModal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ContactInfoRow from './ContactInfoRow';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import SocialIcons from './SocialIcons';
|
||||
import EditContact from './EditContact';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContactInfoRow,
|
||||
EditContact,
|
||||
Thumbnail,
|
||||
SocialIcons,
|
||||
},
|
||||
props: {
|
||||
contact: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
channelType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showEditModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
additionalAttibutes() {
|
||||
return this.contact.additional_attributes || {};
|
||||
},
|
||||
socialProfiles() {
|
||||
const {
|
||||
social_profiles: socialProfiles,
|
||||
screen_name: twitterScreenName,
|
||||
} = this.additionalAttibutes;
|
||||
|
||||
return { twitter: twitterScreenName, ...(socialProfiles || {}) };
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleEditModal() {
|
||||
this.showEditModal = !this.showEditModal;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~dashboard/assets/scss/variables';
|
||||
@import '~dashboard/assets/scss/mixins';
|
||||
.contact--profile {
|
||||
align-items: flex-start;
|
||||
padding: $space-normal;
|
||||
|
||||
.user-thumbnail-box {
|
||||
margin-right: $space-normal;
|
||||
}
|
||||
}
|
||||
|
||||
.contact--details {
|
||||
margin-top: $space-small;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.contact--info {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.contact--name {
|
||||
@include text-ellipsis;
|
||||
text-transform: capitalize;
|
||||
|
||||
font-weight: $font-weight-bold;
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
|
||||
.contact--bio {
|
||||
margin: $space-small 0 0;
|
||||
}
|
||||
|
||||
.contact--metadata {
|
||||
margin: $space-small 0 $space-normal;
|
||||
}
|
||||
|
||||
.social--icons {
|
||||
i {
|
||||
font-size: $font-weight-normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="contact-info--row">
|
||||
<a v-if="href" :href="href" class="contact-info--details">
|
||||
<i :class="icon" class="contact-info--icon" />
|
||||
<span v-if="value" class="text-truncate">{{ value }}</span>
|
||||
<span v-else class="text-muted">{{
|
||||
$t('CONTACT_PANEL.NOT_AVAILABLE')
|
||||
}}</span>
|
||||
</a>
|
||||
<div v-else class="contact-info--details">
|
||||
<i :class="icon" class="contact-info--icon" />
|
||||
<span v-if="value" class="text-truncate">{{ value }}</span>
|
||||
<span v-else class="text-muted">{{
|
||||
$t('CONTACT_PANEL.NOT_AVAILABLE')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
href: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import '~dashboard/assets/scss/variables';
|
||||
|
||||
.contact-info--row {
|
||||
.contact-info--icon {
|
||||
font-size: $font-size-default;
|
||||
min-width: $space-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-info--details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $space-smaller;
|
||||
color: $color-body;
|
||||
|
||||
&.a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<woot-modal :show.sync="show" :on-close="onCancel">
|
||||
<div class="column content-box">
|
||||
<woot-modal-header
|
||||
:header-title="
|
||||
`${$t('EDIT_CONTACT.TITLE')} - ${contact.name || contact.email}`
|
||||
"
|
||||
:header-content="$t('EDIT_CONTACT.DESC')"
|
||||
/>
|
||||
<form class="edit-contact--form" @submit.prevent="onSubmit">
|
||||
<div class="row">
|
||||
<div class="medium-9 columns">
|
||||
<label :class="{ error: $v.name.$error }">
|
||||
{{ $t('EDIT_CONTACT.FORM.NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="name"
|
||||
type="text"
|
||||
:placeholder="$t('EDIT_CONTACT.FORM.NAME.PLACEHOLDER')"
|
||||
@input="$v.name.$touch"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label :class="{ error: $v.email.$error }">
|
||||
{{ $t('EDIT_CONTACT.FORM.EMAIL_ADDRESS.LABEL') }}
|
||||
<input
|
||||
v-model.trim="email"
|
||||
type="text"
|
||||
:placeholder="$t('EDIT_CONTACT.FORM.EMAIL_ADDRESS.PLACEHOLDER')"
|
||||
@input="$v.email.$touch"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="medium-12 columns">
|
||||
<label :class="{ error: $v.description.$error }">
|
||||
{{ $t('EDIT_CONTACT.FORM.BIO.LABEL') }}
|
||||
<textarea
|
||||
v-model.trim="description"
|
||||
type="text"
|
||||
:placeholder="$t('EDIT_CONTACT.FORM.BIO.PLACEHOLDER')"
|
||||
@input="$v.description.$touch"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<woot-input
|
||||
v-model.trim="phoneNumber"
|
||||
class="medium-6 columns"
|
||||
:label="$t('EDIT_CONTACT.FORM.PHONE_NUMBER.LABEL')"
|
||||
:placeholder="$t('EDIT_CONTACT.FORM.PHONE_NUMBER.PLACEHOLDER')"
|
||||
/>
|
||||
|
||||
<woot-input
|
||||
v-model.trim="location"
|
||||
class="medium-6 columns"
|
||||
:label="$t('EDIT_CONTACT.FORM.LOCATION.LABEL')"
|
||||
:placeholder="$t('EDIT_CONTACT.FORM.LOCATION.PLACEHOLDER')"
|
||||
/>
|
||||
</div>
|
||||
<woot-input
|
||||
v-model.trim="companyName"
|
||||
class="medium-6 columns"
|
||||
:label="$t('EDIT_CONTACT.FORM.COMPANY_NAME.LABEL')"
|
||||
:placeholder="$t('EDIT_CONTACT.FORM.COMPANY_NAME.PLACEHOLDER')"
|
||||
/>
|
||||
<div class="medium-12 columns">
|
||||
<label>
|
||||
Social Profiles
|
||||
</label>
|
||||
<div
|
||||
v-for="socialProfile in socialProfileKeys"
|
||||
:key="socialProfile.key"
|
||||
class="input-group"
|
||||
>
|
||||
<span class="input-group-label">{{ socialProfile.prefixURL }}</span>
|
||||
<input
|
||||
v-model="socialProfileUserNames[socialProfile.key]"
|
||||
class="input-group-field"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="medium-12 columns">
|
||||
<woot-submit-button :button-text="$t('EDIT_CONTACT.FORM.SUBMIT')" />
|
||||
<button class="button clear" @click.prevent="onCancel">
|
||||
{{ $t('EDIT_CONTACT.FORM.CANCEL') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { DuplicateContactException } from 'shared/helpers/CustomErrors';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
contact: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasADuplicateContact: false,
|
||||
duplicateContact: {},
|
||||
companyName: '',
|
||||
description: '',
|
||||
email: '',
|
||||
location: '',
|
||||
name: '',
|
||||
phoneNumber: '',
|
||||
socialProfileUserNames: {
|
||||
facebook: '',
|
||||
twitter: '',
|
||||
linkedin: '',
|
||||
},
|
||||
socialProfileKeys: [
|
||||
{ key: 'facebook', prefixURL: 'https://facebook.com/' },
|
||||
{ key: 'twitter', prefixURL: 'https://twitter.com/' },
|
||||
{ key: 'linkedin', prefixURL: 'https://linkedin.com/' },
|
||||
],
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
name: {
|
||||
required,
|
||||
},
|
||||
description: {},
|
||||
email: {},
|
||||
companyName: {},
|
||||
phoneNumber: {},
|
||||
location: {},
|
||||
bio: {},
|
||||
},
|
||||
watch: {
|
||||
contact() {
|
||||
this.setContactObject();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onCancel() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
setContactObject() {
|
||||
const { email: email, phone_number: phoneNumber, name } = this.contact;
|
||||
const additionalAttributes = this.contact.additional_attributes || {};
|
||||
|
||||
this.name = name || '';
|
||||
this.email = email || '';
|
||||
this.phoneNumber = phoneNumber || '';
|
||||
this.location = additionalAttributes.location || '';
|
||||
this.companyName = additionalAttributes.company_name || '';
|
||||
this.description = additionalAttributes.description || '';
|
||||
const {
|
||||
social_profiles: socialProfiles = {},
|
||||
screen_name: twitterScreenName,
|
||||
} = additionalAttributes;
|
||||
this.socialProfileUserNames = {
|
||||
twitter: socialProfiles.twitter || twitterScreenName || '',
|
||||
facebook: socialProfiles.facebook || '',
|
||||
linkedin: socialProfiles.linkedin || '',
|
||||
};
|
||||
},
|
||||
getContactObject() {
|
||||
return {
|
||||
id: this.contact.id,
|
||||
name: this.name,
|
||||
email: this.email,
|
||||
phone_number: this.phoneNumber,
|
||||
additional_attributes: {
|
||||
...this.contact.additional_attributes,
|
||||
description: this.description,
|
||||
location: this.location,
|
||||
company_name: this.companyName,
|
||||
social_profiles: this.socialProfileUserNames,
|
||||
},
|
||||
};
|
||||
},
|
||||
resetDuplicate() {
|
||||
this.hasADuplicateContact = false;
|
||||
this.duplicateContact = {};
|
||||
},
|
||||
async onSubmit() {
|
||||
this.resetDuplicate();
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.$store.dispatch('contacts/update', this.getContactObject());
|
||||
this.showAlert(this.$t('EDIT_CONTACT.SUCCESS_MESSAGE'));
|
||||
} catch (error) {
|
||||
if (error instanceof DuplicateContactException) {
|
||||
this.hasADuplicateContact = true;
|
||||
this.duplicateContact = error.data;
|
||||
this.showAlert(this.$t('EDIT_CONTACT.CONTACT_ALREADY_EXIST'));
|
||||
} else {
|
||||
this.showAlert(this.$t('EDIT_CONTACT.ERROR_MESSAGE'));
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.edit-contact--form {
|
||||
padding: var(--space-normal) var(--space-large) var(--space-large);
|
||||
|
||||
.columns {
|
||||
padding: 0 var(--space-smaller);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div v-if="availableProfiles.length" class="social--icons">
|
||||
<a
|
||||
v-for="profile in availableProfiles"
|
||||
:key="profile.key"
|
||||
:href="`${profile.link}${socialProfiles[profile.key]}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
class="contact--social-icon"
|
||||
>
|
||||
<i :class="`ion-social-${profile.icon}`" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
socialProfiles: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
socialMediaLinks: [
|
||||
{ key: 'facebook', icon: 'facebook', link: 'https://facebook.com/' },
|
||||
{ key: 'twitter', icon: 'twitter', link: 'https://twitter.com/' },
|
||||
{ key: 'linkedin', icon: 'linkedin', link: 'https://linkedin.com/' },
|
||||
{ key: 'github', icon: 'github', link: 'https://github.com/' },
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
availableProfiles() {
|
||||
return this.socialMediaLinks.filter(
|
||||
mediaLink => !!this.socialProfiles[mediaLink.key]
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~dashboard/assets/scss/variables';
|
||||
|
||||
.contact--social-icon {
|
||||
font-size: $font-size-medium;
|
||||
padding-right: $space-slab;
|
||||
color: $color-body;
|
||||
}
|
||||
|
||||
.social--icons {
|
||||
margin-top: $space-small;
|
||||
}
|
||||
</style>
|
||||
@@ -35,7 +35,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global bus */
|
||||
import { mapGetters } from 'vuex';
|
||||
import AddLabelToConversation from './AddLabelToConversation';
|
||||
import ContactDetailsItem from '../ContactDetailsItem';
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
/* global bus */
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import InboxMembersAPI from '../../../../api/inboxMembers';
|
||||
|
||||
Reference in New Issue
Block a user