Merge branch 'develop' into feat/CW-3082
This commit is contained in:
@@ -88,7 +88,6 @@
|
||||
<script>
|
||||
import { getUnixTime } from 'date-fns';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
@@ -109,7 +108,7 @@ export default {
|
||||
WootDropdownMenu,
|
||||
CustomSnoozeModal,
|
||||
},
|
||||
mixins: [clickaway, alertMixin, keyboardEventListenerMixins],
|
||||
mixins: [alertMixin, keyboardEventListenerMixins],
|
||||
props: { conversationId: { type: [String, Number], required: true } },
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
@@ -67,7 +66,7 @@ export default {
|
||||
AvailabilityStatusBadge,
|
||||
},
|
||||
|
||||
mixins: [clickaway, alertMixin],
|
||||
mixins: [alertMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
import Auth from '../../../api/auth';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
@@ -119,7 +118,6 @@ export default {
|
||||
WootDropdownItem,
|
||||
AvailabilityStatus,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -31,7 +31,7 @@ import CalendarMonth from './components/CalendarMonth.vue';
|
||||
import CalendarWeek from './components/CalendarWeek.vue';
|
||||
import CalendarFooter from './components/CalendarFooter.vue';
|
||||
|
||||
const { LAST_7_DAYS, CUSTOM_RANGE } = DATE_RANGE_TYPES;
|
||||
const { LAST_7_DAYS, LAST_30_DAYS, CUSTOM_RANGE } = DATE_RANGE_TYPES;
|
||||
const { START_CALENDAR, END_CALENDAR } = CALENDAR_TYPES;
|
||||
const { WEEK, MONTH, YEAR } = CALENDAR_PERIODS;
|
||||
|
||||
@@ -59,14 +59,16 @@ const emit = defineEmits(['change']);
|
||||
// Watcher will set the start and end dates based on the selected range
|
||||
watch(selectedRange, newRange => {
|
||||
if (newRange !== CUSTOM_RANGE) {
|
||||
// If selecting a range other than last 7 days, set the start and end dates to the selected start and end dates
|
||||
// If selecting last 7 days, set the start date to the selected start date
|
||||
// If selecting a range other than last 7 days or last 30 days, set the start and end dates to the selected start and end dates
|
||||
// If selecting last 7 days or last 30 days is, set the start date to the selected start date
|
||||
// and the end date to one month ahead of the start date if the start date and end date are in the same month
|
||||
// Otherwise set the end date to the selected end date
|
||||
const isLast7days = newRange === LAST_7_DAYS;
|
||||
const isLastSevenOrThirtyDays =
|
||||
newRange === LAST_7_DAYS || newRange === LAST_30_DAYS;
|
||||
startCurrentDate.value = selectedStartDate.value;
|
||||
endCurrentDate.value =
|
||||
isLast7days && isSameMonth(selectedStartDate.value, selectedEndDate.value)
|
||||
isLastSevenOrThirtyDays &&
|
||||
isSameMonth(selectedStartDate.value, selectedEndDate.value)
|
||||
? startOfMonth(addMonths(selectedStartDate.value, 1))
|
||||
: selectedEndDate.value;
|
||||
selectingEndDate.value = false;
|
||||
|
||||
@@ -18,13 +18,11 @@
|
||||
|
||||
<script>
|
||||
import { Chrome } from 'vue-color';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Chrome,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
import AddLabel from 'shared/components/ui/dropdown/AddLabel.vue';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
import LabelDropdown from 'shared/components/ui/label/LabelDropdown.vue';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||
|
||||
export default {
|
||||
@@ -42,7 +41,7 @@ export default {
|
||||
LabelDropdown,
|
||||
},
|
||||
|
||||
mixins: [clickaway, adminMixin, keyboardEventListenerMixins],
|
||||
mixins: [adminMixin, keyboardEventListenerMixins],
|
||||
|
||||
props: {
|
||||
allLabels: {
|
||||
|
||||
+1
-2
@@ -45,7 +45,6 @@
|
||||
<script>
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import FilterItem from './FilterItem.vue';
|
||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
|
||||
@@ -53,7 +52,7 @@ export default {
|
||||
components: {
|
||||
FilterItem,
|
||||
},
|
||||
mixins: [clickaway, uiSettingsMixin],
|
||||
mixins: [uiSettingsMixin],
|
||||
data() {
|
||||
return {
|
||||
showActionsDropdown: false,
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import EmailTranscriptModal from './EmailTranscriptModal.vue';
|
||||
import ResolveAction from '../../buttons/ResolveAction.vue';
|
||||
@@ -52,7 +51,7 @@ export default {
|
||||
EmailTranscriptModal,
|
||||
ResolveAction,
|
||||
},
|
||||
mixins: [alertMixin, clickaway],
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
showEmailActionsModal: false,
|
||||
|
||||
@@ -153,7 +153,6 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
|
||||
@@ -218,7 +217,6 @@ export default {
|
||||
ArticleSearchPopover,
|
||||
},
|
||||
mixins: [
|
||||
clickaway,
|
||||
inboxMixin,
|
||||
uiSettingsMixin,
|
||||
alertMixin,
|
||||
|
||||
@@ -180,7 +180,6 @@
|
||||
</woot-modal>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
import timeMixin from 'dashboard/mixins/time';
|
||||
@@ -200,7 +199,7 @@ export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
},
|
||||
mixins: [keyboardEventListenerMixins, clickaway, timeMixin],
|
||||
mixins: [keyboardEventListenerMixins, timeMixin],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
<script>
|
||||
import { evaluateSLAStatus } from '../helpers/SLAHelper';
|
||||
import SLAPopoverCard from './SLAPopoverCard.vue';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
const REFRESH_INTERVAL = 60000;
|
||||
|
||||
@@ -59,7 +58,6 @@ export default {
|
||||
components: {
|
||||
SLAPopoverCard,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
chat: {
|
||||
type: Object,
|
||||
|
||||
-2
@@ -101,14 +101,12 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
selectedInboxes: {
|
||||
type: Array,
|
||||
|
||||
-2
@@ -74,11 +74,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
mixins: [clickaway],
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
|
||||
-2
@@ -59,10 +59,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
export default {
|
||||
mixins: [clickaway],
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
|
||||
-2
@@ -36,7 +36,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
|
||||
@@ -45,7 +44,6 @@ export default {
|
||||
WootDropdownItem,
|
||||
WootDropdownMenu,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
selectedInboxes: {
|
||||
type: Array,
|
||||
|
||||
@@ -87,7 +87,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { SHORTCUT_KEYS } from './constants';
|
||||
import Hotkey from 'dashboard/components/base/Hotkey.vue';
|
||||
|
||||
@@ -95,7 +94,6 @@ export default {
|
||||
components: {
|
||||
Hotkey,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"body": "Add agents to the created inbox."
|
||||
},
|
||||
{
|
||||
"title": "Voila!",
|
||||
"title": "Voilà!",
|
||||
"route": "settings_inbox_finish",
|
||||
"body": "You are all set to go!"
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
"CHOOSE_PLACEHOLDER": "Select a page from the list",
|
||||
"INBOX_NAME": "Inbox Name",
|
||||
"ADD_NAME": "Add a name for your inbox",
|
||||
"PICK_NAME": "Pick A Name Your Inbox",
|
||||
"PICK_NAME": "Pick a Name for your Inbox",
|
||||
"PICK_A_VALUE": "Pick a value"
|
||||
},
|
||||
"TWITTER": {
|
||||
@@ -62,7 +62,7 @@
|
||||
},
|
||||
"CHANNEL_WEBHOOK_URL": {
|
||||
"LABEL": "Webhook URL",
|
||||
"PLACEHOLDER": "Enter your Webhook URL",
|
||||
"PLACEHOLDER": "Please enter your Webhook URL",
|
||||
"ERROR": "Please enter a valid URL"
|
||||
},
|
||||
"CHANNEL_DOMAIN": {
|
||||
@@ -143,7 +143,7 @@
|
||||
"ERROR": "This field is required"
|
||||
},
|
||||
"PHONE_NUMBER": {
|
||||
"LABEL": "Phone number",
|
||||
"LABEL": "Phone Number",
|
||||
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
|
||||
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
|
||||
},
|
||||
@@ -175,12 +175,12 @@
|
||||
},
|
||||
"API_KEY": {
|
||||
"LABEL": "API Key",
|
||||
"PLACEHOLDER": "Please enter your Bandwith API Key",
|
||||
"PLACEHOLDER": "Please enter your Bandwidth API Key",
|
||||
"ERROR": "This field is required"
|
||||
},
|
||||
"API_SECRET": {
|
||||
"LABEL": "API Secret",
|
||||
"PLACEHOLDER": "Please enter your Bandwith API Secret",
|
||||
"PLACEHOLDER": "Please enter your Bandwidth API Secret",
|
||||
"ERROR": "This field is required"
|
||||
},
|
||||
"APPLICATION_ID": {
|
||||
@@ -239,7 +239,7 @@
|
||||
},
|
||||
"WEBHOOK_VERIFY_TOKEN": {
|
||||
"LABEL": "Webhook Verify Token",
|
||||
"PLACEHOLDER": "Enter a verify token which you want to configure for facebook webhooks.",
|
||||
"PLACEHOLDER": "Enter a verify token which you want to configure for Facebook webhooks.",
|
||||
"ERROR": "Please enter a valid value."
|
||||
},
|
||||
"API_KEY": {
|
||||
@@ -269,7 +269,7 @@
|
||||
},
|
||||
"WEBHOOK_URL": {
|
||||
"LABEL": "Webhook URL",
|
||||
"SUBTITLE": "Configure the URL where you want to recieve callbacks on events.",
|
||||
"SUBTITLE": "Configure the URL where you want to receive callbacks on events.",
|
||||
"PLACEHOLDER": "Webhook URL"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create API Channel",
|
||||
@@ -279,7 +279,7 @@
|
||||
},
|
||||
"EMAIL_CHANNEL": {
|
||||
"TITLE": "Email Channel",
|
||||
"DESC": "Integrate you email inbox.",
|
||||
"DESC": "Integrate your email inbox.",
|
||||
"CHANNEL_NAME": {
|
||||
"LABEL": "Channel Name",
|
||||
"PLACEHOLDER": "Please enter a channel name",
|
||||
@@ -345,7 +345,7 @@
|
||||
"AGENTS": {
|
||||
"TITLE": "Agents",
|
||||
"DESC": "Here you can add agents to manage your newly created inbox. Only these selected agents will have access to your inbox. Agents which are not part of this inbox will not be able to see or respond to messages in this inbox when they login. <br> <b>PS:</b> As an administrator, if you need access to all inboxes, you should add yourself as agent to all inboxes that you create.",
|
||||
"VALIDATION_ERROR": "Add atleast one agent to your new Inbox",
|
||||
"VALIDATION_ERROR": "Add at least one agent to your new Inbox",
|
||||
"PICK_AGENTS": "Pick agents for the inbox"
|
||||
},
|
||||
"DETAILS": {
|
||||
@@ -406,7 +406,7 @@
|
||||
},
|
||||
"SENDER_NAME_SECTION": {
|
||||
"TITLE": "Sender name",
|
||||
"SUB_TEXT": "Select the name shown to the your customer when they receive emails from your agents.",
|
||||
"SUB_TEXT": "Select the name shown to your customer when they receive emails from your agents.",
|
||||
"FOR_EG": "For eg:",
|
||||
"FRIENDLY": {
|
||||
"TITLE": "Friendly",
|
||||
@@ -508,12 +508,12 @@
|
||||
"ALLOW_MESSAGES_AFTER_RESOLVED": "Allow messages after conversation resolved",
|
||||
"ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT": "Allow the end-users to send messages even after the conversation is resolved.",
|
||||
"WHATSAPP_SECTION_SUBHEADER": "This API Key is used for the integration with the WhatsApp APIs.",
|
||||
"WHATSAPP_SECTION_UPDATE_SUBHEADER": "Enter the updated key to be used for the integration with the WhatsApp APIs.",
|
||||
"WHATSAPP_SECTION_UPDATE_SUBHEADER": "Enter the new API key to be used for the integration with the WhatsApp APIs.",
|
||||
"WHATSAPP_SECTION_TITLE": "API Key",
|
||||
"WHATSAPP_SECTION_UPDATE_TITLE": "Update API Key",
|
||||
"WHATSAPP_SECTION_UPDATE_PLACEHOLDER": "Enter the new API Key here",
|
||||
"WHATSAPP_SECTION_UPDATE_BUTTON": "Update",
|
||||
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verify Token",
|
||||
"WHATSAPP_WEBHOOK_TITLE": "Webhook Verification Token",
|
||||
"WHATSAPP_WEBHOOK_SUBHEADER": "This token is used to verify the authenticity of the webhook endpoint.",
|
||||
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
|
||||
},
|
||||
@@ -585,7 +585,7 @@
|
||||
"NOTE_TEXT": "To enable SMTP, please configure IMAP.",
|
||||
"UPDATE": "Update IMAP settings",
|
||||
"TOGGLE_AVAILABILITY": "Enable IMAP configuration for this inbox",
|
||||
"TOGGLE_HELP": "Enabling IMAP will help the user to recieve email",
|
||||
"TOGGLE_HELP": "Enabling IMAP will help the user to receive email",
|
||||
"EDIT": {
|
||||
"SUCCESS_MESSAGE": "IMAP settings updated successfully",
|
||||
"ERROR_MESSAGE": "Unable to update IMAP settings"
|
||||
|
||||
@@ -57,13 +57,19 @@
|
||||
},
|
||||
"ACCESS_TOKEN": {
|
||||
"TITLE": "Access Token",
|
||||
"NOTE": "This token can be used if you are building an API based integration"
|
||||
"NOTE": "This token can be used if you are building an API based integration",
|
||||
"COPY": "Copy"
|
||||
},
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Notifications",
|
||||
"NOTE": "Enable audio notifications in dashboard for new messages and conversations.",
|
||||
"ALERT_TYPES": {
|
||||
"NONE": "None",
|
||||
"MINE": "Assigned",
|
||||
"ALL": "All"
|
||||
},
|
||||
"ALERT_TYPE": {
|
||||
"TITLE": "Alert events:",
|
||||
"TITLE": "Alert events for conversations:",
|
||||
"NONE": "None",
|
||||
"ASSIGNED": "Assigned Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations"
|
||||
@@ -89,6 +95,22 @@
|
||||
"SLA_MISSED_NEXT_RESPONSE": "Send email notifications when a conversation misses next response SLA",
|
||||
"SLA_MISSED_RESOLUTION": "Send email notifications when a conversation misses resolution SLA"
|
||||
},
|
||||
"NOTIFICATIONS": {
|
||||
"TITLE": "Notification preferences",
|
||||
"TYPE_TITLE": "Notification type",
|
||||
"EMAIL": "Email",
|
||||
"PUSH": "Push notification",
|
||||
"TYPES": {
|
||||
"CONVERSATION_CREATED": "A new conversation is created",
|
||||
"CONVERSATION_ASSIGNED": "A conversation is assigned to you",
|
||||
"CONVERSATION_MENTION": "You are mentioned in a conversation",
|
||||
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "A new message is created in an assigned conversation",
|
||||
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "A new message is created in a participating conversation",
|
||||
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
|
||||
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
|
||||
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"UPDATE_SUCCESS": "Your notification preferences are updated successfully",
|
||||
"UPDATE_ERROR": "There is an error while updating the preferences, please try again"
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import AddCannedModal from 'dashboard/routes/dashboard/settings/canned/AddCanned.vue';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
@@ -124,7 +123,7 @@ export default {
|
||||
TranslateModal,
|
||||
MenuItem,
|
||||
},
|
||||
mixins: [alertMixin, clickaway, messageFormatterMixin],
|
||||
mixins: [alertMixin, messageFormatterMixin],
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
|
||||
@@ -73,7 +73,6 @@ import SearchResultConversationsList from './SearchResultConversationsList.vue';
|
||||
import SearchResultMessagesList from './SearchResultMessagesList.vue';
|
||||
import SearchResultContactsList from './SearchResultContactsList.vue';
|
||||
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
export default {
|
||||
@@ -84,7 +83,6 @@ export default {
|
||||
SearchResultConversationsList,
|
||||
SearchResultMessagesList,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
data() {
|
||||
return {
|
||||
selectedTab: 'all',
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { VeTable } from 'vue-easytable';
|
||||
import { getCountryFlag } from 'dashboard/helper/flag';
|
||||
|
||||
@@ -45,7 +44,7 @@ export default {
|
||||
Spinner,
|
||||
VeTable,
|
||||
},
|
||||
mixins: [clickaway, timeMixin, rtlMixin],
|
||||
mixins: [timeMixin, rtlMixin],
|
||||
props: {
|
||||
contacts: {
|
||||
type: Array,
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -92,7 +91,7 @@ export default {
|
||||
ThumbnailGroup,
|
||||
MultiselectDropdownItems,
|
||||
},
|
||||
mixins: [alertMixin, agentMixin, clickaway],
|
||||
mixins: [alertMixin, agentMixin],
|
||||
props: {
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import MacroPreview from './MacroPreview.vue';
|
||||
import { CONVERSATION_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
|
||||
@@ -42,7 +41,7 @@ export default {
|
||||
components: {
|
||||
MacroPreview,
|
||||
},
|
||||
mixins: [alertMixin, clickaway],
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
macro: {
|
||||
type: Object,
|
||||
|
||||
@@ -166,7 +166,6 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import timeMixin from 'dashboard/mixins/time';
|
||||
import ContactInfoRow from './ContactInfoRow.vue';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
@@ -195,7 +194,7 @@ export default {
|
||||
NewConversation,
|
||||
ContactMergeModal,
|
||||
},
|
||||
mixins: [alertMixin, adminMixin, clickaway, timeMixin],
|
||||
mixins: [alertMixin, adminMixin, timeMixin],
|
||||
props: {
|
||||
contact: {
|
||||
type: Object,
|
||||
|
||||
@@ -48,7 +48,6 @@ import { mapGetters } from 'vuex';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import LabelDropdown from 'shared/components/ui/label/LabelDropdown.vue';
|
||||
import AddLabel from 'shared/components/ui/dropdown/AddLabel.vue';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin';
|
||||
@@ -60,12 +59,7 @@ export default {
|
||||
AddLabel,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
clickaway,
|
||||
conversationLabelMixin,
|
||||
adminMixin,
|
||||
keyboardEventListenerMixins,
|
||||
],
|
||||
mixins: [conversationLabelMixin, adminMixin, keyboardEventListenerMixins],
|
||||
props: {
|
||||
conversationId: {
|
||||
type: Number,
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
import timeMixin from '../../../../mixins/time';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
@@ -50,7 +49,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
mixins: [timeMixin, messageFormatterMixin, clickaway],
|
||||
mixins: [timeMixin, messageFormatterMixin],
|
||||
props: {
|
||||
isOnExpandedLayout: {
|
||||
type: Boolean,
|
||||
|
||||
+1
-2
@@ -34,7 +34,6 @@
|
||||
|
||||
<script>
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
|
||||
|
||||
import SearchHeader from './Header.vue';
|
||||
@@ -52,7 +51,7 @@ export default {
|
||||
SearchResults,
|
||||
ArticleView,
|
||||
},
|
||||
mixins: [clickaway, portalMixin, alertMixin, keyboardEventListenerMixins],
|
||||
mixins: [portalMixin, alertMixin, keyboardEventListenerMixins],
|
||||
props: {
|
||||
selectedPortalSlug: {
|
||||
type: String,
|
||||
|
||||
-3
@@ -138,8 +138,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
import MultiselectDropdownItems from 'shared/components/ui/MultiselectDropdownItems.vue';
|
||||
@@ -152,7 +150,6 @@ export default {
|
||||
WootDropdownMenu,
|
||||
MultiselectDropdownItems,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
headerTitle: {
|
||||
type: String,
|
||||
|
||||
+1
-2
@@ -109,14 +109,13 @@
|
||||
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
|
||||
const { ARTICLE_STATUS_TYPES } = wootConstants;
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin, clickaway],
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
isSidebarOpen: {
|
||||
type: Boolean,
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex-grow-0 flex-shrink-0 w-full h-full max-w-full bg-white border border-transparent border-solid dark:bg-slate-900 dark:border-transparent md:max-w-2xl"
|
||||
>
|
||||
<h3
|
||||
v-if="$slots.title || title"
|
||||
class="text-lg text-black-900 dark:text-slate-200"
|
||||
>
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</h3>
|
||||
<div
|
||||
class="mx-0 my-4 border-b border-solid border-slate-25 dark:border-slate-800"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<slot name="footer-left" />
|
||||
</div>
|
||||
<div>
|
||||
<slot name="footer-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,13 +47,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import PortalSwitch from './PortalSwitch.vue';
|
||||
export default {
|
||||
components: {
|
||||
PortalSwitch,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
portals: {
|
||||
type: Array,
|
||||
|
||||
+221
-236
@@ -1,256 +1,241 @@
|
||||
<template>
|
||||
<div
|
||||
class="pt-3 bg-white dark:bg-slate-900 h-full border border-solid border-transparent px-6 pb-6 dark:border-transparent w-full max-w-full md:w-3/4 md:max-w-[75%] flex-shrink-0 flex-grow-0"
|
||||
>
|
||||
<div class="w-full">
|
||||
<h3 class="text-lg text-black-900 dark:text-slate-200">
|
||||
{{
|
||||
$t(
|
||||
'HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.BASIC_SETTINGS_PAGE.TITLE'
|
||||
)
|
||||
}}
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="my-4 mx-0 border-b border-solid border-slate-25 dark:border-slate-800"
|
||||
>
|
||||
<div class="w-[65%] flex-shrink-0 flex-grow-0 max-w-[65%]">
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center flex-row">
|
||||
<woot-avatar-uploader
|
||||
ref="imageUpload"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.LOGO.LABEL')"
|
||||
:src="logoUrl"
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div v-if="showDeleteButton" class="avatar-delete-btn">
|
||||
<woot-button
|
||||
type="button"
|
||||
color-scheme="alert"
|
||||
variant="hollow"
|
||||
size="small"
|
||||
@click="deleteAvatar"
|
||||
>
|
||||
{{ $t('PROFILE_SETTINGS.DELETE_AVATAR') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
class="mt-1 mb-0 text-xs text-slate-600 dark:text-slate-400 not-italic"
|
||||
>
|
||||
{{ $t('HELP_CENTER.PORTAL.ADD.LOGO.HELP_TEXT') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model.trim="name"
|
||||
:class="{ error: $v.name.$error }"
|
||||
:error="nameError"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.NAME.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.NAME.PLACEHOLDER')"
|
||||
:help-text="$t('HELP_CENTER.PORTAL.ADD.NAME.HELP_TEXT')"
|
||||
@blur="$v.name.$touch"
|
||||
@input="onNameChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model.trim="slug"
|
||||
:class="{ error: $v.slug.$error }"
|
||||
:error="slugError"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.SLUG.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.SLUG.PLACEHOLDER')"
|
||||
:help-text="domainHelpText"
|
||||
@blur="$v.slug.$touch"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model.trim="domain"
|
||||
:class="{ error: $v.domain.$error }"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.PLACEHOLDER')"
|
||||
:help-text="domainExampleHelpText"
|
||||
:error="domainError"
|
||||
@blur="$v.domain.$touch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<woot-button
|
||||
:is-loading="isSubmitting"
|
||||
:is-disabled="$v.$invalid"
|
||||
@click="onSubmitClick"
|
||||
>
|
||||
{{ submitButtonText }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
|
||||
<script>
|
||||
import { required, minLength } from 'vuelidate/lib/validators';
|
||||
import { isDomain } from 'shared/helpers/Validators';
|
||||
import { defineComponent, reactive, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
|
||||
import { buildPortalURL } from 'dashboard/helper/portalHelper';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { hasValidAvatarUrl } from 'dashboard/helper/URLHelper';
|
||||
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||
import { uploadFile } from 'dashboard/helper/uploadHelper';
|
||||
import { isDomain } from 'shared/helpers/Validators';
|
||||
import SettingsLayout from './Layout/SettingsLayout.vue';
|
||||
|
||||
const { EXAMPLE_URL } = wootConstants;
|
||||
const MAXIMUM_FILE_UPLOAD_SIZE = 4; // in MB
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
portal: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
submitButtonText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
slug: '',
|
||||
domain: '',
|
||||
alertMessage: '',
|
||||
const { t } = useI18n();
|
||||
|
||||
// Logouploader keys
|
||||
avatarBlobId: '',
|
||||
logoUrl: '',
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
name: {
|
||||
required,
|
||||
minLength: minLength(2),
|
||||
},
|
||||
slug: {
|
||||
required,
|
||||
},
|
||||
domain: {
|
||||
isDomain,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
nameError() {
|
||||
if (this.$v.name.$error) {
|
||||
return this.$t('HELP_CENTER.CATEGORY.ADD.NAME.ERROR');
|
||||
}
|
||||
return '';
|
||||
},
|
||||
slugError() {
|
||||
if (this.$v.slug.$error) {
|
||||
return this.$t('HELP_CENTER.CATEGORY.ADD.SLUG.ERROR');
|
||||
}
|
||||
return '';
|
||||
},
|
||||
domainError() {
|
||||
if (this.$v.domain.$error) {
|
||||
return this.$t('HELP_CENTER.PORTAL.ADD.DOMAIN.ERROR');
|
||||
}
|
||||
return '';
|
||||
},
|
||||
domainHelpText() {
|
||||
return buildPortalURL(this.slug);
|
||||
},
|
||||
domainExampleHelpText() {
|
||||
return this.$t('HELP_CENTER.PORTAL.ADD.DOMAIN.HELP_TEXT', {
|
||||
exampleURL: EXAMPLE_URL,
|
||||
});
|
||||
},
|
||||
showDeleteButton() {
|
||||
return hasValidAvatarUrl(this.logoUrl);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const portal = this.portal || {};
|
||||
this.name = portal.name || '';
|
||||
this.slug = portal.slug || '';
|
||||
this.domain = portal.custom_domain || '';
|
||||
this.alertMessage = '';
|
||||
if (portal.logo) {
|
||||
const {
|
||||
logo: { file_url: logoURL, blob_id: blobId },
|
||||
} = portal;
|
||||
this.logoUrl = logoURL;
|
||||
this.avatarBlobId = blobId;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onNameChange() {
|
||||
this.slug = convertToCategorySlug(this.name);
|
||||
},
|
||||
onSubmitClick() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
defineComponent({
|
||||
name: 'PortalSettingsBasicForm',
|
||||
});
|
||||
|
||||
const portal = {
|
||||
name: this.name,
|
||||
slug: this.slug,
|
||||
custom_domain: this.domain,
|
||||
blob_id: this.avatarBlobId || null,
|
||||
};
|
||||
this.$emit('submit', portal);
|
||||
},
|
||||
async deleteAvatar() {
|
||||
this.logoUrl = '';
|
||||
this.avatarBlobId = '';
|
||||
this.$emit('delete-logo');
|
||||
},
|
||||
onFileChange({ file }) {
|
||||
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
|
||||
this.uploadLogoToStorage(file);
|
||||
} else {
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_SIZE_ERROR',
|
||||
{
|
||||
size: MAXIMUM_FILE_UPLOAD_SIZE,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
const props = defineProps({
|
||||
portal: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
submitButtonText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
this.$refs.imageUpload.value = '';
|
||||
},
|
||||
async uploadLogoToStorage(file) {
|
||||
try {
|
||||
const { fileUrl, blobId } = await uploadFile(file);
|
||||
if (fileUrl) {
|
||||
this.logoUrl = fileUrl;
|
||||
this.avatarBlobId = blobId;
|
||||
}
|
||||
} catch (error) {
|
||||
this.showAlert(
|
||||
this.$t('HELP_CENTER.PORTAL.ADD.LOGO.IMAGE_DELETE_ERROR')
|
||||
);
|
||||
}
|
||||
},
|
||||
const state = reactive({
|
||||
name: '',
|
||||
slug: '',
|
||||
domain: '',
|
||||
logoUrl: '',
|
||||
avatarBlobId: '',
|
||||
});
|
||||
|
||||
const rules = {
|
||||
name: {
|
||||
required,
|
||||
minLength: minLength(2),
|
||||
},
|
||||
slug: {
|
||||
required,
|
||||
},
|
||||
domain: {
|
||||
isDomain,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
input {
|
||||
@apply mb-1;
|
||||
|
||||
const v$ = useVuelidate(rules, state);
|
||||
|
||||
const nameError = computed(() => {
|
||||
if (v$.value.name.$error) {
|
||||
return t('HELP_CENTER.CATEGORY.ADD.NAME.ERROR');
|
||||
}
|
||||
.help-text {
|
||||
@apply mb-0;
|
||||
return '';
|
||||
});
|
||||
|
||||
const slugError = computed(() => {
|
||||
if (v$.value.slug.$error) {
|
||||
return t('HELP_CENTER.CATEGORY.ADD.SLUG.ERROR');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const domainError = computed(() => {
|
||||
if (v$.value.domain.$error) {
|
||||
return t('HELP_CENTER.PORTAL.ADD.DOMAIN.ERROR');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const domainHelpText = computed(() => {
|
||||
return buildPortalURL(state.slug);
|
||||
});
|
||||
|
||||
const domainExampleHelpText = computed(() => {
|
||||
return t('HELP_CENTER.PORTAL.ADD.DOMAIN.HELP_TEXT', {
|
||||
exampleURL: EXAMPLE_URL,
|
||||
});
|
||||
});
|
||||
|
||||
const showDeleteButton = computed(() => {
|
||||
return hasValidAvatarUrl(state.logoUrl);
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit', 'delete-logo']);
|
||||
|
||||
onMounted(() => {
|
||||
const portal = props.portal || {};
|
||||
state.name = portal.name || '';
|
||||
state.slug = portal.slug || '';
|
||||
state.domain = portal.custom_domain || '';
|
||||
|
||||
if (portal.logo) {
|
||||
const {
|
||||
logo: { file_url: logoURL, blob_id: blobId },
|
||||
} = portal;
|
||||
state.logoUrl = logoURL;
|
||||
state.avatarBlobId = blobId;
|
||||
}
|
||||
});
|
||||
|
||||
function onNameChange() {
|
||||
state.slug = convertToCategorySlug(state.name);
|
||||
}
|
||||
|
||||
function onSubmitClick() {
|
||||
v$.value.$touch();
|
||||
if (v$.value.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const portal = {
|
||||
name: state.name,
|
||||
slug: state.slug,
|
||||
custom_domain: state.domain,
|
||||
blob_id: state.avatarBlobId || null,
|
||||
};
|
||||
emit('submit', portal);
|
||||
}
|
||||
async function deleteAvatar() {
|
||||
state.logoUrl = '';
|
||||
state.avatarBlobId = '';
|
||||
emit('delete-logo');
|
||||
}
|
||||
|
||||
async function uploadLogoToStorage(file) {
|
||||
try {
|
||||
const { fileUrl, blobId } = await uploadFile(file);
|
||||
if (fileUrl) {
|
||||
state.logoUrl = fileUrl;
|
||||
state.avatarBlobId = blobId;
|
||||
}
|
||||
} catch (error) {
|
||||
useAlert(t('HELP_CENTER.PORTAL.ADD.LOGO.IMAGE_UPLOAD_ERROR'));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
function onFileChange({ file }) {
|
||||
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
|
||||
uploadLogoToStorage(file);
|
||||
} else {
|
||||
const errorKey =
|
||||
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_SIZE_ERROR';
|
||||
useAlert(t(errorKey, { size: MAXIMUM_FILE_UPLOAD_SIZE }));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SettingsLayout
|
||||
:title="
|
||||
$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.BASIC_SETTINGS_PAGE.TITLE')
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<div class="flex flex-row items-center">
|
||||
<woot-avatar-uploader
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.LOGO.LABEL')"
|
||||
:src="state.logoUrl"
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div v-if="showDeleteButton" class="avatar-delete-btn">
|
||||
<woot-button
|
||||
type="button"
|
||||
color-scheme="alert"
|
||||
variant="hollow"
|
||||
size="small"
|
||||
@click="deleteAvatar"
|
||||
>
|
||||
{{ $t('PROFILE_SETTINGS.DELETE_AVATAR') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
class="mt-1 mb-0 text-xs not-italic text-slate-600 dark:text-slate-400"
|
||||
>
|
||||
{{ $t('HELP_CENTER.PORTAL.ADD.LOGO.HELP_TEXT') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model="state.name"
|
||||
:class="{ error: v$.name.$error }"
|
||||
:error="nameError"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.NAME.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.NAME.PLACEHOLDER')"
|
||||
:help-text="$t('HELP_CENTER.PORTAL.ADD.NAME.HELP_TEXT')"
|
||||
@blur="v$.name.$touch"
|
||||
@input="onNameChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model="state.slug"
|
||||
:class="{ error: v$.slug.$error }"
|
||||
:error="slugError"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.SLUG.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.SLUG.PLACEHOLDER')"
|
||||
:help-text="domainHelpText"
|
||||
@blur="v$.slug.$touch"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model="state.domain"
|
||||
:class="{ error: v$.domain.$error }"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.PLACEHOLDER')"
|
||||
:help-text="domainExampleHelpText"
|
||||
:error="domainError"
|
||||
@blur="v$.domain.$touch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer-right>
|
||||
<woot-button
|
||||
:is-loading="isSubmitting"
|
||||
:is-disabled="v$.$invalid"
|
||||
@click="onSubmitClick"
|
||||
>
|
||||
{{ submitButtonText }}
|
||||
</woot-button>
|
||||
</template>
|
||||
</SettingsLayout>
|
||||
</template>
|
||||
|
||||
+133
-143
@@ -1,68 +1,140 @@
|
||||
<script setup>
|
||||
import { getRandomColor } from 'dashboard/helper/labelColor';
|
||||
import SettingsLayout from './Layout/SettingsLayout.vue';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
const { EXAMPLE_URL } = wootConstants;
|
||||
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { url } from '@vuelidate/validators';
|
||||
|
||||
import { defineComponent, reactive, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
|
||||
defineComponent({
|
||||
name: 'PortalSettingsCustomizationForm',
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
portal: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit']);
|
||||
|
||||
const state = reactive({
|
||||
color: getRandomColor(),
|
||||
pageTitle: '',
|
||||
headerText: '',
|
||||
homePageLink: '',
|
||||
});
|
||||
|
||||
const rules = {
|
||||
homePageLink: { url },
|
||||
};
|
||||
|
||||
const homepageExampleHelpText = computed(() => {
|
||||
return t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.HELP_TEXT', {
|
||||
exampleURL: EXAMPLE_URL,
|
||||
});
|
||||
});
|
||||
|
||||
const v$ = useVuelidate(rules, state);
|
||||
|
||||
function updateDataFromStore() {
|
||||
const { portal } = props;
|
||||
if (portal) {
|
||||
state.color = portal.color || getRandomColor();
|
||||
state.pageTitle = portal.page_title || '';
|
||||
state.headerText = portal.header_text || '';
|
||||
state.homePageLink = portal.homepage_link || '';
|
||||
}
|
||||
}
|
||||
|
||||
function onSubmitClick() {
|
||||
v$.value.$touch();
|
||||
if (v$.value.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const portal = {
|
||||
id: props.portal.id,
|
||||
slug: props.portal.slug,
|
||||
color: state.color,
|
||||
page_title: state.pageTitle,
|
||||
header_text: state.headerText,
|
||||
homepage_link: state.homePageLink,
|
||||
};
|
||||
|
||||
emit('submit', portal);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateDataFromStore();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="pt-3 bg-white dark:bg-slate-900 h-full border border-solid border-transparent px-6 pb-6 dark:border-transparent w-full max-w-full md:w-3/4 md:max-w-[75%] flex-shrink-0 flex-grow-0"
|
||||
<SettingsLayout
|
||||
:title="
|
||||
$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.CUSTOMIZATION_PAGE.TITLE')
|
||||
"
|
||||
>
|
||||
<div class="w-full">
|
||||
<h3 class="text-lg text-black-900 dark:text-slate-200">
|
||||
{{
|
||||
$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.CUSTOMIZATION_PAGE.TITLE')
|
||||
}}
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
class="my-4 mx-0 border-b border-solid border-slate-25 dark:border-slate-800"
|
||||
>
|
||||
<div class="w-[65%] flex-shrink-0 flex-grow-0 max-w-[65%]">
|
||||
<div class="mb-4">
|
||||
<label>
|
||||
{{ $t('HELP_CENTER.PORTAL.ADD.THEME_COLOR.LABEL') }}
|
||||
</label>
|
||||
<woot-color-picker v-model="color" />
|
||||
<p
|
||||
class="mt-1 mb-0 text-xs text-slate-600 dark:text-slate-400 not-italic"
|
||||
>
|
||||
{{ $t('HELP_CENTER.PORTAL.ADD.THEME_COLOR.HELP_TEXT') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model.trim="pageTitle"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.PLACEHOLDER')"
|
||||
:help-text="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.HELP_TEXT')"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model.trim="headerText"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.PLACEHOLDER')"
|
||||
:help-text="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.HELP_TEXT')"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model.trim="homePageLink"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.LABEL')"
|
||||
:placeholder="
|
||||
$t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.PLACEHOLDER')
|
||||
"
|
||||
:help-text="homepageExampleHelpText"
|
||||
:error="
|
||||
$v.homePageLink.$error
|
||||
? $t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.ERROR')
|
||||
: ''
|
||||
"
|
||||
:class="{ error: $v.homePageLink.$error }"
|
||||
@blur="$v.homePageLink.$touch"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-grow-0 flex-shrink-0">
|
||||
<div class="mb-4">
|
||||
<label>
|
||||
{{ $t('HELP_CENTER.PORTAL.ADD.THEME_COLOR.LABEL') }}
|
||||
</label>
|
||||
<woot-color-picker v-model="state.color" />
|
||||
<p
|
||||
class="mt-1 mb-0 text-xs not-italic text-slate-600 dark:text-slate-400"
|
||||
>
|
||||
{{ $t('HELP_CENTER.PORTAL.ADD.THEME_COLOR.HELP_TEXT') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model="state.pageTitle"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.PLACEHOLDER')"
|
||||
:help-text="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.HELP_TEXT')"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model="state.headerText"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.PLACEHOLDER')"
|
||||
:help-text="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.HELP_TEXT')"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<woot-input
|
||||
v-model="state.homePageLink"
|
||||
:label="$t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.LABEL')"
|
||||
:placeholder="$t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.PLACEHOLDER')"
|
||||
:help-text="homepageExampleHelpText"
|
||||
:error="
|
||||
v$.homePageLink.$error
|
||||
? $t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.ERROR')
|
||||
: ''
|
||||
"
|
||||
:class="{ error: v$.homePageLink.$error }"
|
||||
@blur="v$.homePageLink.$touch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<template #footer-right>
|
||||
<woot-button
|
||||
:is-loading="isSubmitting"
|
||||
:is-disabled="$v.$invalid"
|
||||
:is-disabled="v$.$invalid"
|
||||
@click="onSubmitClick"
|
||||
>
|
||||
{{
|
||||
@@ -71,94 +143,12 @@
|
||||
)
|
||||
}}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</SettingsLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { url } from 'vuelidate/lib/validators';
|
||||
import { getRandomColor } from 'dashboard/helper/labelColor';
|
||||
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
|
||||
const { EXAMPLE_URL } = wootConstants;
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
portal: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
color: '#000',
|
||||
pageTitle: '',
|
||||
headerText: '',
|
||||
homePageLink: '',
|
||||
alertMessage: '',
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
homePageLink: {
|
||||
url,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
homepageExampleHelpText() {
|
||||
return this.$t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.HELP_TEXT', {
|
||||
exampleURL: EXAMPLE_URL,
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.color = getRandomColor();
|
||||
this.updateDataFromStore();
|
||||
},
|
||||
methods: {
|
||||
updateDataFromStore() {
|
||||
const { portal } = this;
|
||||
if (portal) {
|
||||
this.color = portal.color || getRandomColor();
|
||||
this.pageTitle = portal.page_title || '';
|
||||
this.headerText = portal.header_text || '';
|
||||
this.homePageLink = portal.homepage_link || '';
|
||||
this.alertMessage = '';
|
||||
}
|
||||
},
|
||||
onSubmitClick() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
const portal = {
|
||||
id: this.portal.id,
|
||||
slug: this.portal.slug,
|
||||
color: this.color,
|
||||
page_title: this.pageTitle,
|
||||
header_text: this.headerText,
|
||||
homepage_link: this.homePageLink,
|
||||
};
|
||||
this.$emit('submit', portal);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
input {
|
||||
@apply mb-1;
|
||||
}
|
||||
.help-text {
|
||||
@apply mb-0;
|
||||
}
|
||||
.colorpicker--selected {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
+105
-121
@@ -1,15 +1,115 @@
|
||||
<script setup>
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
|
||||
import { defineComponent, ref, computed } from 'vue';
|
||||
|
||||
import CategoryListItem from './CategoryListItem.vue';
|
||||
import AddCategory from './AddCategory.vue';
|
||||
import EditCategory from './EditCategory.vue';
|
||||
|
||||
defineComponent({
|
||||
name: 'ListAllCategories',
|
||||
});
|
||||
|
||||
const selectedCategory = ref({});
|
||||
const currentLocaleCode = ref('en');
|
||||
const showEditCategoryModal = ref(false);
|
||||
const showAddCategoryModal = ref(false);
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const track = useTrack();
|
||||
const { t } = useI18n();
|
||||
const currentPortalSlug = computed(() => {
|
||||
return route.params.portalSlug;
|
||||
});
|
||||
|
||||
const categoriesByLocaleCode = computed(() => {
|
||||
return getters['categories/categoriesByLocaleCode'].value(
|
||||
currentLocaleCode.value
|
||||
);
|
||||
});
|
||||
|
||||
const currentPortal = computed(() => {
|
||||
const slug = currentPortalSlug.value;
|
||||
if (slug) return getters['portals/portalBySlug'].value(slug);
|
||||
|
||||
return getters['portals/allPortals'].value[0];
|
||||
});
|
||||
const currentPortalName = computed(() => {
|
||||
return currentPortal.value ? currentPortal.value.name : '';
|
||||
});
|
||||
const allLocales = computed(() => {
|
||||
return currentPortal.value ? currentPortal.value.config.allowed_locales : [];
|
||||
});
|
||||
|
||||
const allowedLocaleCodes = computed(() => {
|
||||
return allLocales.value.map(locale => locale.code);
|
||||
});
|
||||
|
||||
function openAddCategoryModal() {
|
||||
showAddCategoryModal.value = true;
|
||||
}
|
||||
function openEditCategoryModal(category) {
|
||||
selectedCategory.value = category;
|
||||
showEditCategoryModal.value = true;
|
||||
}
|
||||
function closeAddCategoryModal() {
|
||||
showAddCategoryModal.value = false;
|
||||
}
|
||||
function closeEditCategoryModal() {
|
||||
showEditCategoryModal.value = false;
|
||||
}
|
||||
async function fetchCategoriesByPortalSlugAndLocale(localeCode) {
|
||||
await store.dispatch('categories/index', {
|
||||
portalSlug: currentPortalSlug.value,
|
||||
locale: localeCode,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteCategory(category) {
|
||||
let alertMessage = '';
|
||||
try {
|
||||
await store.dispatch('categories/delete', {
|
||||
portalSlug: currentPortalSlug.value,
|
||||
categoryId: category.id,
|
||||
});
|
||||
alertMessage = t('HELP_CENTER.CATEGORY.DELETE.API.SUCCESS_MESSAGE');
|
||||
track(PORTALS_EVENTS.DELETE_CATEGORY, {
|
||||
hasArticles: category?.meta?.articles_count !== 0,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage = error?.message;
|
||||
alertMessage =
|
||||
errorMessage || t('HELP_CENTER.CATEGORY.DELETE.API.ERROR_MESSAGE');
|
||||
} finally {
|
||||
useAlert(alertMessage);
|
||||
}
|
||||
}
|
||||
function changeCurrentCategory(event) {
|
||||
const localeCode = event.target.value;
|
||||
currentLocaleCode.value = localeCode;
|
||||
fetchCategoriesByPortalSlugAndLocale(localeCode);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full pl-4">
|
||||
<header class="flex justify-between items-center mb-4">
|
||||
<div class="w-full max-w-6xl">
|
||||
<header class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center w-full gap-3">
|
||||
<label
|
||||
class="font-normal mb-0 text-base text-slate-800 dark:text-slate-100"
|
||||
class="mb-0 text-base font-normal text-slate-800 dark:text-slate-100"
|
||||
>
|
||||
{{ $t('HELP_CENTER.PORTAL.EDIT.CATEGORIES.TITLE') }}
|
||||
</label>
|
||||
<select
|
||||
:value="currentLocaleCode"
|
||||
class="w-[15%] select-locale"
|
||||
class="w-[15%] h-8 mb-0 py-0.5"
|
||||
@change="changeCurrentCategory"
|
||||
>
|
||||
<option
|
||||
@@ -21,7 +121,7 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-none items-center">
|
||||
<div class="items-center flex-none">
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
@@ -58,119 +158,3 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import portalMixin from '../../mixins/portalMixin';
|
||||
|
||||
import CategoryListItem from './CategoryListItem.vue';
|
||||
import AddCategory from './AddCategory.vue';
|
||||
import EditCategory from './EditCategory.vue';
|
||||
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CategoryListItem,
|
||||
AddCategory,
|
||||
EditCategory,
|
||||
},
|
||||
mixins: [alertMixin, portalMixin],
|
||||
data() {
|
||||
return {
|
||||
selectedCategory: {},
|
||||
selectedLocaleCode: '',
|
||||
currentLocaleCode: 'en',
|
||||
showEditCategoryModal: false,
|
||||
showAddCategoryModal: false,
|
||||
alertMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
portals: 'portals/allPortals',
|
||||
meta: 'portals/getMeta',
|
||||
isFetching: 'portals/isFetchingPortals',
|
||||
}),
|
||||
currentPortalSlug() {
|
||||
return this.$route.params.portalSlug;
|
||||
},
|
||||
categoriesByLocaleCode() {
|
||||
return this.$store.getters['categories/categoriesByLocaleCode'](
|
||||
this.currentLocaleCode
|
||||
);
|
||||
},
|
||||
currentPortal() {
|
||||
const slug = this.currentPortalSlug;
|
||||
if (slug) return this.$store.getters['portals/portalBySlug'](slug);
|
||||
|
||||
return this.$store.getters['portals/allPortals'][0];
|
||||
},
|
||||
currentPortalName() {
|
||||
return this.currentPortal ? this.currentPortal.name : '';
|
||||
},
|
||||
currentPortalLocale() {
|
||||
return this.currentPortal ? this.currentPortal?.meta?.default_locale : '';
|
||||
},
|
||||
allLocales() {
|
||||
return this.currentPortal
|
||||
? this.currentPortal.config.allowed_locales
|
||||
: [];
|
||||
},
|
||||
allowedLocaleCodes() {
|
||||
return this.allLocales.map(locale => locale.code);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openAddCategoryModal() {
|
||||
this.showAddCategoryModal = true;
|
||||
},
|
||||
openEditCategoryModal(category) {
|
||||
this.selectedCategory = category;
|
||||
this.showEditCategoryModal = true;
|
||||
},
|
||||
closeAddCategoryModal() {
|
||||
this.showAddCategoryModal = false;
|
||||
},
|
||||
closeEditCategoryModal() {
|
||||
this.showEditCategoryModal = false;
|
||||
},
|
||||
async fetchCategoriesByPortalSlugAndLocale(localeCode) {
|
||||
await this.$store.dispatch('categories/index', {
|
||||
portalSlug: this.currentPortalSlug,
|
||||
locale: localeCode,
|
||||
});
|
||||
},
|
||||
async deleteCategory(category) {
|
||||
try {
|
||||
await this.$store.dispatch('categories/delete', {
|
||||
portalSlug: this.currentPortalSlug,
|
||||
categoryId: category.id,
|
||||
});
|
||||
this.alertMessage = this.$t(
|
||||
'HELP_CENTER.CATEGORY.DELETE.API.SUCCESS_MESSAGE'
|
||||
);
|
||||
this.$track(PORTALS_EVENTS.DELETE_CATEGORY, {
|
||||
hasArticles: category?.meta?.articles_count !== 0,
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage = error?.message;
|
||||
this.alertMessage =
|
||||
errorMessage ||
|
||||
this.$t('HELP_CENTER.CATEGORY.DELETE.API.ERROR_MESSAGE');
|
||||
} finally {
|
||||
this.showAlert(this.alertMessage);
|
||||
}
|
||||
},
|
||||
changeCurrentCategory(event) {
|
||||
const localeCode = event.target.value;
|
||||
this.currentLocaleCode = localeCode;
|
||||
this.fetchCategoriesByPortalSlugAndLocale(localeCode);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.select-locale {
|
||||
@apply h-8 mb-0 py-0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
-3
@@ -36,13 +36,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
const EmojiInput = () => import('shared/components/emoji/EmojiInput');
|
||||
|
||||
export default {
|
||||
components: { EmojiInput },
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/>
|
||||
</woot-tabs>
|
||||
</setting-intro-banner>
|
||||
<div class="p-4 max-w-full my-auto flex flex-wrap">
|
||||
<div class="flex flex-wrap max-w-full px-8 py-4 my-auto">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+72
-77
@@ -1,3 +1,75 @@
|
||||
<script setup>
|
||||
import PortalSettingsBasicForm from 'dashboard/routes/dashboard/helpcenter/components/PortalSettingsBasicForm.vue';
|
||||
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute, useRouter } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineComponent, computed, ref, onMounted } from 'vue';
|
||||
|
||||
defineComponent({ name: 'EditPortalBasic' });
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const uiFlags = getters['portals/uiFlagsIn'];
|
||||
|
||||
const lastPortalSlug = ref(null);
|
||||
|
||||
const currentPortalSlug = computed(() => {
|
||||
return route.params.portalSlug;
|
||||
});
|
||||
|
||||
const currentPortal = computed(() => {
|
||||
const slug = route.params.portalSlug;
|
||||
return getters['portals/portalBySlug'].value(slug);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
lastPortalSlug.value = currentPortalSlug.value;
|
||||
});
|
||||
|
||||
async function updatePortalSettings(portalObj) {
|
||||
let alertMessage = '';
|
||||
|
||||
try {
|
||||
const portalSlug = lastPortalSlug.value;
|
||||
await store.dispatch('portals/update', { ...portalObj, portalSlug });
|
||||
|
||||
alertMessage = t('HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_UPDATE');
|
||||
|
||||
if (lastPortalSlug.value !== portalObj.slug) {
|
||||
await store.dispatch('portals/index');
|
||||
router.replace({
|
||||
name: route.name,
|
||||
params: { portalSlug: portalObj.slug },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
alertMessage =
|
||||
error?.message ||
|
||||
t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_UPDATE');
|
||||
} finally {
|
||||
useAlert(alertMessage);
|
||||
}
|
||||
}
|
||||
async function deleteLogo() {
|
||||
try {
|
||||
const portalSlug = lastPortalSlug.value;
|
||||
await store.dispatch('portals/deleteLogo', {
|
||||
portalSlug,
|
||||
});
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error?.message || t('HELP_CENTER.PORTAL.ADD.LOGO.IMAGE_DELETE_ERROR')
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<portal-settings-basic-form
|
||||
v-if="currentPortal"
|
||||
@@ -10,80 +82,3 @@
|
||||
@delete-logo="deleteLogo"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
import PortalSettingsBasicForm from 'dashboard/routes/dashboard/helpcenter/components/PortalSettingsBasicForm.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PortalSettingsBasicForm,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
lastPortalSlug: undefined,
|
||||
alertMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'portals/uiFlagsIn',
|
||||
}),
|
||||
currentPortalSlug() {
|
||||
return this.$route.params.portalSlug;
|
||||
},
|
||||
currentPortal() {
|
||||
return this.$store.getters['portals/portalBySlug'](
|
||||
this.currentPortalSlug
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.lastPortalSlug = this.currentPortalSlug;
|
||||
},
|
||||
methods: {
|
||||
async updatePortalSettings(portalObj) {
|
||||
try {
|
||||
const portalSlug = this.lastPortalSlug;
|
||||
await this.$store.dispatch('portals/update', {
|
||||
...portalObj,
|
||||
portalSlug,
|
||||
});
|
||||
this.alertMessage = this.$t(
|
||||
'HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_UPDATE'
|
||||
);
|
||||
|
||||
if (this.lastPortalSlug !== portalObj.slug) {
|
||||
await this.$store.dispatch('portals/index');
|
||||
this.$router.replace({
|
||||
name: this.$route.name,
|
||||
params: { portalSlug: portalObj.slug },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.alertMessage =
|
||||
error?.message ||
|
||||
this.$t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_UPDATE');
|
||||
} finally {
|
||||
this.showAlert(this.alertMessage);
|
||||
}
|
||||
},
|
||||
async deleteLogo() {
|
||||
try {
|
||||
const portalSlug = this.lastPortalSlug;
|
||||
await this.$store.dispatch('portals/deleteLogo', {
|
||||
portalSlug,
|
||||
});
|
||||
} catch (error) {
|
||||
this.alertMessage =
|
||||
error?.message ||
|
||||
this.$t('HELP_CENTER.PORTAL.ADD.LOGO.IMAGE_DELETE_ERROR');
|
||||
this.showAlert(this.alertMessage);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
+45
-48
@@ -1,3 +1,48 @@
|
||||
<script setup>
|
||||
import PortalSettingsCustomizationForm from 'dashboard/routes/dashboard/helpcenter/components/PortalSettingsCustomizationForm.vue';
|
||||
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
|
||||
defineComponent({
|
||||
name: 'EditPortalCustomization',
|
||||
});
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const uiFlags = getters['portals/uiFlagsIn'];
|
||||
|
||||
const currentPortal = computed(() => {
|
||||
const slug = route.params.portalSlug;
|
||||
return getters['portals/portalBySlug'].value(slug);
|
||||
});
|
||||
|
||||
async function updatePortalSettings(portalObj) {
|
||||
const portalSlug = route.params.portalSlug;
|
||||
let alertMessage = '';
|
||||
try {
|
||||
await store.dispatch('portals/update', {
|
||||
...portalObj,
|
||||
portalSlug,
|
||||
});
|
||||
|
||||
alertMessage = t('HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_UPDATE');
|
||||
} catch (error) {
|
||||
alertMessage =
|
||||
error?.message ||
|
||||
t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_UPDATE');
|
||||
} finally {
|
||||
useAlert(alertMessage);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<portal-settings-customization-form
|
||||
v-if="currentPortal"
|
||||
@@ -9,51 +54,3 @@
|
||||
@submit="updatePortalSettings"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import PortalSettingsCustomizationForm from 'dashboard/routes/dashboard/helpcenter/components/PortalSettingsCustomizationForm.vue';
|
||||
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PortalSettingsCustomizationForm,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
alertMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'portals/uiFlagsIn',
|
||||
}),
|
||||
currentPortal() {
|
||||
const slug = this.$route.params.portalSlug;
|
||||
return this.$store.getters['portals/portalBySlug'](slug);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async updatePortalSettings(portalObj) {
|
||||
const portalSlug = this.$route.params.portalSlug;
|
||||
try {
|
||||
await this.$store.dispatch('portals/update', {
|
||||
...portalObj,
|
||||
portalSlug,
|
||||
});
|
||||
this.alertMessage = this.$t(
|
||||
'HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_UPDATE'
|
||||
);
|
||||
} catch (error) {
|
||||
this.alertMessage =
|
||||
error?.message ||
|
||||
this.$t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_UPDATE');
|
||||
} finally {
|
||||
this.showAlert(this.alertMessage);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
+121
-134
@@ -1,6 +1,117 @@
|
||||
<script setup>
|
||||
import LocaleItemTable from 'dashboard/routes/dashboard/helpcenter/components/PortalListItemTable.vue';
|
||||
import AddLocale from 'dashboard/routes/dashboard/helpcenter/components/AddLocale.vue';
|
||||
import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineComponent, ref, onBeforeMount, computed } from 'vue';
|
||||
|
||||
defineComponent({
|
||||
name: 'EditPortalLocales',
|
||||
});
|
||||
|
||||
const isAddLocaleModalOpen = ref(false);
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const track = useTrack();
|
||||
const { t } = useI18n();
|
||||
|
||||
const currentPortalSlug = computed(() => {
|
||||
return route.params.portalSlug;
|
||||
});
|
||||
const currentPortal = computed(() => {
|
||||
const slug = currentPortalSlug.value;
|
||||
if (slug) return getters['portals/portalBySlug'].value(slug);
|
||||
|
||||
return getters['portals/allPortals'].value[0];
|
||||
});
|
||||
const locales = computed(() => {
|
||||
return currentPortal.value?.config.allowed_locales;
|
||||
});
|
||||
const allowedLocales = computed(() => {
|
||||
return Object.keys(locales.value).map(key => {
|
||||
return this.locales.value[key].code;
|
||||
});
|
||||
});
|
||||
|
||||
async function fetchPortals() {
|
||||
await store.dispatch('portals/index');
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
fetchPortals();
|
||||
});
|
||||
|
||||
async function updatePortalLocales({
|
||||
newAllowedLocales,
|
||||
defaultLocale,
|
||||
messageKey,
|
||||
}) {
|
||||
let alertMessage = '';
|
||||
try {
|
||||
await store.dispatch('portals/update', {
|
||||
portalSlug: currentPortalSlug.value,
|
||||
config: {
|
||||
default_locale: defaultLocale,
|
||||
allowed_locales: newAllowedLocales,
|
||||
},
|
||||
});
|
||||
alertMessage = t(`HELP_CENTER.PORTAL.${messageKey}.API.SUCCESS_MESSAGE`);
|
||||
} catch (error) {
|
||||
alertMessage =
|
||||
error?.message || t(`HELP_CENTER.PORTAL.${messageKey}.API.ERROR_MESSAGE`);
|
||||
} finally {
|
||||
useAlert(alertMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function changeDefaultLocale({ localeCode }) {
|
||||
updatePortalLocales({
|
||||
allowedLocales: allowedLocales.value,
|
||||
defaultLocale: localeCode,
|
||||
messageKey: 'CHANGE_DEFAULT_LOCALE',
|
||||
});
|
||||
|
||||
track(PORTALS_EVENTS.SET_DEFAULT_LOCALE, {
|
||||
newLocale: localeCode,
|
||||
from: route.name,
|
||||
});
|
||||
}
|
||||
function deletePortalLocale({ localeCode }) {
|
||||
const updatedLocales = allowedLocales.value.filter(
|
||||
code => code !== localeCode
|
||||
);
|
||||
|
||||
const defaultLocale = currentPortal.value?.meta.default_locale;
|
||||
|
||||
updatePortalLocales({
|
||||
allowedLocales: updatedLocales,
|
||||
defaultLocale,
|
||||
messageKey: 'DELETE_LOCALE',
|
||||
});
|
||||
|
||||
track(PORTALS_EVENTS.DELETE_LOCALE, {
|
||||
deletedLocale: localeCode,
|
||||
from: route.name,
|
||||
});
|
||||
}
|
||||
|
||||
function closeAddLocaleModal() {
|
||||
isAddLocaleModalOpen.value = false;
|
||||
}
|
||||
function addLocale() {
|
||||
isAddLocaleModalOpen.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="portal-locales">
|
||||
<div class="button-container">
|
||||
<div class="w-full h-full max-w-6xl space-y-4 bg-white dark:bg-slate-900">
|
||||
<div class="flex justify-end">
|
||||
<woot-button
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@@ -11,19 +122,18 @@
|
||||
{{ $t('HELP_CENTER.PORTAL.PORTAL_SETTINGS.LIST_ITEM.HEADER.ADD') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<div class="locale-container">
|
||||
<locale-item-table
|
||||
:locales="locales"
|
||||
:selected-locale-code="currentPortal.meta.default_locale"
|
||||
@change-default-locale="changeDefaultLocale"
|
||||
@delete="deletePortalLocale"
|
||||
/>
|
||||
</div>
|
||||
<LocaleItemTable
|
||||
v-if="currentPortal"
|
||||
:locales="locales"
|
||||
:selected-locale-code="currentPortal.meta.default_locale"
|
||||
@change-default-locale="changeDefaultLocale"
|
||||
@delete="deletePortalLocale"
|
||||
/>
|
||||
<woot-modal
|
||||
:show.sync="isAddLocaleModalOpen"
|
||||
:on-close="closeAddLocaleModal"
|
||||
>
|
||||
<add-locale
|
||||
<AddLocale
|
||||
:show="isAddLocaleModalOpen"
|
||||
:portal="currentPortal"
|
||||
@cancel="closeAddLocaleModal"
|
||||
@@ -31,126 +141,3 @@
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import LocaleItemTable from 'dashboard/routes/dashboard/helpcenter/components/PortalListItemTable.vue';
|
||||
import AddLocale from 'dashboard/routes/dashboard/helpcenter/components/AddLocale.vue';
|
||||
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
export default {
|
||||
components: {
|
||||
LocaleItemTable,
|
||||
AddLocale,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
isAddLocaleModalOpen: false,
|
||||
lastPortalSlug: undefined,
|
||||
alertMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'portals/uiFlagsIn',
|
||||
}),
|
||||
currentPortalSlug() {
|
||||
return this.$route.params.portalSlug;
|
||||
},
|
||||
currentPortal() {
|
||||
return this.$store.getters['portals/portalBySlug'](
|
||||
this.currentPortalSlug
|
||||
);
|
||||
},
|
||||
locales() {
|
||||
return this.currentPortal.config.allowed_locales;
|
||||
},
|
||||
allowedLocales() {
|
||||
return Object.keys(this.locales).map(key => {
|
||||
return this.locales[key].code;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.lastPortalSlug = this.currentPortalSlug;
|
||||
},
|
||||
methods: {
|
||||
changeDefaultLocale({ localeCode }) {
|
||||
this.updatePortalLocales({
|
||||
allowedLocales: this.allowedLocales,
|
||||
defaultLocale: localeCode,
|
||||
successMessage: this.$t(
|
||||
'HELP_CENTER.PORTAL.CHANGE_DEFAULT_LOCALE.API.SUCCESS_MESSAGE'
|
||||
),
|
||||
errorMessage: this.$t(
|
||||
'HELP_CENTER.PORTAL.CHANGE_DEFAULT_LOCALE.API.ERROR_MESSAGE'
|
||||
),
|
||||
});
|
||||
this.$track(PORTALS_EVENTS.SET_DEFAULT_LOCALE, {
|
||||
newLocale: localeCode,
|
||||
from: this.$route.name,
|
||||
});
|
||||
},
|
||||
deletePortalLocale({ localeCode }) {
|
||||
const updatedLocales = this.allowedLocales.filter(
|
||||
code => code !== localeCode
|
||||
);
|
||||
const defaultLocale = this.currentPortal.meta.default_locale;
|
||||
this.updatePortalLocales({
|
||||
allowedLocales: updatedLocales,
|
||||
defaultLocale,
|
||||
successMessage: this.$t(
|
||||
'HELP_CENTER.PORTAL.DELETE_LOCALE.API.SUCCESS_MESSAGE'
|
||||
),
|
||||
errorMessage: this.$t(
|
||||
'HELP_CENTER.PORTAL.DELETE_LOCALE.API.ERROR_MESSAGE'
|
||||
),
|
||||
});
|
||||
this.$track(PORTALS_EVENTS.DELETE_LOCALE, {
|
||||
deletedLocale: localeCode,
|
||||
from: this.$route.name,
|
||||
});
|
||||
},
|
||||
async updatePortalLocales({
|
||||
allowedLocales,
|
||||
defaultLocale,
|
||||
successMessage,
|
||||
errorMessage,
|
||||
}) {
|
||||
try {
|
||||
await this.$store.dispatch('portals/update', {
|
||||
portalSlug: this.currentPortal.slug,
|
||||
config: {
|
||||
default_locale: defaultLocale,
|
||||
allowed_locales: allowedLocales,
|
||||
},
|
||||
});
|
||||
this.alertMessage = successMessage;
|
||||
} catch (error) {
|
||||
this.alertMessage = error?.message || errorMessage;
|
||||
} finally {
|
||||
this.showAlert(this.alertMessage);
|
||||
}
|
||||
},
|
||||
closeAddLocaleModal() {
|
||||
this.isAddLocaleModalOpen = false;
|
||||
this.selectedPortal = {};
|
||||
},
|
||||
addLocale() {
|
||||
this.isAddLocaleModalOpen = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.portal-locales {
|
||||
@apply w-full bg-white dark:bg-slate-900 h-full py-0 pr-0 pl-4;
|
||||
.button-container {
|
||||
@apply flex justify-end;
|
||||
}
|
||||
.locale-container {
|
||||
@apply mt-4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex-1">
|
||||
<section class="flex-1">
|
||||
<settings-header
|
||||
button-route="new"
|
||||
:header-title="portalHeaderText"
|
||||
@@ -10,16 +10,20 @@
|
||||
:show-new-button="false"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-row overflow-auto py-4 pl-4 rtl:pl-0 rtl:pr-4 h-full bg-slate-50 dark:bg-slate-800"
|
||||
class="grid grid-cols-[20rem_1fr] w-full h-full overflow-auto rtl:pl-0 rtl:pr-4 bg-slate-50 dark:bg-slate-800 p-5"
|
||||
>
|
||||
<woot-wizard
|
||||
class="hidden md:block w-1/4"
|
||||
class="hidden md:block"
|
||||
:global-config="globalConfig"
|
||||
:items="items"
|
||||
/>
|
||||
<router-view />
|
||||
<div
|
||||
class="w-full p-5 bg-white border border-transparent border-solid rounded-md shadow-sm dark:bg-slate-900 dark:border-transparent"
|
||||
>
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
+60
-72
@@ -1,3 +1,63 @@
|
||||
<script setup>
|
||||
import PortalSettingsCustomizationForm from 'dashboard/routes/dashboard/helpcenter/components/PortalSettingsCustomizationForm.vue';
|
||||
import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute, useRouter } from 'dashboard/composables/route';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { defineComponent, onMounted, computed } from 'vue';
|
||||
|
||||
defineComponent({
|
||||
name: 'PortalCustomization',
|
||||
});
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const track = useTrack();
|
||||
const { t } = useI18n();
|
||||
|
||||
const uiFlags = getters['portals/uiFlagsIn'];
|
||||
|
||||
const currentPortal = computed(() => {
|
||||
const slug = route.params.portalSlug;
|
||||
if (slug) return getters['portals/portalBySlug'].value(slug);
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('portals/index');
|
||||
});
|
||||
|
||||
async function updatePortalSettings(portalObj) {
|
||||
const portalSlug = route.params.portalSlug;
|
||||
let alertMessage = '';
|
||||
try {
|
||||
await store.dispatch('portals/update', {
|
||||
portalSlug,
|
||||
...portalObj,
|
||||
});
|
||||
alertMessage = t('HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_UPDATE');
|
||||
|
||||
track(PORTALS_EVENTS.ONBOARD_CUSTOMIZATION, {
|
||||
hasHomePageLink: Boolean(portalObj.homepage_link),
|
||||
hasPageTitle: Boolean(portalObj.page_title),
|
||||
hasHeaderText: Boolean(portalObj.headerText),
|
||||
});
|
||||
} catch (error) {
|
||||
alertMessage =
|
||||
error?.message ||
|
||||
t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_UPDATE');
|
||||
} finally {
|
||||
useAlert(alertMessage);
|
||||
router.push({ name: 'portal_finish' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<portal-settings-customization-form
|
||||
v-if="currentPortal"
|
||||
@@ -9,75 +69,3 @@
|
||||
@submit="updatePortalSettings"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import PortalSettingsCustomizationForm from 'dashboard/routes/dashboard/helpcenter/components/PortalSettingsCustomizationForm.vue';
|
||||
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import { getRandomColor } from 'dashboard/helper/labelColor';
|
||||
import { PORTALS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PortalSettingsCustomizationForm,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
color: '#000',
|
||||
pageTitle: '',
|
||||
headerText: '',
|
||||
homePageLink: '',
|
||||
alertMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'portals/uiFlagsIn',
|
||||
portals: 'portals/allPortals',
|
||||
}),
|
||||
currentPortal() {
|
||||
const slug = this.$route.params.portalSlug;
|
||||
return this.$store.getters['portals/portalBySlug'](slug);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchPortals();
|
||||
this.color = getRandomColor();
|
||||
},
|
||||
methods: {
|
||||
fetchPortals() {
|
||||
this.$store.dispatch('portals/index');
|
||||
},
|
||||
async updatePortalSettings(portalObj) {
|
||||
const portalSlug = this.$route.params.portalSlug;
|
||||
try {
|
||||
await this.$store.dispatch('portals/update', {
|
||||
portalSlug,
|
||||
...portalObj,
|
||||
});
|
||||
this.alertMessage = this.$t(
|
||||
'HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_UPDATE'
|
||||
);
|
||||
|
||||
this.$track(PORTALS_EVENTS.ONBOARD_CUSTOMIZATION, {
|
||||
hasHomePageLink: Boolean(portalObj.homepage_link),
|
||||
hasPageTitle: Boolean(portalObj.page_title),
|
||||
hasHeaderText: Boolean(portalObj.headerText),
|
||||
});
|
||||
} catch (error) {
|
||||
this.alertMessage =
|
||||
error?.message ||
|
||||
this.$t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_UPDATE');
|
||||
} finally {
|
||||
this.showAlert(this.alertMessage);
|
||||
this.$router.push({
|
||||
name: 'portal_finish',
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
+7
-15
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="pt-3 bg-white dark:bg-slate-900 h-full border border-solid border-transparent px-6 pb-6 dark:border-transparent w-full max-w-full md:w-3/4 md:max-w-[75%] flex-shrink-0 flex-grow-0"
|
||||
class="flex-grow-0 flex-shrink-0 w-full h-full max-w-full px-6 pt-3 pb-6 bg-white border border-transparent border-solid dark:bg-slate-900 dark:border-transparent"
|
||||
>
|
||||
<empty-state
|
||||
:title="$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.FINISH_PAGE.TITLE')"
|
||||
@@ -10,7 +10,7 @@
|
||||
>
|
||||
<div class="w-full text-center">
|
||||
<router-link
|
||||
class="button success nice rounded"
|
||||
class="rounded button success nice"
|
||||
:to="{
|
||||
name: 'list_all_portals',
|
||||
}"
|
||||
@@ -22,18 +22,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
export default {
|
||||
components: {
|
||||
EmptyState,
|
||||
},
|
||||
methods: {
|
||||
changeRoute() {
|
||||
this.$router.push({
|
||||
name: 'list_all_portals',
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
import { defineComponent } from 'vue';
|
||||
defineComponent({
|
||||
name: 'PortalSettingsFinish',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import InboxOptionMenu from './InboxOptionMenu.vue';
|
||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import InboxDisplayMenu from './InboxDisplayMenu.vue';
|
||||
@@ -68,7 +67,7 @@ export default {
|
||||
InboxOptionMenu,
|
||||
InboxDisplayMenu,
|
||||
},
|
||||
mixins: [clickaway, alertMixin],
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
isContextMenuOpen: {
|
||||
type: Boolean,
|
||||
|
||||
+1
-2
@@ -115,7 +115,6 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||
import NotificationPanelList from './NotificationPanelList.vue';
|
||||
|
||||
@@ -125,7 +124,7 @@ export default {
|
||||
components: {
|
||||
NotificationPanelList,
|
||||
},
|
||||
mixins: [clickaway, rtlMixin],
|
||||
mixins: [rtlMixin],
|
||||
data() {
|
||||
return {
|
||||
pageSize: 15,
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
import campaignMixin from 'shared/mixins/campaignMixin';
|
||||
@@ -34,7 +33,7 @@ export default {
|
||||
CampaignCard,
|
||||
},
|
||||
|
||||
mixins: [clickaway, campaignMixin],
|
||||
mixins: [campaignMixin],
|
||||
|
||||
props: {
|
||||
campaigns: {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<label
|
||||
class="flex justify-between pb-1 text-sm font-medium leading-6 text-ash-900"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="flex flex-row items-start gap-2"
|
||||
>
|
||||
<CheckBox
|
||||
:is-checked="item.model"
|
||||
:value="item.value"
|
||||
@update="onChange"
|
||||
/>
|
||||
<label class="text-sm font-normal text-ash-900">
|
||||
{{ item.label }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import CheckBox from 'v3/components/Form/CheckBox.vue';
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['change']);
|
||||
const onChange = (id, value) => {
|
||||
emit('change', id, value);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div>
|
||||
<label
|
||||
class="flex justify-between pb-1 text-sm font-medium leading-6 text-ash-900"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
<div
|
||||
class="flex flex-row justify-between h-10 max-w-xl p-2 border border-solid rounded-xl border-ash-200"
|
||||
>
|
||||
<div
|
||||
v-for="option in alertEvents"
|
||||
:key="option.value"
|
||||
class="flex flex-row items-center justify-center gap-2 px-4 border-r border-ash-200 grow last:border-r-0"
|
||||
>
|
||||
<input
|
||||
:id="`radio-${option.value}`"
|
||||
v-model="selectedValue"
|
||||
class="shadow cursor-pointer grid place-items-center border-2 border-ash-200 appearance-none rounded-full w-4 h-4 checked:bg-primary-600 before:content-[''] before:bg-primary-600 before:border-4 before:rounded-full before:border-ash-25 checked:before:w-[14px] checked:before:h-[14px] checked:border checked:border-primary-600"
|
||||
type="radio"
|
||||
:value="option.value"
|
||||
/>
|
||||
<label
|
||||
:for="`radio-${option.value}`"
|
||||
class="text-sm font-medium"
|
||||
:class="
|
||||
selectedValue === option.value ? 'text-ash-900' : 'text-ash-800'
|
||||
"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
`PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.ALERT_TYPES.${option.label.toUpperCase()}`
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { ALERT_EVENTS } from './constants';
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: 'all',
|
||||
},
|
||||
});
|
||||
|
||||
const alertEvents = ALERT_EVENTS;
|
||||
|
||||
const emit = defineEmits(['update']);
|
||||
|
||||
const selectedValue = computed({
|
||||
get: () => props.value,
|
||||
set: value => {
|
||||
emit('update', value);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<form-select
|
||||
v-model="selectedValue"
|
||||
name="alertTone"
|
||||
spacing="compact"
|
||||
:value="selectedValue"
|
||||
:options="alertTones"
|
||||
:label="label"
|
||||
class="max-w-xl"
|
||||
>
|
||||
<option
|
||||
v-for="tone in alertTones"
|
||||
:key="tone.label"
|
||||
:value="tone.value"
|
||||
:selected="tone.value === selectedValue"
|
||||
>
|
||||
{{ tone.label }}
|
||||
</option>
|
||||
</form-select>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import FormSelect from 'v3/components/Form/Select.vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => ['ding', 'bell'].includes(value),
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const alertTones = computed(() => [
|
||||
{
|
||||
value: 'ding',
|
||||
label: 'Ding',
|
||||
},
|
||||
{
|
||||
value: 'bell',
|
||||
label: 'Bell',
|
||||
},
|
||||
]);
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const selectedValue = computed({
|
||||
get: () => props.value,
|
||||
set: value => {
|
||||
emit('change', value);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div id="profile-settings-notifications" class="flex flex-col gap-6">
|
||||
<audio-alert-tone
|
||||
:value="alertTone"
|
||||
:label="
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.DEFAULT_TONE.TITLE'
|
||||
)
|
||||
"
|
||||
@change="handleAudioToneChange"
|
||||
/>
|
||||
|
||||
<audio-alert-event
|
||||
:label="
|
||||
$t('PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.ALERT_TYPE.TITLE')
|
||||
"
|
||||
:value="audioAlert"
|
||||
@update="handAudioAlertChange"
|
||||
/>
|
||||
|
||||
<audio-alert-condition
|
||||
:items="audioAlertConditions"
|
||||
:label="
|
||||
$t('PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.CONDITIONS.TITLE')
|
||||
"
|
||||
@change="handleAudioAlertConditions"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import configMixin from 'shared/mixins/configMixin';
|
||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
import AudioAlertTone from './AudioAlertTone.vue';
|
||||
import AudioAlertEvent from './AudioAlertEvent.vue';
|
||||
import AudioAlertCondition from './AudioAlertCondition.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AudioAlertEvent,
|
||||
AudioAlertTone,
|
||||
AudioAlertCondition,
|
||||
},
|
||||
mixins: [alertMixin, configMixin, uiSettingsMixin],
|
||||
data() {
|
||||
return {
|
||||
audioAlert: '',
|
||||
playAudioWhenTabIsInactive: false,
|
||||
alertIfUnreadConversationExist: false,
|
||||
alertTone: 'ding',
|
||||
audioAlertConditions: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
uiSettings: 'getUISettings',
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
uiSettings(value) {
|
||||
this.notificationUISettings(value);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.notificationUISettings(this.uiSettings);
|
||||
this.$store.dispatch('userNotificationSettings/get');
|
||||
},
|
||||
methods: {
|
||||
notificationUISettings(uiSettings) {
|
||||
const {
|
||||
enable_audio_alerts: audioAlert = '',
|
||||
always_play_audio_alert: alwaysPlayAudioAlert,
|
||||
alert_if_unread_assigned_conversation_exist:
|
||||
alertIfUnreadConversationExist,
|
||||
notification_tone: alertTone,
|
||||
} = uiSettings;
|
||||
this.audioAlert = audioAlert;
|
||||
this.playAudioWhenTabIsInactive = !alwaysPlayAudioAlert;
|
||||
this.alertIfUnreadConversationExist = alertIfUnreadConversationExist;
|
||||
this.audioAlertConditions = [
|
||||
{
|
||||
id: 'audio1',
|
||||
label: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.CONDITIONS.CONDITION_ONE'
|
||||
),
|
||||
model: this.playAudioWhenTabIsInactive,
|
||||
value: 'tab_is_inactive',
|
||||
},
|
||||
{
|
||||
id: 'audio2',
|
||||
label: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.CONDITIONS.CONDITION_TWO'
|
||||
),
|
||||
model: this.alertIfUnreadConversationExist,
|
||||
value: 'conversations_are_read',
|
||||
},
|
||||
];
|
||||
this.alertTone = alertTone || 'ding';
|
||||
},
|
||||
handAudioAlertChange(value) {
|
||||
this.audioAlert = value;
|
||||
this.updateUISettings({
|
||||
enable_audio_alerts: this.audioAlert,
|
||||
});
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_SUCCESS'));
|
||||
},
|
||||
handleAudioAlertConditions(id, value) {
|
||||
if (id === 'tab_is_inactive') {
|
||||
this.updateUISettings({
|
||||
always_play_audio_alert: !value,
|
||||
});
|
||||
} else if (id === 'conversations_are_read') {
|
||||
this.updateUISettings({
|
||||
alert_if_unread_assigned_conversation_exist: value,
|
||||
});
|
||||
}
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_SUCCESS'));
|
||||
},
|
||||
handleAudioToneChange(value) {
|
||||
this.updateUISettings({ notification_tone: value });
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_SUCCESS'));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<form @submit.prevent="changePassword()">
|
||||
<div class="flex flex-col w-full gap-4">
|
||||
<woot-input
|
||||
v-model="currentPassword"
|
||||
type="password"
|
||||
:styles="inputStyles"
|
||||
:class="{ error: $v.currentPassword.$error }"
|
||||
:label="$t('PROFILE_SETTINGS.FORM.CURRENT_PASSWORD.LABEL')"
|
||||
:placeholder="$t('PROFILE_SETTINGS.FORM.CURRENT_PASSWORD.PLACEHOLDER')"
|
||||
:error="`${
|
||||
$v.currentPassword.$error
|
||||
? $t('PROFILE_SETTINGS.FORM.CURRENT_PASSWORD.ERROR')
|
||||
: ''
|
||||
}`"
|
||||
@input="$v.currentPassword.$touch"
|
||||
/>
|
||||
|
||||
<woot-input
|
||||
v-model="password"
|
||||
type="password"
|
||||
:styles="inputStyles"
|
||||
:class="{ error: $v.password.$error }"
|
||||
:label="$t('PROFILE_SETTINGS.FORM.PASSWORD.LABEL')"
|
||||
:placeholder="$t('PROFILE_SETTINGS.FORM.PASSWORD.PLACEHOLDER')"
|
||||
:error="`${
|
||||
$v.password.$error ? $t('PROFILE_SETTINGS.FORM.PASSWORD.ERROR') : ''
|
||||
}`"
|
||||
@input="$v.password.$touch"
|
||||
/>
|
||||
|
||||
<woot-input
|
||||
v-model="passwordConfirmation"
|
||||
type="password"
|
||||
:styles="inputStyles"
|
||||
:class="{ error: $v.passwordConfirmation.$error }"
|
||||
:label="$t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.LABEL')"
|
||||
:placeholder="
|
||||
$t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.PLACEHOLDER')
|
||||
"
|
||||
:error="`${
|
||||
$v.passwordConfirmation.$error
|
||||
? $t('PROFILE_SETTINGS.FORM.PASSWORD_CONFIRMATION.ERROR')
|
||||
: ''
|
||||
}`"
|
||||
@input="$v.passwordConfirmation.$touch"
|
||||
/>
|
||||
|
||||
<form-button
|
||||
type="submit"
|
||||
color-scheme="primary"
|
||||
variant="solid"
|
||||
size="large"
|
||||
:disabled="isButtonDisabled"
|
||||
>
|
||||
{{ $t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.BTN_TEXT') }}
|
||||
</form-button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { required, minLength } from 'vuelidate/lib/validators';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||
import FormButton from 'v3/components/Form/Button.vue';
|
||||
export default {
|
||||
components: {
|
||||
FormButton,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
currentPassword: '',
|
||||
password: '',
|
||||
passwordConfirmation: '',
|
||||
isPasswordChanging: false,
|
||||
errorMessage: '',
|
||||
inputStyles: {
|
||||
borderRadius: '12px',
|
||||
padding: '6px 12px',
|
||||
fontSize: '14px',
|
||||
marginBottom: '2px',
|
||||
},
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
currentPassword: {
|
||||
required,
|
||||
},
|
||||
password: {
|
||||
minLength: minLength(6),
|
||||
},
|
||||
passwordConfirmation: {
|
||||
minLength: minLength(6),
|
||||
isEqPassword(value) {
|
||||
if (value !== this.password) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isButtonDisabled() {
|
||||
return (
|
||||
!this.currentPassword ||
|
||||
!this.passwordConfirmation ||
|
||||
!this.$v.passwordConfirmation.isEqPassword
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async changePassword() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.ERROR'));
|
||||
return;
|
||||
}
|
||||
let alertMessage = this.$t('PROFILE_SETTINGS.PASSWORD_UPDATE_SUCCESS');
|
||||
try {
|
||||
await this.$store.dispatch('updateProfile', {
|
||||
password: this.password,
|
||||
password_confirmation: this.passwordConfirmation,
|
||||
current_password: this.currentPassword,
|
||||
});
|
||||
} catch (error) {
|
||||
alertMessage =
|
||||
parseAPIErrorResponse(error) ||
|
||||
this.$t('RESET_PASSWORD.API.ERROR_MESSAGE');
|
||||
} finally {
|
||||
this.showAlert(alertMessage);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-4 w-full h-fit sm:max-h-[220px] p-4 sm:max-w-[350px] rounded-md border border-solid border-ash-200"
|
||||
:class="{
|
||||
'border-primary-300 ': active,
|
||||
}"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<div class="flex flex-col gap-2 items-center w-full rounded-t-[5px]">
|
||||
<div class="flex items-center justify-between w-full gap-1">
|
||||
<div class="flex items-center text-base font-medium text-ash-900">
|
||||
{{ title }}
|
||||
</div>
|
||||
<input
|
||||
:checked="active"
|
||||
type="radio"
|
||||
:name="`hotkey-${title}`"
|
||||
class="shadow cursor-pointer grid place-items-center border-2 border-ash-200 appearance-none rounded-full w-5 h-5 checked:bg-primary-600 before:content-[''] before:bg-primary-600 before:border-4 before:rounded-full before:border-ash-25 checked:before:w-[18px] checked:before:h-[18px] checked:border checked:border-primary-600"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-ash-900 text-sm line-clamp-2 leading-[1.4] text-start">
|
||||
{{ description }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<img
|
||||
:src="lightImage"
|
||||
:alt="`Light themed image for ${title}`"
|
||||
class="block object-cover w-full dark:hidden"
|
||||
/>
|
||||
<img
|
||||
:src="darkImage"
|
||||
:alt="`Dark themed image for ${title}`"
|
||||
class="hidden object-cover w-full dark:block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
lightImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
darkImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex items-center w-full overflow-y-auto">
|
||||
<div class="flex flex-col h-full p-5 pt-16 mx-auto my-0 font-inter">
|
||||
<div class="flex flex-col gap-16 sm:max-w-[720px]">
|
||||
<div class="flex flex-col gap-16 pb-8 sm:max-w-[720px]">
|
||||
<div class="flex flex-col gap-6">
|
||||
<h2 class="mt-4 text-2xl font-medium text-ash-900">
|
||||
{{ $t('PROFILE_SETTINGS.TITLE') }}
|
||||
@@ -33,6 +33,48 @@
|
||||
@update-signature="updateSignature"
|
||||
/>
|
||||
</form-section>
|
||||
<form-section
|
||||
:header="$t('PROFILE_SETTINGS.FORM.SEND_MESSAGE.TITLE')"
|
||||
:description="$t('PROFILE_SETTINGS.FORM.SEND_MESSAGE.NOTE')"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col justify-between w-full gap-5 sm:gap-4 sm:flex-row"
|
||||
>
|
||||
<button
|
||||
v-for="hotKey in hotKeys"
|
||||
:key="hotKey.key"
|
||||
class="px-0 reset-base"
|
||||
>
|
||||
<hot-key-card
|
||||
:key="hotKey.title"
|
||||
:title="hotKey.title"
|
||||
:description="hotKey.description"
|
||||
:light-image="hotKey.lightImage"
|
||||
:dark-image="hotKey.darkImage"
|
||||
:active="isEditorHotKeyEnabled(uiSettings, hotKey.key)"
|
||||
@click="toggleHotKey(hotKey.key)"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</form-section>
|
||||
<form-section
|
||||
:header="$t('PROFILE_SETTINGS.FORM.PASSWORD_SECTION.TITLE')"
|
||||
>
|
||||
<change-password v-if="!globalConfig.disableUserProfileUpdate" />
|
||||
</form-section>
|
||||
<form-section
|
||||
:header="
|
||||
$t('PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.TITLE')
|
||||
"
|
||||
:description="
|
||||
$t('PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.NOTE')
|
||||
"
|
||||
>
|
||||
<audio-notifications />
|
||||
</form-section>
|
||||
<form-section :header="$t('PROFILE_SETTINGS.FORM.NOTIFICATIONS.TITLE')">
|
||||
<notification-preferences />
|
||||
</form-section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,6 +91,10 @@ import { clearCookiesOnLogout } from 'dashboard/store/utils/api.js';
|
||||
import UserProfilePicture from './UserProfilePicture.vue';
|
||||
import UserBasicDetails from './UserBasicDetails.vue';
|
||||
import MessageSignature from './MessageSignature.vue';
|
||||
import HotKeyCard from './HotKeyCard.vue';
|
||||
import ChangePassword from './ChangePassword.vue';
|
||||
import NotificationPreferences from './NotificationPreferences.vue';
|
||||
import AudioNotifications from './AudioNotifications.vue';
|
||||
import FormSection from 'dashboard/components/FormSection.vue';
|
||||
|
||||
export default {
|
||||
@@ -57,6 +103,10 @@ export default {
|
||||
FormSection,
|
||||
UserProfilePicture,
|
||||
UserBasicDetails,
|
||||
HotKeyCard,
|
||||
ChangePassword,
|
||||
NotificationPreferences,
|
||||
AudioNotifications,
|
||||
},
|
||||
mixins: [alertMixin, globalConfigMixin, uiSettingsMixin],
|
||||
data() {
|
||||
@@ -67,6 +117,31 @@ export default {
|
||||
displayName: '',
|
||||
email: '',
|
||||
messageSignature: '',
|
||||
hotKeys: [
|
||||
{
|
||||
key: 'enter',
|
||||
title: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.SEND_MESSAGE.CARD.ENTER_KEY.HEADING'
|
||||
),
|
||||
description: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.SEND_MESSAGE.CARD.ENTER_KEY.CONTENT'
|
||||
),
|
||||
lightImage: '/assets/images/dashboard/profile/hot-key-enter.svg',
|
||||
darkImage: '/assets/images/dashboard/profile/hot-key-enter-dark.svg',
|
||||
},
|
||||
{
|
||||
key: 'cmd_enter',
|
||||
title: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.SEND_MESSAGE.CARD.CMD_ENTER_KEY.HEADING'
|
||||
),
|
||||
description: this.$t(
|
||||
'PROFILE_SETTINGS.FORM.SEND_MESSAGE.CARD.CMD_ENTER_KEY.CONTENT'
|
||||
),
|
||||
lightImage: '/assets/images/dashboard/profile/hot-key-ctrl-enter.svg',
|
||||
darkImage:
|
||||
'/assets/images/dashboard/profile/hot-key-ctrl-enter-dark.svg',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -156,6 +231,15 @@ export default {
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.AVATAR_DELETE_FAILED'));
|
||||
}
|
||||
},
|
||||
toggleHotKey(key) {
|
||||
this.hotKeys = this.hotKeys.map(hotKey =>
|
||||
hotKey.key === key ? { ...hotKey, active: !hotKey.active } : hotKey
|
||||
);
|
||||
this.updateUISettings({ editor_message_key: key });
|
||||
this.showAlert(
|
||||
this.$t('PROFILE_SETTINGS.FORM.SEND_MESSAGE.UPDATE_SUCCESS')
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-start gap-2 px-0 text-sm tracking-[0.5] text-left rtl:text-right"
|
||||
:class="`col-span-${span}`"
|
||||
>
|
||||
<input
|
||||
v-model="localFlags"
|
||||
class="mt-1 flex-shrink-0 border-ash-200 border checked:border-none checked:bg-primary-600 dark:checked:bg-primary-600 shadow appearance-none rounded-[4px] w-4 h-4 focus:ring-1 after:content-[''] after:text-white checked:after:content-['✓'] after:flex after:items-center after:justify-center after:text-center after:text-xs after:font-bold after:relative"
|
||||
type="checkbox"
|
||||
:value="localValue"
|
||||
@input="handleInput"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
span: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
selectedFlags: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['input']);
|
||||
|
||||
const localValue = ref(props.value);
|
||||
const localFlags = ref(props.selectedFlags);
|
||||
|
||||
watch(
|
||||
() => props.selectedFlags,
|
||||
newValue => {
|
||||
localFlags.value = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
const handleInput = e => {
|
||||
emit('input', props.type, e);
|
||||
};
|
||||
</script>
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div id="profile-settings-notifications" class="flex flex-col gap-6">
|
||||
<!-- Layout for desktop devices -->
|
||||
<div class="hidden sm:block">
|
||||
<div
|
||||
class="grid content-center h-12 grid-cols-12 gap-4 py-0 rounded-t-xl"
|
||||
>
|
||||
<table-header-cell
|
||||
:span="7"
|
||||
label="`${$t('PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPE_TITLE')}`"
|
||||
>
|
||||
<span class="text-sm font-normal normal-case text-ash-800">
|
||||
{{ $t('PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPE_TITLE') }}
|
||||
</span>
|
||||
</table-header-cell>
|
||||
<table-header-cell
|
||||
:span="2"
|
||||
label="`${$t('PROFILE_SETTINGS.FORM.NOTIFICATIONS.EMAIL')}`"
|
||||
>
|
||||
<span class="text-sm font-medium normal-case text-ash-900">
|
||||
{{ $t('PROFILE_SETTINGS.FORM.NOTIFICATIONS.EMAIL') }}
|
||||
</span>
|
||||
</table-header-cell>
|
||||
<table-header-cell
|
||||
:span="3"
|
||||
label="`${$t('PROFILE_SETTINGS.FORM.NOTIFICATIONS.PUSH')}`"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-1">
|
||||
<span
|
||||
class="text-sm font-medium normal-case text-ash-900 whitespace-nowrap"
|
||||
>
|
||||
{{ $t('PROFILE_SETTINGS.FORM.NOTIFICATIONS.PUSH') }}
|
||||
</span>
|
||||
<form-switch
|
||||
:value="hasEnabledPushPermissions"
|
||||
@input="onRequestPermissions"
|
||||
/>
|
||||
</div>
|
||||
</table-header-cell>
|
||||
</div>
|
||||
<div
|
||||
v-for="(notification, index) in filteredNotificationTypes"
|
||||
:key="index"
|
||||
>
|
||||
<div
|
||||
class="grid items-center content-center h-12 grid-cols-12 gap-4 py-0 rounded-t-xl"
|
||||
>
|
||||
<div
|
||||
class="flex flex-row items-start gap-2 col-span-7 px-0 py-2 text-sm tracking-[0.5] rtl:text-right"
|
||||
>
|
||||
<span class="text-sm text-ash-900">
|
||||
{{ $t(notification.label) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(type, typeIndex) in ['email', 'push']"
|
||||
:key="typeIndex"
|
||||
class="flex items-start gap-2 px-0 text-sm tracking-[0.5] text-left rtl:text-right"
|
||||
:class="`col-span-${type === 'push' ? 3 : 2}`"
|
||||
>
|
||||
<CheckBox
|
||||
:value="`${type}_${notification.value}`"
|
||||
:is-checked="
|
||||
checkFlagStatus(type, notification.value, selectedPushFlags)
|
||||
"
|
||||
@update="id => handleInput(type, id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Layout for mobile devices -->
|
||||
<div class="flex flex-col gap-6 sm:hidden">
|
||||
<span class="text-sm font-medium normal-case text-ash-900">
|
||||
{{ $t('PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.TITLE') }}
|
||||
</span>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
v-for="(notification, index) in filteredNotificationTypes"
|
||||
:key="index"
|
||||
class="flex flex-row items-start gap-2"
|
||||
>
|
||||
<CheckBox
|
||||
:id="`email_${notification.value}`"
|
||||
:value="`email_${notification.value}`"
|
||||
:is-checked="checkFlagStatus('email', notification.value)"
|
||||
@update="handleEmailInput"
|
||||
/>
|
||||
<span class="text-sm text-ash-900">{{ $t(notification.label) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<span class="text-sm font-medium normal-case text-ash-900">
|
||||
{{ $t('PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.TITLE') }}
|
||||
</span>
|
||||
<form-switch
|
||||
:value="hasEnabledPushPermissions"
|
||||
@input="onRequestPermissions"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
v-for="(notification, index) in filteredNotificationTypes"
|
||||
:key="index"
|
||||
class="flex flex-row items-start gap-2"
|
||||
>
|
||||
<CheckBox
|
||||
:id="`push_${notification.value}`"
|
||||
:value="`push_${notification.value}`"
|
||||
:is-checked="checkFlagStatus('push', notification.value)"
|
||||
@update="handlePushInput"
|
||||
/>
|
||||
<span class="text-sm text-ash-900">{{ $t(notification.label) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import configMixin from 'shared/mixins/configMixin';
|
||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
import TableHeaderCell from 'dashboard/components/widgets/TableHeaderCell.vue';
|
||||
import CheckBox from 'v3/components/Form/CheckBox.vue';
|
||||
import {
|
||||
hasPushPermissions,
|
||||
requestPushPermissions,
|
||||
verifyServiceWorkerExistence,
|
||||
} from 'dashboard/helper/pushHelper.js';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import FormSwitch from 'v3/components/Form/Switch.vue';
|
||||
import { NOTIFICATION_TYPES } from './constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TableHeaderCell,
|
||||
FormSwitch,
|
||||
|
||||
CheckBox,
|
||||
},
|
||||
mixins: [alertMixin, configMixin, uiSettingsMixin],
|
||||
data() {
|
||||
return {
|
||||
selectedEmailFlags: [],
|
||||
selectedPushFlags: [],
|
||||
enableAudioAlerts: false,
|
||||
hasEnabledPushPermissions: false,
|
||||
notificationTypes: NOTIFICATION_TYPES,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
emailFlags: 'userNotificationSettings/getSelectedEmailFlags',
|
||||
pushFlags: 'userNotificationSettings/getSelectedPushFlags',
|
||||
uiSettings: 'getUISettings',
|
||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||
}),
|
||||
hasPushAPISupport() {
|
||||
return !!('Notification' in window);
|
||||
},
|
||||
isSLAEnabled() {
|
||||
return this.isFeatureEnabledonAccount(this.accountId, FEATURE_FLAGS.SLA);
|
||||
},
|
||||
filteredNotificationTypes() {
|
||||
return this.notificationTypes.filter(notification =>
|
||||
this.isSLAEnabled
|
||||
? true
|
||||
: ![
|
||||
'sla_missed_first_response',
|
||||
'sla_missed_next_response',
|
||||
'sla_missed_resolution',
|
||||
].includes(notification.value)
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
emailFlags(value) {
|
||||
this.selectedEmailFlags = value;
|
||||
},
|
||||
pushFlags(value) {
|
||||
this.selectedPushFlags = value;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (hasPushPermissions()) {
|
||||
this.getPushSubscription();
|
||||
}
|
||||
this.$store.dispatch('userNotificationSettings/get');
|
||||
},
|
||||
methods: {
|
||||
checkFlagStatus(type, flagType) {
|
||||
const selectedFlags =
|
||||
type === 'email' ? this.selectedEmailFlags : this.selectedPushFlags;
|
||||
return selectedFlags.includes(`${type}_${flagType}`);
|
||||
},
|
||||
onRegistrationSuccess() {
|
||||
this.hasEnabledPushPermissions = true;
|
||||
},
|
||||
onRequestPermissions() {
|
||||
requestPushPermissions({
|
||||
onSuccess: this.onRegistrationSuccess,
|
||||
});
|
||||
},
|
||||
getPushSubscription() {
|
||||
verifyServiceWorkerExistence(registration =>
|
||||
registration.pushManager
|
||||
.getSubscription()
|
||||
.then(subscription => {
|
||||
if (!subscription) {
|
||||
this.hasEnabledPushPermissions = false;
|
||||
} else {
|
||||
this.hasEnabledPushPermissions = true;
|
||||
}
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
.catch(error => console.log(error))
|
||||
);
|
||||
},
|
||||
async updateNotificationSettings() {
|
||||
try {
|
||||
this.$store.dispatch('userNotificationSettings/update', {
|
||||
selectedEmailFlags: this.selectedEmailFlags,
|
||||
selectedPushFlags: this.selectedPushFlags,
|
||||
});
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_SUCCESS'));
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_ERROR'));
|
||||
}
|
||||
},
|
||||
|
||||
handleAudioInput(e) {
|
||||
this.enableAudioAlerts = e.target.value;
|
||||
this.updateUISettings({
|
||||
enable_audio_alerts: this.enableAudioAlerts,
|
||||
});
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_SUCCESS'));
|
||||
},
|
||||
handleAudioAlertConditions(e) {
|
||||
let condition = e.target.value;
|
||||
if (condition === 'tab_is_inactive') {
|
||||
this.updateUISettings({
|
||||
always_play_audio_alert: !e.target.checked,
|
||||
});
|
||||
} else if (condition === 'conversations_are_read') {
|
||||
this.updateUISettings({
|
||||
alert_if_unread_assigned_conversation_exist: e.target.checked,
|
||||
});
|
||||
}
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_SUCCESS'));
|
||||
},
|
||||
handleAudioToneChange(value) {
|
||||
this.updateUISettings({ notification_tone: value });
|
||||
this.showAlert(this.$t('PROFILE_SETTINGS.FORM.API.UPDATE_SUCCESS'));
|
||||
},
|
||||
handleInput(type, id) {
|
||||
if (type === 'email') {
|
||||
this.handleEmailInput(id);
|
||||
} else {
|
||||
this.handlePushInput(id);
|
||||
}
|
||||
},
|
||||
handleEmailInput(id) {
|
||||
this.selectedEmailFlags = this.toggleInput(this.selectedEmailFlags, id);
|
||||
this.updateNotificationSettings();
|
||||
},
|
||||
handlePushInput(id) {
|
||||
this.selectedPushFlags = this.toggleInput(this.selectedPushFlags, id);
|
||||
this.updateNotificationSettings();
|
||||
},
|
||||
toggleInput(selected, current) {
|
||||
if (selected.includes(current)) {
|
||||
const newSelectedFlags = selected.filter(flag => flag !== current);
|
||||
return newSelectedFlags;
|
||||
}
|
||||
return [...selected, current];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,52 @@
|
||||
export const NOTIFICATION_TYPES = [
|
||||
{
|
||||
label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.CONVERSATION_CREATED',
|
||||
value: 'conversation_creation',
|
||||
},
|
||||
{
|
||||
label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.CONVERSATION_ASSIGNED',
|
||||
value: 'conversation_assignment',
|
||||
},
|
||||
{
|
||||
label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.CONVERSATION_MENTION',
|
||||
value: 'conversation_mention',
|
||||
},
|
||||
{
|
||||
label:
|
||||
'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.ASSIGNED_CONVERSATION_NEW_MESSAGE',
|
||||
value: 'assigned_conversation_new_message',
|
||||
},
|
||||
{
|
||||
label:
|
||||
'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.PARTICIPATING_CONVERSATION_NEW_MESSAGE',
|
||||
value: 'participating_conversation_new_message',
|
||||
},
|
||||
{
|
||||
label:
|
||||
'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.SLA_MISSED_FIRST_RESPONSE',
|
||||
value: 'sla_missed_first_response',
|
||||
},
|
||||
{
|
||||
label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.SLA_MISSED_NEXT_RESPONSE',
|
||||
value: 'sla_missed_next_response',
|
||||
},
|
||||
{
|
||||
label: 'PROFILE_SETTINGS.FORM.NOTIFICATIONS.TYPES.SLA_MISSED_RESOLUTION',
|
||||
value: 'sla_missed_resolution',
|
||||
},
|
||||
];
|
||||
|
||||
export const ALERT_EVENTS = [
|
||||
{
|
||||
value: 'none',
|
||||
label: 'none',
|
||||
},
|
||||
{
|
||||
value: 'mine',
|
||||
label: 'mine',
|
||||
},
|
||||
{
|
||||
value: 'all',
|
||||
label: 'all',
|
||||
},
|
||||
];
|
||||
-3
@@ -22,14 +22,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import SLAPopoverCard from 'dashboard/components/widgets/conversation/components/SLAPopoverCard.vue';
|
||||
export default {
|
||||
components: {
|
||||
SLAPopoverCard,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
|
||||
props: {
|
||||
slaEvents: {
|
||||
type: Array,
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
import SearchSuggestions from './SearchSuggestions.vue';
|
||||
import PublicSearchInput from './PublicSearchInput.vue';
|
||||
|
||||
@@ -35,7 +33,6 @@ export default {
|
||||
PublicSearchInput,
|
||||
SearchSuggestions,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
|
||||
@@ -74,13 +74,11 @@
|
||||
<script>
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import MultiselectDropdownItems from 'shared/components/ui/MultiselectDropdownItems.vue';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
MultiselectDropdownItems,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
|
||||
@@ -11,22 +11,26 @@ export default {
|
||||
if (events) {
|
||||
const wrappedEvents = this.wrapEventsInKeybindingsHandler(events);
|
||||
const keydownHandler = createKeybindingsHandler(wrappedEvents);
|
||||
this.appendToHandler(keydownHandler);
|
||||
document.addEventListener('keydown', keydownHandler);
|
||||
this.addEventHandler(keydownHandler);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.$el && this.$el.dataset.keydownHandlerIndex) {
|
||||
if (this.$el && this.$el.dataset?.keydownHandlerIndex) {
|
||||
const handlerToRemove =
|
||||
taggedHandlers[this.$el.dataset.keydownHandlerIndex];
|
||||
document.removeEventListener('keydown', handlerToRemove);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
appendToHandler(keydownHandler) {
|
||||
addEventHandler(keydownHandler) {
|
||||
const indexToAppend = taggedHandlers.push(keydownHandler) - 1;
|
||||
const root = this.$el;
|
||||
root.dataset.keydownHandlerIndex = indexToAppend;
|
||||
if (root && root.dataset) {
|
||||
// For the components with a top level v-if Vue renders it as an empty comment in the DOM
|
||||
// so we need to check if the root element has a dataset property to ensure it is a valid element
|
||||
document.addEventListener('keydown', keydownHandler);
|
||||
root.dataset.keydownHandlerIndex = indexToAppend;
|
||||
}
|
||||
},
|
||||
getKeyboardEvents() {
|
||||
return null;
|
||||
|
||||
@@ -58,7 +58,8 @@ const attrs = useAttrs();
|
||||
const baseClasses = {
|
||||
outline: 'outline outline-1 -outline-offset-1 focus:ring focus:ring-offset-1',
|
||||
ghost: 'hover:text-600 active:text-600 focus:ring focus:ring-offset-1',
|
||||
solid: 'hover:bg-700 active:bg-700 focus:ring focus:ring-offset-1',
|
||||
solid:
|
||||
'hover:bg-700 active:bg-700 focus:ring focus:ring-offset-1 focus:ring-2',
|
||||
};
|
||||
|
||||
const colorClass = computed(() => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<input
|
||||
:id="value"
|
||||
v-model="checked"
|
||||
type="checkbox"
|
||||
:value="value"
|
||||
class="flex-shrink-0 mt-0.5 border-ash-200 border checked:border-none checked:bg-primary-600 dark:checked:bg-primary-600 shadow appearance-none rounded-[4px] w-4 h-4 focus:ring-1 after:content-[''] after:text-white checked:after:content-['✓'] after:flex after:items-center after:justify-center after:text-center after:text-xs after:font-bold after:relative"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
isChecked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['input']);
|
||||
|
||||
const checked = computed({
|
||||
get: () => props.isChecked,
|
||||
set: value => emit('update', props.value, value),
|
||||
});
|
||||
</script>
|
||||
@@ -11,11 +11,11 @@
|
||||
:selected="value"
|
||||
:name="name"
|
||||
:class="{
|
||||
'text-slate-400': !value,
|
||||
'text-slate-900 dark:text-slate-100': value,
|
||||
'text-ash-400': !value,
|
||||
'text-ash-900': value,
|
||||
'pl-9': icon,
|
||||
}"
|
||||
class="block w-full px-3 py-2 pr-6 mb-0 border-0 rounded-md shadow-sm outline-none appearance-none select-caret ring-1 ring-inset placeholder:text-slate-400 focus:ring-2 focus:ring-inset focus:ring-woot-500 sm:text-sm sm:leading-6 dark:bg-slate-700 dark:ring-slate-600 dark:focus:ring-woot-500 ring-slate-200"
|
||||
class="block w-full px-3 py-2 pr-6 mb-0 border-0 shadow-sm outline-none appearance-none rounded-xl select-caret ring-ash-200 ring-1 ring-inset placeholder:text-ash-900 focus:ring-2 focus:ring-inset focus:ring-primary-500 sm:text-sm sm:leading-6"
|
||||
@input="onInput"
|
||||
>
|
||||
<option value="" disabled selected class="hidden">
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="relative flex-shrink-0 h-4 p-0 border-none shadow-inner w-7 rounded-3xl"
|
||||
:class="
|
||||
value ? 'bg-primary-600 shadow-primary-800' : 'shadow-ash-400 bg-ash-200'
|
||||
"
|
||||
role="switch"
|
||||
:aria-checked="value.toString()"
|
||||
@click="onClick"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="rounded-full bg-white top-0.5 absolute dark:bg-white w-3 h-3 translate-y-0 duration-200 transition-transform ease-in-out"
|
||||
:class="
|
||||
value
|
||||
? 'ltr:translate-x-0 rtl:translate-x-[12px]'
|
||||
: 'ltr:-translate-x-[12px] rtl:translate-x-0'
|
||||
"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: { type: Boolean, default: false },
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('input', !this.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -49,7 +49,6 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
import ChatAttachmentButton from 'widget/components/ChatAttachment.vue';
|
||||
import ChatSendButton from 'widget/components/ChatSendButton.vue';
|
||||
@@ -69,7 +68,7 @@ export default {
|
||||
FluentIcon,
|
||||
ResizableTextArea,
|
||||
},
|
||||
mixins: [clickaway, configMixin, darkModeMixin],
|
||||
mixins: [configMixin, darkModeMixin],
|
||||
props: {
|
||||
onSendMessage: {
|
||||
type: Function,
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import mentionSelectionKeyboardMixin from 'dashboard/components/widgets/mentions/mentionSelectionKeyboardMixin.js';
|
||||
@@ -94,12 +93,7 @@ export default {
|
||||
components: {
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [
|
||||
mentionSelectionKeyboardMixin,
|
||||
FormulateInputMixin,
|
||||
darkModeMixin,
|
||||
clickaway,
|
||||
],
|
||||
mixins: [mentionSelectionKeyboardMixin, FormulateInputMixin, darkModeMixin],
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
|
||||
@@ -13,7 +13,6 @@ class Inboxes::FetchImapEmailsJob < MutexApplicationJob
|
||||
end
|
||||
rescue *ExceptionList::IMAP_EXCEPTIONS => e
|
||||
Rails.logger.error "Authorization error for email channel - #{channel.inbox.id} : #{e.message}"
|
||||
channel.authorization_error!
|
||||
rescue EOFError, OpenSSL::SSL::SSLError, Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, Net::IMAP::InvalidResponseError => e
|
||||
Rails.logger.error "Error for email channel - #{channel.inbox.id} : #{e.message}"
|
||||
rescue LockAcquisitionError
|
||||
|
||||
@@ -81,7 +81,7 @@ class Attachment < ApplicationRecord
|
||||
height: file.metadata[:height]
|
||||
}
|
||||
|
||||
metadata[:data_url] = metadata[:thumb_url] = external_url if message.instagram_story_mention?
|
||||
metadata[:data_url] = metadata[:thumb_url] = external_url if message.inbox.instagram?
|
||||
metadata
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace :sidekiq do
|
||||
desc "Clear ActionCableJobs from sidekiq's critical queue"
|
||||
task clear_action_cable_broadcast_jobs: :environment do
|
||||
queue_name = 'critical'
|
||||
queue = Sidekiq::Queue.new(queue_name)
|
||||
jobs_cleared = 0
|
||||
|
||||
queue.each do |job|
|
||||
if job['wrapped'] == 'ActionCableBroadcastJob'
|
||||
job.delete
|
||||
jobs_cleared += 1
|
||||
end
|
||||
end
|
||||
|
||||
puts "Cleared #{jobs_cleared} ActionCableBroadcastJob(s) from the #{queue_name} queue."
|
||||
end
|
||||
end
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.2 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1,25 @@
|
||||
<svg width="318" height="100" viewBox="0 0 318 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="318" height="100" rx="8" fill="url(#paint0_linear_425_68232)"/>
|
||||
<g filter="url(#filter0_d_425_68232)">
|
||||
<rect x="223" y="56" width="79" height="28" rx="6" fill="#4B7DFB"/>
|
||||
<path d="M236.142 74V65.2727H241.614V66.4062H237.459V69.0653H241.328V70.1946H237.459V72.8665H241.665V74H236.142ZM244.545 70.1136V74H243.271V67.4545H244.494V68.5199H244.575C244.725 68.1733 244.961 67.8949 245.282 67.6847C245.606 67.4744 246.013 67.3693 246.505 67.3693C246.951 67.3693 247.342 67.4631 247.677 67.6506C248.012 67.8352 248.272 68.1108 248.457 68.4773C248.641 68.8437 248.734 69.2969 248.734 69.8366V74H247.46V69.9901C247.46 69.5156 247.336 69.1449 247.089 68.8778C246.842 68.608 246.502 68.473 246.07 68.473C245.775 68.473 245.512 68.5369 245.282 68.6648C245.055 68.7926 244.874 68.9801 244.741 69.2273C244.61 69.4716 244.545 69.767 244.545 70.1136ZM253.498 67.4545V68.4773H249.923V67.4545H253.498ZM250.881 65.8864H252.156V72.0781C252.156 72.3253 252.192 72.5114 252.266 72.6364C252.34 72.7585 252.435 72.8423 252.552 72.8878C252.671 72.9304 252.8 72.9517 252.94 72.9517C253.042 72.9517 253.131 72.9446 253.208 72.9304C253.285 72.9162 253.344 72.9048 253.387 72.8963L253.617 73.9489C253.543 73.9773 253.438 74.0057 253.302 74.0341C253.165 74.0653 252.995 74.0824 252.79 74.0852C252.455 74.0909 252.143 74.0312 251.853 73.9062C251.563 73.7812 251.329 73.5881 251.15 73.3267C250.971 73.0653 250.881 72.7372 250.881 72.3423V65.8864ZM257.658 74.1321C257.013 74.1321 256.458 73.9943 255.992 73.7188C255.529 73.4403 255.171 73.0497 254.918 72.5469C254.668 72.0412 254.543 71.4489 254.543 70.7699C254.543 70.0994 254.668 69.5085 254.918 68.9972C255.171 68.4858 255.523 68.0866 255.975 67.7997C256.429 67.5128 256.961 67.3693 257.569 67.3693C257.938 67.3693 258.296 67.4304 258.642 67.5526C258.989 67.6747 259.3 67.8665 259.576 68.1278C259.851 68.3892 260.069 68.7287 260.228 69.1463C260.387 69.5611 260.466 70.0653 260.466 70.6591V71.1108H255.263V70.1562H259.218C259.218 69.821 259.15 69.5241 259.013 69.2656C258.877 69.0043 258.685 68.7983 258.438 68.6477C258.194 68.4972 257.907 68.4219 257.577 68.4219C257.219 68.4219 256.907 68.5099 256.64 68.6861C256.375 68.8594 256.171 69.0866 256.026 69.3679C255.884 69.6463 255.813 69.9489 255.813 70.2756V71.0213C255.813 71.4588 255.89 71.831 256.043 72.1378C256.199 72.4446 256.417 72.679 256.695 72.8409C256.973 73 257.299 73.0795 257.671 73.0795C257.912 73.0795 258.132 73.0455 258.331 72.9773C258.53 72.9062 258.702 72.8011 258.847 72.6619C258.992 72.5227 259.103 72.3509 259.179 72.1463L260.385 72.3636C260.289 72.7187 260.115 73.0298 259.865 73.2969C259.618 73.5611 259.307 73.767 258.932 73.9148C258.56 74.0597 258.135 74.1321 257.658 74.1321ZM261.88 74V67.4545H263.112V68.4943H263.18C263.299 68.142 263.509 67.8651 263.81 67.6634C264.114 67.4588 264.458 67.3565 264.842 67.3565C264.921 67.3565 265.015 67.3594 265.123 67.3651C265.234 67.3707 265.32 67.3778 265.383 67.3864V68.6051C265.332 68.5909 265.241 68.5753 265.11 68.5582C264.979 68.5384 264.849 68.5284 264.718 68.5284C264.417 68.5284 264.148 68.5923 263.913 68.7202C263.68 68.8452 263.495 69.0199 263.359 69.2443C263.222 69.4659 263.154 69.7187 263.154 70.0028V74H261.88ZM270.032 70.3778C270.032 69.3097 270.173 68.3267 270.454 67.429C270.735 66.5312 271.146 65.7031 271.685 64.9446H272.853C272.643 65.2259 272.447 65.571 272.265 65.9801C272.083 66.3892 271.924 66.8381 271.788 67.3267C271.651 67.8125 271.545 68.3168 271.468 68.8395C271.391 69.3594 271.353 69.8722 271.353 70.3778C271.353 71.054 271.42 71.7386 271.553 72.4318C271.687 73.125 271.867 73.7685 272.094 74.3622C272.322 74.956 272.575 75.4403 272.853 75.8153H271.685C271.146 75.0568 270.735 74.2287 270.454 73.331C270.173 72.4332 270.032 71.4489 270.032 70.3778ZM277.192 72.8324L274.525 70.1818L277.192 67.5312V72.8324ZM276.361 70.7273V69.6364H284.057V70.7273H276.361ZM282.996 70.7273V65.2727H284.083V70.7273H282.996ZM288.588 70.3778C288.588 71.4489 288.447 72.4332 288.166 73.331C287.885 74.2287 287.474 75.0568 286.935 75.8153H285.767C285.977 75.5341 286.173 75.1889 286.355 74.7798C286.537 74.3707 286.696 73.9233 286.832 73.4375C286.969 72.9489 287.075 72.4432 287.152 71.9205C287.229 71.3977 287.267 70.8835 287.267 70.3778C287.267 69.7045 287.2 69.0213 287.067 68.3281C286.933 67.6349 286.753 66.9915 286.526 66.3977C286.298 65.804 286.045 65.3196 285.767 64.9446H286.935C287.474 65.7031 287.885 66.5312 288.166 67.429C288.447 68.3267 288.588 69.3097 288.588 70.3778Z" fill="white"/>
|
||||
</g>
|
||||
<rect x="8" y="10" width="202" height="12" rx="6" fill="#383942"/>
|
||||
<rect x="8" y="30" width="137" height="12" rx="6" fill="#383942"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_425_68232" x="222.167" y="55.5833" width="80.6667" height="29.6667" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.416667"/>
|
||||
<feGaussianBlur stdDeviation="0.416667"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.214953 0 0 0 0 0.364486 0 0 0 0 0.985047 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_425_68232"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_425_68232" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_425_68232" x1="0" y1="50" x2="318" y2="50" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.39" stop-color="#2A2B31"/>
|
||||
<stop offset="1" stop-color="#242F45"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,25 @@
|
||||
<svg width="318" height="100" viewBox="0 0 318 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="318" height="100" rx="8" fill="url(#paint0_linear_487_6995)"/>
|
||||
<g filter="url(#filter0_d_487_6995)">
|
||||
<rect x="223" y="56" width="79" height="28" rx="6" fill="#4B7DFB"/>
|
||||
<path d="M236.142 74V65.2727H241.614V66.4062H237.459V69.0653H241.328V70.1946H237.459V72.8665H241.665V74H236.142ZM244.545 70.1136V74H243.271V67.4545H244.494V68.5199H244.575C244.725 68.1733 244.961 67.8949 245.282 67.6847C245.606 67.4744 246.013 67.3693 246.505 67.3693C246.951 67.3693 247.342 67.4631 247.677 67.6506C248.012 67.8352 248.272 68.1108 248.457 68.4773C248.641 68.8437 248.734 69.2969 248.734 69.8366V74H247.46V69.9901C247.46 69.5156 247.336 69.1449 247.089 68.8778C246.842 68.608 246.502 68.473 246.07 68.473C245.775 68.473 245.512 68.5369 245.282 68.6648C245.055 68.7926 244.874 68.9801 244.741 69.2273C244.61 69.4716 244.545 69.767 244.545 70.1136ZM253.498 67.4545V68.4773H249.923V67.4545H253.498ZM250.881 65.8864H252.156V72.0781C252.156 72.3253 252.192 72.5114 252.266 72.6364C252.34 72.7585 252.435 72.8423 252.552 72.8878C252.671 72.9304 252.8 72.9517 252.94 72.9517C253.042 72.9517 253.131 72.9446 253.208 72.9304C253.285 72.9162 253.344 72.9048 253.387 72.8963L253.617 73.9489C253.543 73.9773 253.438 74.0057 253.302 74.0341C253.165 74.0653 252.995 74.0824 252.79 74.0852C252.455 74.0909 252.143 74.0312 251.853 73.9062C251.563 73.7812 251.329 73.5881 251.15 73.3267C250.971 73.0653 250.881 72.7372 250.881 72.3423V65.8864ZM257.658 74.1321C257.013 74.1321 256.458 73.9943 255.992 73.7188C255.529 73.4403 255.171 73.0497 254.918 72.5469C254.668 72.0412 254.543 71.4489 254.543 70.7699C254.543 70.0994 254.668 69.5085 254.918 68.9972C255.171 68.4858 255.523 68.0866 255.975 67.7997C256.429 67.5128 256.961 67.3693 257.569 67.3693C257.938 67.3693 258.296 67.4304 258.642 67.5526C258.989 67.6747 259.3 67.8665 259.576 68.1278C259.851 68.3892 260.069 68.7287 260.228 69.1463C260.387 69.5611 260.466 70.0653 260.466 70.6591V71.1108H255.263V70.1562H259.218C259.218 69.821 259.15 69.5241 259.013 69.2656C258.877 69.0043 258.685 68.7983 258.438 68.6477C258.194 68.4972 257.907 68.4219 257.577 68.4219C257.219 68.4219 256.907 68.5099 256.64 68.6861C256.375 68.8594 256.171 69.0866 256.026 69.3679C255.884 69.6463 255.813 69.9489 255.813 70.2756V71.0213C255.813 71.4588 255.89 71.831 256.043 72.1378C256.199 72.4446 256.417 72.679 256.695 72.8409C256.973 73 257.299 73.0795 257.671 73.0795C257.912 73.0795 258.132 73.0455 258.331 72.9773C258.53 72.9062 258.702 72.8011 258.847 72.6619C258.992 72.5227 259.103 72.3509 259.179 72.1463L260.385 72.3636C260.289 72.7187 260.115 73.0298 259.865 73.2969C259.618 73.5611 259.307 73.767 258.932 73.9148C258.56 74.0597 258.135 74.1321 257.658 74.1321ZM261.88 74V67.4545H263.112V68.4943H263.18C263.299 68.142 263.509 67.8651 263.81 67.6634C264.114 67.4588 264.458 67.3565 264.842 67.3565C264.921 67.3565 265.015 67.3594 265.123 67.3651C265.234 67.3707 265.32 67.3778 265.383 67.3864V68.6051C265.332 68.5909 265.241 68.5753 265.11 68.5582C264.979 68.5384 264.849 68.5284 264.718 68.5284C264.417 68.5284 264.148 68.5923 263.913 68.7202C263.68 68.8452 263.495 69.0199 263.359 69.2443C263.222 69.4659 263.154 69.7187 263.154 70.0028V74H261.88ZM270.032 70.3778C270.032 69.3097 270.173 68.3267 270.454 67.429C270.735 66.5312 271.146 65.7031 271.685 64.9446H272.853C272.643 65.2259 272.447 65.571 272.265 65.9801C272.083 66.3892 271.924 66.8381 271.788 67.3267C271.651 67.8125 271.545 68.3168 271.468 68.8395C271.391 69.3594 271.353 69.8722 271.353 70.3778C271.353 71.054 271.42 71.7386 271.553 72.4318C271.687 73.125 271.867 73.7685 272.094 74.3622C272.322 74.956 272.575 75.4403 272.853 75.8153H271.685C271.146 75.0568 270.735 74.2287 270.454 73.331C270.173 72.4332 270.032 71.4489 270.032 70.3778ZM277.192 72.8324L274.525 70.1818L277.192 67.5312V72.8324ZM276.361 70.7273V69.6364H284.057V70.7273H276.361ZM282.996 70.7273V65.2727H284.083V70.7273H282.996ZM288.588 70.3778C288.588 71.4489 288.447 72.4332 288.166 73.331C287.885 74.2287 287.474 75.0568 286.935 75.8153H285.767C285.977 75.5341 286.173 75.1889 286.355 74.7798C286.537 74.3707 286.696 73.9233 286.832 73.4375C286.969 72.9489 287.075 72.4432 287.152 71.9205C287.229 71.3977 287.267 70.8835 287.267 70.3778C287.267 69.7045 287.2 69.0213 287.067 68.3281C286.933 67.6349 286.753 66.9915 286.526 66.3977C286.298 65.804 286.045 65.3196 285.767 64.9446H286.935C287.474 65.7031 287.885 66.5312 288.166 67.429C288.447 68.3267 288.588 69.3097 288.588 70.3778Z" fill="white"/>
|
||||
</g>
|
||||
<rect x="8" y="10" width="202" height="12" rx="6" fill="#FDFDFD"/>
|
||||
<rect x="8" y="30" width="137" height="12" rx="6" fill="#FDFDFD"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_487_6995" x="222.167" y="55.5833" width="80.6667" height="29.6667" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.416667"/>
|
||||
<feGaussianBlur stdDeviation="0.416667"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.214953 0 0 0 0 0.364486 0 0 0 0 0.985047 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_487_6995"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_487_6995" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_487_6995" x1="0" y1="50" x2="318" y2="50" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ECEEF2"/>
|
||||
<stop offset="1" stop-color="#E7F2FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -69,16 +69,6 @@ RSpec.describe Inboxes::FetchImapEmailsJob do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when IMAP connection errors out' do
|
||||
it 'mark the connection for authorization required' do
|
||||
allow(Imap::FetchEmailService).to receive(:new).with(channel: imap_email_channel, interval: 1).and_raise(Errno::ECONNREFUSED)
|
||||
allow(Redis::Alfred).to receive(:incr)
|
||||
|
||||
expect(Redis::Alfred).to receive(:incr).with("AUTHORIZATION_ERROR_COUNT:channel_email:#{imap_email_channel.id}")
|
||||
described_class.perform_now(imap_email_channel)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the fetch service returns the email objects' do
|
||||
let(:inbound_mail) { create_inbound_email_from_fixture('welcome.eml').mail }
|
||||
let(:mailbox) { double }
|
||||
|
||||
Reference in New Issue
Block a user