feat(reports): add common monitor templates (#16011)
The empty Monitors page keeps its centered heading, create action, faded preview, and compact template list. It now offers four familiar customer conversation topics: missing order updates, feature requests, refund requests, and cancellation intent. Selecting one prefills its name and full condition. The preview uses example text without invented counts, and the WhatsApp BSUID and self-hosted automation examples are removed. Related to #15966. ## How to test 1. Open Reports → Monitors in an account with no monitors. Confirm the existing centered empty-state layout and four compact template rows appear. 2. Select Missing order updates or Feature requests. Confirm the creation form contains the corresponding name and full condition. 3. Confirm Login problems, Competitor mentions, WhatsApp BSUID, and self-hosted automation examples are absent, and the faded preview shows no fabricated counts. 4. Sign in without administrator access. Confirm the templates are disabled and the administrator help text appears.
This commit is contained in:
@@ -22,19 +22,27 @@
|
||||
"EMPTY_TITLE": "Turn conversations into a trend you can follow",
|
||||
"EMPTY_DESCRIPTION": "Describe a topic in plain English. A monitor finds matching conversations and keeps its chart updated as new customer messages arrive.",
|
||||
"ADMIN_HELP": "Ask an administrator to create your first monitor.",
|
||||
"EXAMPLES_TITLE": "Try an example",
|
||||
"EXAMPLES_TITLE": "Start with a template",
|
||||
"EXAMPLES": {
|
||||
"REFUNDS": {
|
||||
"NAME": "Refund conversations",
|
||||
"CONDITION": "All conversations mentioning refunds"
|
||||
"MISSING_ORDER_UPDATES": {
|
||||
"NAME": "Missing order updates",
|
||||
"CONDITION": "A customer says an order confirmation, shipping update, or delivery notice did not arrive or was sent for the wrong order. Exclude questions about parcel location when the customer received tracking information.",
|
||||
"BENEFIT": "Track recurring delivery communication problems."
|
||||
},
|
||||
"BSUID": {
|
||||
"NAME": "WhatsApp BSUID",
|
||||
"CONDITION": "All conversations related to WhatsApp BSUID"
|
||||
"FEATURE_REQUESTS": {
|
||||
"NAME": "Feature requests",
|
||||
"CONDITION": "A customer asks for a capability the product does not currently offer. Exclude questions about how to use a feature that already exists.",
|
||||
"BENEFIT": "Keep a running count of unmet needs."
|
||||
},
|
||||
"AUTOMATIONS": {
|
||||
"NAME": "Self-hosted automations",
|
||||
"CONDITION": "All conversations related to automations from a self-hosted customer"
|
||||
"REFUND_REQUESTS": {
|
||||
"NAME": "Refund requests",
|
||||
"CONDITION": "A customer asks for money back on a payment already made, including duplicate charges or refunds after cancellation. Exclude questions about the refund policy and requests for a discount.",
|
||||
"BENEFIT": "Bring a consistent definition to refund requests."
|
||||
},
|
||||
"CANCELLATION_INTENT": {
|
||||
"NAME": "Cancellation intent",
|
||||
"CONDITION": "A customer says they intend to cancel, will not renew, or plan to stop using the product. Exclude requests to reduce seats or downgrade, and cancellations withdrawn in the same conversation.",
|
||||
"BENEFIT": "Find customers who may need a conversation before they leave."
|
||||
}
|
||||
},
|
||||
"MATCHING_CONVERSATIONS": "Matching conversations created",
|
||||
|
||||
@@ -4,51 +4,63 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import MonitorListItem from './MonitorListItem.vue';
|
||||
|
||||
const emit = defineEmits(['create']);
|
||||
|
||||
const PREVIEW_COUNTS = [128, 64, 37];
|
||||
|
||||
const { t } = useI18n();
|
||||
const { isAdmin } = useAdmin();
|
||||
|
||||
const examples = computed(() => [
|
||||
{
|
||||
name: t('MONITORS.EXAMPLES.REFUNDS.NAME'),
|
||||
condition: t('MONITORS.EXAMPLES.REFUNDS.CONDITION'),
|
||||
name: t('MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.NAME'),
|
||||
condition: t('MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.CONDITION'),
|
||||
benefit: t('MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.BENEFIT'),
|
||||
},
|
||||
{
|
||||
name: t('MONITORS.EXAMPLES.BSUID.NAME'),
|
||||
condition: t('MONITORS.EXAMPLES.BSUID.CONDITION'),
|
||||
name: t('MONITORS.EXAMPLES.FEATURE_REQUESTS.NAME'),
|
||||
condition: t('MONITORS.EXAMPLES.FEATURE_REQUESTS.CONDITION'),
|
||||
benefit: t('MONITORS.EXAMPLES.FEATURE_REQUESTS.BENEFIT'),
|
||||
},
|
||||
{
|
||||
name: t('MONITORS.EXAMPLES.AUTOMATIONS.NAME'),
|
||||
condition: t('MONITORS.EXAMPLES.AUTOMATIONS.CONDITION'),
|
||||
name: t('MONITORS.EXAMPLES.REFUND_REQUESTS.NAME'),
|
||||
condition: t('MONITORS.EXAMPLES.REFUND_REQUESTS.CONDITION'),
|
||||
benefit: t('MONITORS.EXAMPLES.REFUND_REQUESTS.BENEFIT'),
|
||||
},
|
||||
{
|
||||
name: t('MONITORS.EXAMPLES.CANCELLATION_INTENT.NAME'),
|
||||
condition: t('MONITORS.EXAMPLES.CANCELLATION_INTENT.CONDITION'),
|
||||
benefit: t('MONITORS.EXAMPLES.CANCELLATION_INTENT.BENEFIT'),
|
||||
},
|
||||
]);
|
||||
const previewMonitors = computed(() =>
|
||||
examples.value.map((example, index) => ({
|
||||
...example,
|
||||
id: index + 1,
|
||||
recent_count: PREVIEW_COUNTS[index],
|
||||
paused_at: null,
|
||||
}))
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col items-center gap-8 pb-12">
|
||||
<div
|
||||
inert
|
||||
aria-hidden="true"
|
||||
class="w-full select-none opacity-70 [mask-image:linear-gradient(to_bottom,black,transparent)]"
|
||||
>
|
||||
<div class="flex flex-col divide-y divide-n-weak border-t border-n-weak">
|
||||
<MonitorListItem
|
||||
v-for="monitor in previewMonitors"
|
||||
:key="monitor.id"
|
||||
:monitor="monitor"
|
||||
/>
|
||||
<div
|
||||
v-for="example in examples.slice(0, 3)"
|
||||
:key="example.name"
|
||||
class="flex min-w-0 items-center gap-4 py-4"
|
||||
>
|
||||
<span
|
||||
class="flex size-10 shrink-0 items-center justify-center rounded-xl outline outline-1 -outline-offset-1 outline-n-weak"
|
||||
>
|
||||
<Icon icon="i-lucide-monitor" class="size-4 text-n-slate-11" />
|
||||
</span>
|
||||
<span class="flex min-w-0 flex-col gap-0.5">
|
||||
<span class="truncate text-heading-3 text-n-slate-12">{{
|
||||
example.name
|
||||
}}</span>
|
||||
<span class="truncate text-body-main text-n-slate-11">{{
|
||||
example.condition
|
||||
}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-3 text-center">
|
||||
@@ -74,14 +86,22 @@ const previewMonitors = computed(() =>
|
||||
</span>
|
||||
<button
|
||||
v-for="example in examples"
|
||||
:key="example.condition"
|
||||
:key="example.name"
|
||||
type="button"
|
||||
:disabled="!isAdmin"
|
||||
class="flex w-full items-center justify-between gap-2 rounded-lg border border-n-weak bg-n-slate-2 px-3 py-2 text-start text-sm text-n-slate-11 transition-colors enabled:hover:bg-n-slate-3 enabled:hover:text-n-slate-12 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
@click="emit('create', example)"
|
||||
@click="
|
||||
emit('create', { name: example.name, condition: example.condition })
|
||||
"
|
||||
>
|
||||
<span class="min-w-0 truncate">{{ example.condition }}</span>
|
||||
<Icon icon="i-lucide-chevron-right" class="size-4 shrink-0" />
|
||||
<span class="flex min-w-0 flex-col gap-0.5">
|
||||
<span class="font-medium text-n-slate-12">{{ example.name }}</span>
|
||||
<span class="truncate">{{ example.benefit }}</span>
|
||||
</span>
|
||||
<Icon
|
||||
icon="i-lucide-chevron-right"
|
||||
class="size-4 shrink-0 rtl:rotate-180"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -12,22 +12,32 @@ vi.mock('vue-i18n', async importOriginal => ({
|
||||
}));
|
||||
|
||||
describe('MonitorsEmptyState', () => {
|
||||
it('lets an administrator create a monitor or start from an example', async () => {
|
||||
it('lets an administrator create a monitor or start from a template', async () => {
|
||||
state.isAdmin = ref(true);
|
||||
const wrapper = shallowMount(MonitorsEmptyState);
|
||||
|
||||
wrapper.findComponent({ name: 'Button' }).vm.$emit('click');
|
||||
await wrapper.findAll('button')[1].trigger('click');
|
||||
await wrapper.findAll('button')[0].trigger('click');
|
||||
|
||||
expect(wrapper.emitted('create')).toEqual([
|
||||
[],
|
||||
[
|
||||
{
|
||||
name: 'MONITORS.EXAMPLES.BSUID.NAME',
|
||||
condition: 'MONITORS.EXAMPLES.BSUID.CONDITION',
|
||||
name: 'MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.NAME',
|
||||
condition: 'MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.CONDITION',
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(wrapper.findAll('button')).toHaveLength(4);
|
||||
expect(wrapper.text()).toContain('MONITORS.EMPTY_TITLE');
|
||||
expect(wrapper.text()).toContain('MONITORS.EXAMPLES.FEATURE_REQUESTS.NAME');
|
||||
expect(wrapper.text()).toContain(
|
||||
'MONITORS.EXAMPLES.FEATURE_REQUESTS.BENEFIT'
|
||||
);
|
||||
expect(wrapper.text()).not.toContain('MONITORS.EXAMPLES.LOGIN_PROBLEMS');
|
||||
expect(wrapper.text()).not.toContain(
|
||||
'MONITORS.EXAMPLES.COMPETITOR_MENTIONS'
|
||||
);
|
||||
expect(wrapper.text()).not.toContain('MONITORS.ADMIN_HELP');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user