1<template>
2 <SettingsLayoutBase>
3 <template #title>
4 <div class="flex items-center">
5 <div
6 class="ps-5 pe-2 relative text-ink-gray-7 hover:opacity-70 min-h-8 flex items-center"
7 >
8 <button
9 type="button"
10 @click="internalOnBack"
11 class="absolute top-0 -start-[0.375rem] w-full h-full"
12 >
13 <span class="sr-only">{{ __("back to email event list") }}</span>
14 <LucideChevronLeft class="w-4.5 h-4.5 rtl:rotate-180" />
15 </button>
16 <h1 class="text-xl-semibold">
17 {{ props.title }}
18 </h1>
19 </div>
20 <UnsavedBadge :show="unsavedChanges" />
21 </div>
22 </template>
23 <template #header-actions>
24 <div
25 :inert="notificationDataResource.loading"
26 class="flex items-center gap-x-4 pt-[0.125rem]"
27 :class="{ invisible: notificationDataResource.loading }"
28 >
29 <Switch
30 size="sm"
31 :label="__('Enabled')"
32 v-model="enabled"
33 @update:model-value="(val) => setUnsavedChanges(val)"
34 :style="{ background: 'transparent', padding: '0px' }"
35 class="flex-row-reverse gap-x-2 ps-0"
36 />
37 <Button
38 type="button"
39 :label="__('Save')"
40 theme="gray"
41 variant="solid"
42 :disabled="!unsavedChanges"
43 :loading="props.submitting"
44 @click="props.onSubmit"
45 />
46 </div>
47 </template>
48 <template #content>
49 <div
50 class="flex flex-col gap-8 flex-grow pb-8"
51 :class="{
52 'items-center justify-center': notificationDataResource.loading,
53 }"
54 >
55 <template v-if="!notificationDataResource.loading">
56 <slot name="formFields"></slot>
57 <div class="flex flex-col gap-2">
58 <FormControl
59 type="textarea"
60 size="sm"
61 :label="__('Email Content')"
62 :required="true"
63 :rows="10"
64 v-model="content"
65 @update:model-value="
66 (value) => setUnsavedChanges(undefined, value)
67 "
68 />
69 <div class="flex gap-x-1 items-start justify-between">
70 <p class="text-sm text-ink-gray-7 leading-5">
71 {{
72 __(
73 "Find out all of the variables that can be used in the content"
74 )
75 }}
76 <a
77 :href="props.documentationLink"
78 target="_blank"
79 class="underline font-semibold"
80 >{{ __("here") }}</a
81 >
82 </p>
83 <Button
84 :disabled="content === defaultContent"
85 type="button"
86 size="sm"
87 variant="subtle"
88 @click="onResetContent"
89 class="w-fit"
90 >
91 {{ __("Reset Content") }}
92 </Button>
93 </div>
94 </div>
95 </template>
96 <LoadingIndicator v-else class="w-4" />
97 </div>
98 </template>
99 </SettingsLayoutBase>
100
101 <ConfirmDialog
102 v-model="showUnsavedConfirm"
103 :title="__('Unsaved changes')"
104 :message="
105 __('Are you sure you want to go back? Unsaved changes will be lost.')
106 "
107 :onConfirm="
108 () => {
109 disableSettingModalOutsideClick = false;
110 props.onBack();
111 }
112 "
113 />
114 <ConfirmDialog
115 v-model="showContentChangeConfirm"
116 :title="__('Reset content')"
117 :message="
118 __(
119 'Are you sure you want to reset the content? Current content will be overridden.'
120 )
121 "
122 :onConfirm="
123 () => {
124 resetContent();
125 showContentChangeConfirm = false;
126 }
127 "
128 />
129</template>
130
131<script setup lang="ts">
132import { ref, watch } from "vue";
133import type { NotificationName } from "./types";
134import { createResource, Switch, LoadingIndicator, Button } from "frappe-ui";
135import ConfirmDialog from "@/components/ConfirmDialog.vue";
136import { disableSettingModalOutsideClick } from "../settingsModal";
137import SettingsLayoutBase from "@/components/layouts/SettingsLayoutBase.vue";
138import UnsavedBadge from "@/components/UnsavedBadge.vue";
139
140const props = defineProps<{
141 title: string;
142 description: string;
143 documentationLink: string;
144 defaultContent: string;
145 onBack: () => void;
146 onSubmit: (e: Event & { target: HTMLFormElement }) => void;
147 onGetDataSuccess: (data: any) => void;
148 submitting: boolean;
149 name: NotificationName;
150}>();
151
152const content = defineModel<string>("content", { required: true });
153const enabled = defineModel<boolean>("enabled", { required: true });
154
155const unsavedChanges = ref(false);
156const slotFieldChanged = ref(false);
157const showUnsavedConfirm = ref(false);
158const showContentChangeConfirm = ref(false);
159
160const notificationDataResource = createResource({
161 url: "opendesk.api.settings.email_notifications.get_data",
162 method: "GET",
163 params: {
164 notification: props.name,
165 },
166 auto: true,
167 onSuccess: props.onGetDataSuccess,
168});
169
170function setUnsavedChanges(
171 newEnabled = enabled.value,
172 newContent = content.value
173) {
174 unsavedChanges.value =
175 slotFieldChanged.value ||
176 newEnabled !== notificationDataResource.data.enabled ||
177 newContent !== notificationDataResource.data.content;
178}
179
180/** For fields a notification adds through `#formFields`, which this component
181 * cannot diff itself. */
182function markUnsavedChanges() {
183 slotFieldChanged.value = true;
184 unsavedChanges.value = true;
185}
186
187function resetUnsavedChanges() {
188 notificationDataResource.data = {
189 ...notificationDataResource.data,
190 enabled: enabled.value,
191 content: content.value,
192 };
193 slotFieldChanged.value = false;
194 unsavedChanges.value = false;
195}
196function resetContent() {
197 content.value = props.defaultContent;
198 setUnsavedChanges();
199}
200
201function onResetContent() {
202 if (content.value !== props.defaultContent) {
203 showContentChangeConfirm.value = true;
204 }
205}
206
207function internalOnBack() {
208 if (unsavedChanges.value) {
209 showUnsavedConfirm.value = true;
210 return;
211 }
212 disableSettingModalOutsideClick.value = false;
213 props.onBack();
214}
215
216watch(
217 () => unsavedChanges.value,
218 (val) => {
219 disableSettingModalOutsideClick.value = val;
220 }
221);
222
223defineExpose({
224 setUnsavedChanges,
225 markUnsavedChanges,
226 resetUnsavedChanges,
227});
228</script>
229