feat: add basic ui
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
|
||||
const props = defineProps({
|
||||
campaignId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
|
||||
const allCampaigns = computed(() => store.getters['campaigns/getAllCampaigns']);
|
||||
|
||||
const campaign = computed(() =>
|
||||
allCampaigns.value.find(c => c.id === Number(props.campaignId))
|
||||
);
|
||||
const previewData = ref({
|
||||
title: '',
|
||||
message: '',
|
||||
template: '',
|
||||
audience: '',
|
||||
campaignType: '',
|
||||
});
|
||||
|
||||
const updatePreview = () => {
|
||||
if (campaign.value) {
|
||||
previewData.value = {
|
||||
title: campaign.value.title || '',
|
||||
message: campaign.value.message || '',
|
||||
template: campaign.value.template || '',
|
||||
audience: campaign.value.audience || '',
|
||||
campaignType: campaign.value.campaign_type || '',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => campaign.value,
|
||||
() => updatePreview(),
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4 h-full bg-white rounded-lg border">
|
||||
<h3 class="mb-4 text-lg font-semibold">
|
||||
{{ t('CAMPAIGN.PLAYGROUND.TITLE') }}
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Campaign Title Preview -->
|
||||
<div class="p-4 bg-gray-50 rounded-lg border">
|
||||
<h4 class="mb-2 text-sm font-medium text-gray-700">
|
||||
{{ t('CAMPAIGN.PLAYGROUND.CAMPAIGN_TITLE') }}
|
||||
</h4>
|
||||
<p class="text-sm text-gray-900">
|
||||
{{ previewData.title || t('CAMPAIGN.PLAYGROUND.NO_TITLE') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Message Preview -->
|
||||
<div class="p-4 bg-gray-50 rounded-lg border">
|
||||
<h4 class="mb-2 text-sm font-medium text-gray-700">
|
||||
{{ t('CAMPAIGN.PLAYGROUND.MESSAGE_PREVIEW') }}
|
||||
</h4>
|
||||
<div
|
||||
class="text-sm text-gray-900 whitespace-pre-wrap"
|
||||
v-html="previewData.message || t('CAMPAIGN.PLAYGROUND.NO_MESSAGE')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Template Info -->
|
||||
<div v-if="previewData.template" class="p-4 bg-blue-50 rounded-lg border">
|
||||
<h4 class="mb-2 text-sm font-medium text-blue-700">
|
||||
{{ t('CAMPAIGN.PLAYGROUND.TEMPLATE') }}
|
||||
</h4>
|
||||
<p class="text-sm text-blue-900">
|
||||
{{ previewData.template }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Audience Info -->
|
||||
<div
|
||||
v-if="previewData.audience"
|
||||
class="p-4 bg-green-50 rounded-lg border"
|
||||
>
|
||||
<h4 class="mb-2 text-sm font-medium text-green-700">
|
||||
{{ t('CAMPAIGN.PLAYGROUND.AUDIENCE') }}
|
||||
</h4>
|
||||
<p class="text-sm text-green-900">
|
||||
{{ previewData.audience }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Campaign Type -->
|
||||
<div class="p-4 bg-yellow-50 rounded-lg border">
|
||||
<h4 class="mb-2 text-sm font-medium text-yellow-700">
|
||||
{{ t('CAMPAIGN.PLAYGROUND.CAMPAIGN_TYPE') }}
|
||||
</h4>
|
||||
<p class="text-sm text-yellow-900">
|
||||
{{ previewData.campaignType || t('CAMPAIGN.PLAYGROUND.NO_TYPE') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Preview Note -->
|
||||
<div class="mt-4 text-xs italic text-gray-500">
|
||||
{{ t('CAMPAIGN.PLAYGROUND.PREVIEW_NOTE') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
<script setup>
|
||||
import { reactive, computed, watch, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
||||
import Accordion from 'dashboard/components-next/Accordion/Accordion.vue';
|
||||
import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
|
||||
import TagMultiSelectComboBox from 'dashboard/components-next/combobox/TagMultiSelectComboBox.vue';
|
||||
import WhatsAppTemplateParser from 'dashboard/components-next/whatsapp/WhatsAppTemplateParser.vue';
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => ['edit', 'create'].includes(value),
|
||||
},
|
||||
campaign: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const formState = {
|
||||
uiFlags: useMapGetter('campaigns/getUIFlags'),
|
||||
labels: useMapGetter('labels/getLabels'),
|
||||
getFilteredWhatsAppTemplates: useMapGetter(
|
||||
'inboxes/getFilteredWhatsAppTemplates'
|
||||
),
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
title: '',
|
||||
description: '',
|
||||
message: '',
|
||||
templateId: null,
|
||||
selectedAudience: [],
|
||||
scheduledAt: null,
|
||||
campaignType: 'ongoing',
|
||||
};
|
||||
|
||||
const state = reactive({ ...initialState });
|
||||
const templateParserRef = ref(null);
|
||||
|
||||
const validationRules = {
|
||||
title: { required, minLength: minLength(1) },
|
||||
templateId: { required },
|
||||
selectedAudience: { required },
|
||||
};
|
||||
|
||||
const v$ = useVuelidate(validationRules, state);
|
||||
|
||||
const isLoading = computed(() => formState.uiFlags.value.creatingItem);
|
||||
|
||||
const getErrorMessage = field => {
|
||||
return v$.value[field].$error ? v$.value[field].$errors[0].$message : '';
|
||||
};
|
||||
|
||||
const formErrors = computed(() => ({
|
||||
title: getErrorMessage('title'),
|
||||
template: getErrorMessage('templateId'),
|
||||
audience: getErrorMessage('selectedAudience'),
|
||||
}));
|
||||
|
||||
const templateOptions = computed(() => {
|
||||
if (!props.campaign.inbox.id) return [];
|
||||
const templates = formState.getFilteredWhatsAppTemplates.value(
|
||||
props.campaign.inbox.id
|
||||
);
|
||||
return templates.map(template => {
|
||||
const friendlyName = template.name
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\b\w/g, l => l.toUpperCase());
|
||||
|
||||
return {
|
||||
value: template.id,
|
||||
label: `${friendlyName} (${template.language || 'en'})`,
|
||||
template: template,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const selectedTemplate = computed(() => {
|
||||
if (!state.templateId) return null;
|
||||
return templateOptions.value.find(option => option.value === state.templateId)
|
||||
?.template;
|
||||
});
|
||||
|
||||
const audienceList = computed(() => {
|
||||
return (
|
||||
formState.labels.value?.map(label => ({
|
||||
value: label.id,
|
||||
label: label.title,
|
||||
})) ?? []
|
||||
);
|
||||
});
|
||||
|
||||
const updateStateFromCampaign = campaign => {
|
||||
state.title = campaign.title || '';
|
||||
state.description = campaign.description || '';
|
||||
state.message = campaign.message || '';
|
||||
state.templateId = campaign.template_params?.name
|
||||
? templateOptions.value.find(
|
||||
opt => opt.template.name === campaign.template_params.name
|
||||
)?.value || null
|
||||
: null;
|
||||
state.selectedAudience = campaign.audience?.map(aud => aud.id) || [];
|
||||
state.scheduledAt = campaign.scheduled_at || null;
|
||||
state.campaignType = campaign.campaign_type || 'ongoing';
|
||||
};
|
||||
|
||||
const handleBasicDetailsUpdate = async () => {
|
||||
const result = await Promise.all([v$.value.title.$validate()]).then(results =>
|
||||
results.every(Boolean)
|
||||
);
|
||||
if (!result) return;
|
||||
|
||||
const payload = {
|
||||
title: state.title,
|
||||
description: state.description,
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleSelectTemplateUpdate = async () => {
|
||||
const result = await v$.value.templateId.$validate();
|
||||
if (!result) return;
|
||||
|
||||
const currentTemplate = selectedTemplate.value;
|
||||
const parserData = templateParserRef.value;
|
||||
|
||||
const templateContent = parserData?.renderedTemplate || '';
|
||||
const templateParams = {
|
||||
name: currentTemplate?.name || '',
|
||||
namespace: currentTemplate?.namespace || '',
|
||||
category: currentTemplate?.category || 'UTILITY',
|
||||
language: currentTemplate?.language || 'en_US',
|
||||
processed_params: parserData?.processedParams || {},
|
||||
};
|
||||
|
||||
const payload = {
|
||||
message: templateContent,
|
||||
template_params: templateParams,
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleSelectAudienceUpdate = async () => {
|
||||
const result = await v$.value.selectedAudience.$validate();
|
||||
if (!result) return;
|
||||
|
||||
const payload = {
|
||||
audience: state.selectedAudience?.map(id => ({
|
||||
id,
|
||||
type: 'Label',
|
||||
})),
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleScheduleTemplateUpdate = () => {
|
||||
const payload = {
|
||||
scheduled_at: state.scheduledAt,
|
||||
campaign_type: state.campaignType,
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.campaign,
|
||||
newCampaign => {
|
||||
if (props.mode === 'edit' && newCampaign) {
|
||||
updateStateFromCampaign(newCampaign);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
|
||||
<!-- Basic Details Section -->
|
||||
<Accordion :title="t('CAMPAIGN.FORM.SECTIONS.BASIC_DETAILS')" is-open>
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<Input
|
||||
v-model="state.title"
|
||||
:label="t('CAMPAIGN.FORM.TITLE.LABEL')"
|
||||
:placeholder="t('CAMPAIGN.FORM.TITLE.PLACEHOLDER')"
|
||||
:message="formErrors.title"
|
||||
:message-type="formErrors.title ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-model="state.description"
|
||||
:label="t('CAMPAIGN.FORM.DESCRIPTION.LABEL')"
|
||||
:placeholder="t('CAMPAIGN.FORM.DESCRIPTION.PLACEHOLDER')"
|
||||
/>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
:loading="isLoading"
|
||||
@click="handleBasicDetailsUpdate"
|
||||
>
|
||||
{{ t('CAMPAIGN.FORM.UPDATE') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
<!-- Select Template Section -->
|
||||
<Accordion :title="t('CAMPAIGN.FORM.SECTIONS.SELECT_TEMPLATE')">
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label
|
||||
for="template"
|
||||
class="mb-0.5 text-sm font-medium text-n-slate-12"
|
||||
>
|
||||
{{ t('CAMPAIGN.FORM.TEMPLATE.LABEL') }}
|
||||
</label>
|
||||
<ComboBox
|
||||
id="template"
|
||||
v-model="state.templateId"
|
||||
:options="templateOptions"
|
||||
:has-error="!!formErrors.template"
|
||||
:placeholder="t('CAMPAIGN.FORM.TEMPLATE.SELECT')"
|
||||
:message="formErrors.template"
|
||||
class="[&>div>button]:bg-n-alpha-black2 [&>div>button:not(.focused)]:dark:outline-n-weak [&>div>button:not(.focused)]:hover:!outline-n-slate-6"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-n-slate-11">
|
||||
{{ t('CAMPAIGN.FORM.TEMPLATE.INFO') }}
|
||||
</p>
|
||||
<div class="mt-4 text-xs italic text-gray-500">
|
||||
{{ t('CAMPAIGN.PLAYGROUND.PREVIEW_NOTE') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Template Parser -->
|
||||
<WhatsAppTemplateParser
|
||||
v-if="selectedTemplate"
|
||||
ref="templateParserRef"
|
||||
:template="selectedTemplate"
|
||||
/>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
:loading="isLoading"
|
||||
@click="handleSelectTemplateUpdate"
|
||||
>
|
||||
{{ t('CAMPAIGN.FORM.UPDATE') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
<!-- Select Audience Section -->
|
||||
<Accordion :title="t('CAMPAIGN.FORM.SECTIONS.SELECT_AUDIENCE')">
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label
|
||||
for="audience"
|
||||
class="mb-0.5 text-sm font-medium text-n-slate-12"
|
||||
>
|
||||
{{ t('CAMPAIGN.FORM.AUDIENCE.LABEL') }}
|
||||
</label>
|
||||
<TagMultiSelectComboBox
|
||||
v-model="state.selectedAudience"
|
||||
:options="audienceList"
|
||||
:label="t('CAMPAIGN.FORM.AUDIENCE.LABEL')"
|
||||
:placeholder="t('CAMPAIGN.FORM.AUDIENCE.SELECT')"
|
||||
:has-error="!!formErrors.audience"
|
||||
:message="formErrors.audience"
|
||||
class="[&>div>button]:bg-n-alpha-black2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
:loading="isLoading"
|
||||
@click="handleSelectAudienceUpdate"
|
||||
>
|
||||
{{ t('CAMPAIGN.FORM.UPDATE') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
<!-- Schedule Template Section -->
|
||||
<Accordion :title="t('CAMPAIGN.FORM.SECTIONS.SCHEDULE_TEMPLATE')">
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAMPAIGN.FORM.SCHEDULED_AT.LABEL') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="state.scheduledAt"
|
||||
type="datetime-local"
|
||||
class="p-2 w-full rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
:loading="isLoading"
|
||||
@click="handleScheduleTemplateUpdate"
|
||||
>
|
||||
{{ t('CAMPAIGN.FORM.UPDATE') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
</form>
|
||||
</template>
|
||||
@@ -201,6 +201,61 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"SUCCESS_MESSAGE": "Campaign updated successfully",
|
||||
"ERROR_MESSAGE": "There was an error. Please try again.",
|
||||
"NOT_FOUND": "Campaign not found"
|
||||
},
|
||||
"FORM": {
|
||||
"SECTIONS": {
|
||||
"BASIC_DETAILS": "Basic Details",
|
||||
"SELECT_TEMPLATE": "Select Template",
|
||||
"SELECT_AUDIENCE": "Select Audience",
|
||||
"SCHEDULE_TEMPLATE": "Schedule Template"
|
||||
},
|
||||
"TITLE": {
|
||||
"LABEL": "Campaign Title",
|
||||
"PLACEHOLDER": "Enter campaign title"
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Description",
|
||||
"PLACEHOLDER": "Enter campaign description"
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Message",
|
||||
"PLACEHOLDER": "Enter your campaign message"
|
||||
},
|
||||
"TEMPLATE": {
|
||||
"LABEL": "Template",
|
||||
"SELECT": "Select a template",
|
||||
"INFO": "Select a template to use for this campaign."
|
||||
},
|
||||
"AUDIENCE": {
|
||||
"LABEL": "Audience",
|
||||
"SELECT": "Select audience"
|
||||
},
|
||||
"CAMPAIGN_TYPE": {
|
||||
"LABEL": "Campaign Type",
|
||||
"ONGOING": "Ongoing",
|
||||
"ONE_OFF": "One-off"
|
||||
},
|
||||
"SCHEDULED_AT": {
|
||||
"LABEL": "Schedule Time"
|
||||
},
|
||||
"UPDATE": "Update"
|
||||
},
|
||||
"PLAYGROUND": {
|
||||
"TITLE": "Template Preview",
|
||||
"CAMPAIGN_TITLE": "Campaign Title",
|
||||
"MESSAGE_PREVIEW": "Message Preview",
|
||||
"TEMPLATE": "Template",
|
||||
"AUDIENCE": "Audience",
|
||||
"CAMPAIGN_TYPE": "Campaign Type",
|
||||
"NO_TITLE": "No title set",
|
||||
"NO_MESSAGE": "No message set",
|
||||
"NO_TYPE": "No type set",
|
||||
"PREVIEW_NOTE": "This is a preview of how your campaign will appear to recipients."
|
||||
},
|
||||
"CONFIRM_DELETE": {
|
||||
"TITLE": "Are you sure to delete?",
|
||||
"DESCRIPTION": "The delete action is permanent and cannot be reversed.",
|
||||
|
||||
@@ -4,6 +4,7 @@ import CampaignsPageRouteView from './pages/CampaignsPageRouteView.vue';
|
||||
import LiveChatCampaignsPage from './pages/LiveChatCampaignsPage.vue';
|
||||
import SMSCampaignsPage from './pages/SMSCampaignsPage.vue';
|
||||
import WhatsAppCampaignsPage from './pages/WhatsAppCampaignsPage.vue';
|
||||
import CampaignEdit from './pages/CampaignEdit.vue';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
const meta = {
|
||||
@@ -62,6 +63,17 @@ const campaignsRoutes = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/campaigns/whatsapp/:campaignId/edit'
|
||||
),
|
||||
name: 'campaigns_whatsapp_edit',
|
||||
meta: {
|
||||
...meta,
|
||||
featureFlag: FEATURE_FLAGS.WHATSAPP_CAMPAIGNS,
|
||||
},
|
||||
component: CampaignEdit,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import EditCampaignForm from 'dashboard/components-next/captain/pageComponents/campaign/EditCampaignForm.vue';
|
||||
import CampaignPlayground from 'dashboard/components-next/captain/campaign/CampaignPlayground.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
const campaignId = route.params.campaignId;
|
||||
const uiFlags = useMapGetter('campaigns/getUIFlags');
|
||||
const isFetching = computed(() => uiFlags.value.fetchingItem);
|
||||
const allCampaigns = computed(() => store.getters['campaigns/getAllCampaigns']);
|
||||
const campaign = computed(() =>
|
||||
allCampaigns.value.find(c => c.id === Number(campaignId))
|
||||
);
|
||||
|
||||
const isCampaignAvailable = computed(() => !!campaign.value?.id);
|
||||
|
||||
const campaignStatus = computed(() => {
|
||||
if (!campaign.value) return '';
|
||||
|
||||
// For WhatsApp campaigns, check if it's completed or scheduled
|
||||
const STATUS_COMPLETED = 'completed';
|
||||
|
||||
return campaign.value.campaign_status === STATUS_COMPLETED
|
||||
? t('CAMPAIGN.WHATSAPP.CARD.STATUS.COMPLETED')
|
||||
: t('CAMPAIGN.WHATSAPP.CARD.STATUS.SCHEDULED');
|
||||
});
|
||||
|
||||
const statusTextColor = computed(() => {
|
||||
if (!campaign.value) return 'text-n-slate-12';
|
||||
|
||||
const STATUS_COMPLETED = 'completed';
|
||||
const isActive = campaign.value.campaign_status !== STATUS_COMPLETED;
|
||||
|
||||
return !isActive ? 'text-n-teal-11' : 'text-n-slate-12';
|
||||
});
|
||||
|
||||
const handleSubmit = async updatedCampaign => {
|
||||
try {
|
||||
await store.dispatch('campaigns/update', {
|
||||
id: campaignId,
|
||||
...updatedCampaign,
|
||||
});
|
||||
useAlert(t('CAMPAIGN.EDIT.SUCCESS_MESSAGE'));
|
||||
} catch (error) {
|
||||
const errorMessage = error?.message || t('CAMPAIGN.EDIT.ERROR_MESSAGE');
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (!isCampaignAvailable.value) {
|
||||
store.dispatch('campaigns/get');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:show-pagination-footer="false"
|
||||
:is-fetching="isFetching"
|
||||
:show-know-more="false"
|
||||
:back-url="{ name: 'campaigns_ongoing_index' }"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<div class="flex gap-2 items-center">
|
||||
<span>{{ campaign?.title }}</span>
|
||||
<span
|
||||
class="inline-flex items-center px-2 py-0.5 h-6 text-xs font-medium rounded-md bg-n-alpha-2"
|
||||
:class="statusTextColor"
|
||||
>
|
||||
{{ campaignStatus }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="!isCampaignAvailable">
|
||||
{{ t('CAMPAIGN.EDIT.NOT_FOUND') }}
|
||||
</div>
|
||||
<div v-else class="flex gap-4 h-full">
|
||||
<div class="flex-1 pr-4 h-full lg:overflow-auto md:h-auto">
|
||||
<EditCampaignForm
|
||||
:campaign="campaign"
|
||||
mode="edit"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-[400px] hidden lg:block h-full">
|
||||
<CampaignPlayground :campaign-id="Number(campaignId)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PageLayout>
|
||||
</template>
|
||||
Reference in New Issue
Block a user