PluginProbe ʕ •ᴥ•ʔ
Hostinger Reach – AI-Powered Email Marketing for WordPress / 1.8.0
Hostinger Reach – AI-Powered Email Marketing for WordPress v1.8.0
1.8.0 1.7.2 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 6 days ago skeletons 5 months ago ActionButton.vue 5 months ago ActionButtonsSection.vue 5 months ago Banner.vue 6 days ago FAQ.vue 5 months ago FormItem.vue 6 days ago FormsSection.vue 11 months ago Hero.vue 1 month ago Integrations.vue 6 days ago Pagination.vue 11 months ago PluginEntriesTable.vue 6 days ago PluginEntry.vue 6 days ago PluginExpansion.vue 8 months ago ReachContactsSummary.vue 1 month ago RecommendedPlugins.vue 6 days ago SyncStatusLabel.vue 7 months ago Tabs.vue 10 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 month ago WooCommerceEntriesTable.vue 5 months ago
PluginEntry.vue
372 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 :close-other-popovers-on-open="true"
122 >
123 <template #trigger>
124 <button class="plugin-entry-row__action-button">
125 <HIcon name="ic-dots-vertical-16" />
126 </button>
127 </template>
128 <div class="plugin-entry-row__popover-menu">
129 <div
130 v-if="!!props.integration.addFormUrl"
131 class="plugin-entry-row__menu-item"
132 @click="emit('addForm', props.integration.id)"
133 >
134 <HIcon name="ic-plus-16" />
135 <span>{{ translate('hostinger_reach_plugin_entries_table_add_form') }}</span>
136 <HIcon name="ic-arrow-up-right-square-16" />
137 </div>
138 <div v-if="canSync" class="plugin-entry-row__menu-item" @click="handleContactSyncClick(props.integration)">
139 <HIcon name="ic-arrows-circle-16" />
140 <span>{{ translate('hostinger_reach_sync_contacts_button_text') }}</span>
141 </div>
142 <div
143 v-if="props.integration.isGoToPluginVisible"
144 class="plugin-entry-row__menu-item"
145 @click="emit('goToPlugin', props.integration.id)"
146 >
147 <HIcon name="ic-blocks-plus-16" />
148 <span>{{ translate('hostinger_reach_plugin_entries_table_go_to_plugin') }}</span>
149 <HIcon name="ic-arrow-up-right-square-16" />
150 </div>
151 <div
152 v-if="props.integration.canDeactivate"
153 class="plugin-entry-row__menu-item"
154 @click="emit('disconnectPlugin', props.integration.id)"
155 >
156 <HIcon name="ic-cross-circle-16" />
157 <span>{{ translate('hostinger_reach_plugin_entries_table_disconnect_plugin') }}</span>
158 </div>
159 </div>
160 </HPopover>
161 </div>
162 </div>
163 <div v-if="isExpanded" class="plugin-entry-row__expansion">
164 <PluginExpansion
165 :integration="props.integration"
166 :forms="forms"
167 @toggle-form-status="handleToggleFormStatus"
168 @view-form="handleViewForm"
169 @edit-form="handleEditForm"
170 />
171 </div>
172 </div>
173 </template>
174
175 <style scoped lang="scss">
176 .plugin-entry-row {
177 &__main {
178 display: flex;
179 padding: 16px 16px 16px 0;
180 }
181
182 &__cell {
183 display: flex;
184 align-items: center;
185
186 &--plugin {
187 width: 39%;
188 order: 1;
189 }
190
191 &--forms {
192 width: 21%;
193 order: 2;
194 }
195
196 &--actions {
197 width: 40%;
198 display: flex;
199 justify-content: flex-end;
200 order: 3;
201 }
202 }
203
204 &__action-button {
205 background: var(--neutral--0);
206 border: 1px solid var(--neutral--200);
207 border-radius: 8px;
208 padding: 0 8px;
209 height: 32px;
210 display: flex;
211 align-items: center;
212 justify-content: center;
213 cursor: pointer;
214 transition: all 0.2s ease;
215
216 &:hover {
217 border-color: var(--neutral--300);
218 }
219 }
220
221 &__plugin-content {
222 display: flex;
223 align-items: center;
224 gap: 8px;
225 cursor: pointer;
226 width: 100%;
227 }
228
229 &__expand-button {
230 background: none;
231 border: none;
232 cursor: pointer;
233 padding: 0;
234 display: flex;
235 align-items: center;
236 justify-content: center;
237 width: 16px;
238 height: 16px;
239 }
240
241 &__plugin-info {
242 display: flex;
243 align-items: center;
244 gap: 12px;
245 }
246
247 &__plugin-icon {
248 width: 28px;
249 height: 28px;
250 border-radius: 7px;
251 overflow: hidden;
252 display: flex;
253 align-items: center;
254 justify-content: center;
255 background: var(--neutral--100);
256
257 img {
258 width: 100%;
259 height: 100%;
260 object-fit: contain;
261 }
262 }
263
264 &__plugin-name {
265 font-weight: 700;
266 font-size: 14px;
267 color: var(--neutral--500);
268 }
269
270 &__entries-count {
271 font-weight: 400;
272 font-size: 14px;
273 color: var(--neutral--500);
274 display: flex;
275 gap: 4px;
276 }
277
278 &__status-content {
279 display: flex;
280 align-items: center;
281 gap: 5px;
282
283 &[data-availabe='false']='false'] {
284 color: var(--neutral--300);
285 opacity: 0.7;
286 }
287
288 &[data-status='importing']='importing'] {
289 svg {
290 animation: spin 2.5s linear infinite;
291 }
292 }
293 }
294
295 &__status-label {
296 font-size: 12px;
297 }
298
299 &__mobile-label {
300 font-weight: 500;
301 font-size: 14px;
302 color: var(--neutral--600);
303 margin-right: 8px;
304 display: none;
305 }
306
307 &__expansion {
308 width: 100%;
309 height: 100%;
310 }
311
312 &__popover-menu {
313 padding: 4px;
314 min-width: 180px;
315 }
316
317 &__menu-item {
318 display: flex;
319 align-items: center;
320 gap: 8px;
321 padding: 12px;
322 cursor: pointer;
323 border-radius: 8px;
324 font-weight: 500;
325 font-size: 14px;
326 color: var(--neutral--600);
327 transition: background-color 0.2s ease;
328
329 &:hover {
330 background-color: var(--neutral--50);
331 }
332
333 span {
334 flex: 1;
335 }
336 }
337
338 @media (max-width: 1023px) {
339 &__main {
340 flex-direction: column;
341 gap: 12px;
342 padding: 16px;
343 border-radius: 12px;
344 background: var(--neutral--100);
345 margin-bottom: 12px;
346 }
347
348 &__cell--plugin,
349 &__cell--forms,
350 &__cell--status,
351 &__cell--actions {
352 width: 100%;
353 justify-content: flex-start;
354 }
355
356 &__cell--forms,
357 &__cell--status {
358 align-items: flex-start;
359 }
360
361 &__cell--actions {
362 padding-right: 0;
363 justify-content: flex-end;
364 }
365
366 &__mobile-label {
367 display: inline-block;
368 }
369 }
370 }
371 </style>
372