PluginProbe ʕ •ᴥ•ʔ
Hostinger Reach – AI-Powered Email Marketing for WordPress / 1.7.1
Hostinger Reach – AI-Powered Email Marketing for WordPress v1.7.1
1.7.1 1.7.0 1.6.0 1.5.9 1.5.8 1.5.7 1.5.6 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 trunk 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6
hostinger-reach / frontend / vue / components / PluginEntry.vue
hostinger-reach / frontend / vue / components Last commit date
Modals 1 month ago skeletons 4 months ago ActionButton.vue 5 months ago ActionButtonsSection.vue 5 months ago Banner.vue 3 months ago FAQ.vue 5 months ago FormItem.vue 1 month ago FormsSection.vue 11 months ago Hero.vue 3 weeks ago Integrations.vue 4 months ago Pagination.vue 10 months ago PluginEntriesTable.vue 3 months ago PluginEntry.vue 4 months ago PluginExpansion.vue 8 months ago ReachContactsSummary.vue 1 week ago SyncStatusLabel.vue 6 months ago Tabs.vue 9 months ago Toggle.vue 10 months ago UsageCard.vue 10 months ago UsageCardsRow.vue 1 year ago UsageCardsSection.vue 1 year ago WooCommerce.vue 1 week ago WooCommerceEntriesTable.vue 4 months ago
PluginEntry.vue
371 lines
1 <script setup lang="ts">
2 import { HIcon, HPopover } from '@hostinger/hcomponents';
3 import { computed, ref } from 'vue';
4
5 import PluginExpansion from '@/components/PluginExpansion.vue';
6 import { useModal } from '@/composables';
7 import { type PluginStatus } from '@/data/pluginData';
8 import { ModalName } from '@/types';
9 import type { Form, Integration } from '@/types/models';
10 import { translate } from '@/utils/translate';
11
12 interface Props {
13 integration: Integration;
14 pluginStatus: PluginStatus;
15 forms: Form[];
16 initiallyExpanded: boolean;
17 }
18
19 const props = defineProps<Props>();
20 const { openModal } = useModal();
21
22 const emit = defineEmits<{
23 toggleFormStatus: [form: Form, status: boolean];
24 goToPlugin: [id: string];
25 disconnectPlugin: [id: string];
26 viewForm: [form: Form];
27 editForm: [form: Form];
28 addForm: [id: string];
29 }>();
30
31 const isExpanded = ref(props.initiallyExpanded ?? false);
32
33 const toggleExpansion = () => {
34 isExpanded.value = !isExpanded.value;
35 };
36
37 const handleToggleFormStatus = (form: Form, status: boolean) => {
38 emit('toggleFormStatus', form, status);
39 };
40
41 const handleContactSyncClick = (integration: Integration) => {
42 openModal(
43 ModalName.SYNC_CONTACTS_MODAL,
44 {
45 title: translate('hostinger_reach_contacts_modal_title'),
46 subtitle: translate('hostinger_reach_contacts_modal_subtitle'),
47 data: { integrations: [integration] }
48 },
49 { hasCloseButton: true }
50 );
51 };
52
53 const handleViewForm = (form: Form) => {
54 emit('viewForm', form);
55 };
56
57 const handleEditForm = (form: Form) => {
58 emit('editForm', form);
59 };
60
61 const showPopover = computed(
62 () => props.integration.canDeactivate || !!props.integration.addFormUrl || props.integration.isGoToPluginVisible
63 );
64
65 const activeForms = computed(() => props.forms.filter((form) => form.isActive));
66
67 const canSync = computed(
68 () =>
69 props.integration.importEnabled && props.integration.forms?.length > 0 && props.integration.importStatus.total > 0
70 );
71
72 const expandButtonAriaLabel = computed(() => {
73 const translationKey = isExpanded.value
74 ? 'hostinger_reach_plugin_entries_table_collapse_aria'
75 : 'hostinger_reach_plugin_entries_table_expand_aria';
76
77 return translate(translationKey).replace('{pluginName}', props.integration.title);
78 });
79 </script>
80
81 <template>
82 <div class="plugin-entry-row">
83 <div class="plugin-entry-row__main">
84 <div class="plugin-entry-row__cell plugin-entry-row__cell--plugin">
85 <div class="plugin-entry-row__plugin-content" @click="toggleExpansion">
86 <button
87 class="plugin-entry-row__expand-button"
88 :aria-expanded="isExpanded"
89 :aria-controls="`plugin-expansion-${props.integration.id}`"
90 :aria-label="expandButtonAriaLabel"
91 @click.stop="toggleExpansion"
92 >
93 <HIcon :name="isExpanded ? 'ic-chevron-down-16' : 'ic-chevron-right-16'" dimensions="16px" />
94 </button>
95 <div class="plugin-entry-row__plugin-info">
96 <div class="plugin-entry-row__plugin-icon">
97 <img :src="props.integration.icon" :alt="props.integration.title + ' icon'" />
98 </div>
99 <span class="plugin-entry-row__plugin-name">{{ props.integration.title }}</span>
100 </div>
101 </div>
102 </div>
103 <div class="plugin-entry-row__cell plugin-entry-row__cell--forms">
104 <span class="plugin-entry-row__mobile-label">
105 {{ translate('hostinger_reach_plugin_entries_table_syncing_header') }}:
106 </span>
107 <span class="plugin-entry-row__entries-count">
108 <span>{{ activeForms.length }}</span>
109 <span>{{ translate('hostinger_reach_plugin_entries_table_of') }}</span>
110 <span>{{ props.integration.forms?.length }}</span>
111 </span>
112 </div>
113 <div class="plugin-entry-row__cell plugin-entry-row__cell--actions">
114 <HPopover
115 v-if="showPopover"
116 placement="bottom-end"
117 :show-arrow="false"
118 background-color="neutral--0"
119 border-radius="12px"
120 :outside-click-enabled="true"
121 >
122 <template #trigger>
123 <button class="plugin-entry-row__action-button">
124 <HIcon name="ic-dots-vertical-16" />
125 </button>
126 </template>
127 <div class="plugin-entry-row__popover-menu">
128 <div
129 v-if="!!props.integration.addFormUrl"
130 class="plugin-entry-row__menu-item"
131 @click="emit('addForm', props.integration.id)"
132 >
133 <HIcon name="ic-plus-16" />
134 <span>{{ translate('hostinger_reach_plugin_entries_table_add_form') }}</span>
135 <HIcon name="ic-arrow-up-right-square-16" />
136 </div>
137 <div v-if="canSync" class="plugin-entry-row__menu-item" @click="handleContactSyncClick(props.integration)">
138 <HIcon name="ic-arrows-circle-16" />
139 <span>{{ translate('hostinger_reach_sync_contacts_button_text') }}</span>
140 </div>
141 <div
142 v-if="props.integration.isGoToPluginVisible"
143 class="plugin-entry-row__menu-item"
144 @click="emit('goToPlugin', props.integration.id)"
145 >
146 <HIcon name="ic-blocks-plus-16" />
147 <span>{{ translate('hostinger_reach_plugin_entries_table_go_to_plugin') }}</span>
148 <HIcon name="ic-arrow-up-right-square-16" />
149 </div>
150 <div
151 v-if="props.integration.canDeactivate"
152 class="plugin-entry-row__menu-item"
153 @click="emit('disconnectPlugin', props.integration.id)"
154 >
155 <HIcon name="ic-cross-circle-16" />
156 <span>{{ translate('hostinger_reach_plugin_entries_table_disconnect_plugin') }}</span>
157 </div>
158 </div>
159 </HPopover>
160 </div>
161 </div>
162 <div v-if="isExpanded" class="plugin-entry-row__expansion">
163 <PluginExpansion
164 :integration="props.integration"
165 :forms="forms"
166 @toggle-form-status="handleToggleFormStatus"
167 @view-form="handleViewForm"
168 @edit-form="handleEditForm"
169 />
170 </div>
171 </div>
172 </template>
173
174 <style scoped lang="scss">
175 .plugin-entry-row {
176 &__main {
177 display: flex;
178 padding: 16px 16px 16px 0;
179 }
180
181 &__cell {
182 display: flex;
183 align-items: center;
184
185 &--plugin {
186 width: 39%;
187 order: 1;
188 }
189
190 &--forms {
191 width: 21%;
192 order: 2;
193 }
194
195 &--actions {
196 width: 40%;
197 display: flex;
198 justify-content: flex-end;
199 order: 3;
200 }
201 }
202
203 &__action-button {
204 background: var(--neutral--0);
205 border: 1px solid var(--neutral--200);
206 border-radius: 8px;
207 padding: 0 8px;
208 height: 32px;
209 display: flex;
210 align-items: center;
211 justify-content: center;
212 cursor: pointer;
213 transition: all 0.2s ease;
214
215 &:hover {
216 border-color: var(--neutral--300);
217 }
218 }
219
220 &__plugin-content {
221 display: flex;
222 align-items: center;
223 gap: 8px;
224 cursor: pointer;
225 width: 100%;
226 }
227
228 &__expand-button {
229 background: none;
230 border: none;
231 cursor: pointer;
232 padding: 0;
233 display: flex;
234 align-items: center;
235 justify-content: center;
236 width: 16px;
237 height: 16px;
238 }
239
240 &__plugin-info {
241 display: flex;
242 align-items: center;
243 gap: 12px;
244 }
245
246 &__plugin-icon {
247 width: 28px;
248 height: 28px;
249 border-radius: 7px;
250 overflow: hidden;
251 display: flex;
252 align-items: center;
253 justify-content: center;
254 background: var(--neutral--100);
255
256 img {
257 width: 100%;
258 height: 100%;
259 object-fit: contain;
260 }
261 }
262
263 &__plugin-name {
264 font-weight: 700;
265 font-size: 14px;
266 color: var(--neutral--500);
267 }
268
269 &__entries-count {
270 font-weight: 400;
271 font-size: 14px;
272 color: var(--neutral--500);
273 display: flex;
274 gap: 4px;
275 }
276
277 &__status-content {
278 display: flex;
279 align-items: center;
280 gap: 5px;
281
282 &[data-availabe='false']='false'] {
283 color: var(--neutral--300);
284 opacity: 0.7;
285 }
286
287 &[data-status='importing']='importing'] {
288 svg {
289 animation: spin 2.5s linear infinite;
290 }
291 }
292 }
293
294 &__status-label {
295 font-size: 12px;
296 }
297
298 &__mobile-label {
299 font-weight: 500;
300 font-size: 14px;
301 color: var(--neutral--600);
302 margin-right: 8px;
303 display: none;
304 }
305
306 &__expansion {
307 width: 100%;
308 height: 100%;
309 }
310
311 &__popover-menu {
312 padding: 4px;
313 min-width: 180px;
314 }
315
316 &__menu-item {
317 display: flex;
318 align-items: center;
319 gap: 8px;
320 padding: 12px;
321 cursor: pointer;
322 border-radius: 8px;
323 font-weight: 500;
324 font-size: 14px;
325 color: var(--neutral--600);
326 transition: background-color 0.2s ease;
327
328 &:hover {
329 background-color: var(--neutral--50);
330 }
331
332 span {
333 flex: 1;
334 }
335 }
336
337 @media (max-width: 1023px) {
338 &__main {
339 flex-direction: column;
340 gap: 12px;
341 padding: 16px;
342 border-radius: 12px;
343 background: var(--neutral--100);
344 margin-bottom: 12px;
345 }
346
347 &__cell--plugin,
348 &__cell--forms,
349 &__cell--status,
350 &__cell--actions {
351 width: 100%;
352 justify-content: flex-start;
353 }
354
355 &__cell--forms,
356 &__cell--status {
357 align-items: flex-start;
358 }
359
360 &__cell--actions {
361 padding-right: 0;
362 justify-content: flex-end;
363 }
364
365 &__mobile-label {
366 display: inline-block;
367 }
368 }
369 }
370 </style>
371