PluginProbe ʕ •ᴥ•ʔ
Hostinger Reach – AI-Powered Email Marketing for WordPress / 1.0.5
Hostinger Reach – AI-Powered Email Marketing for WordPress v1.0.5
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 / PluginEntriesTable.vue
hostinger-reach / frontend / vue / components Last commit date
Modals 9 months ago skeletons 9 months ago ActionButton.vue 9 months ago ActionButtonsSection.vue 9 months ago Banner.vue 9 months ago FAQ.vue 9 months ago FormItem.vue 9 months ago FormsSection.vue 9 months ago Hero.vue 9 months ago PluginEntriesTable.vue 9 months ago PluginEntry.vue 9 months ago PluginExpansion.vue 9 months ago Toggle.vue 9 months ago UsageCard.vue 9 months ago UsageCardsRow.vue 9 months ago UsageCardsSection.vue 9 months ago
PluginEntriesTable.vue
420 lines
1 <script setup lang="ts">
2 import { computed } from 'vue';
3
4 import PluginEntry from '@/components/PluginEntry.vue';
5 import PluginEntrySkeleton from '@/components/skeletons/PluginEntrySkeleton.vue';
6 import { getPluginInfo, PLUGIN_STATUSES, type PluginStatus } from '@/data/pluginData';
7 import type { Form, Integration } from '@/types/models';
8 import { translate } from '@/utils/translate';
9
10 interface PluginEntryData {
11 id: string;
12 name: string;
13 icon: string;
14 entries: number;
15 status: PluginStatus;
16 forms: Form[];
17 }
18
19 interface Props {
20 integrations?: Integration[];
21 isLoading?: boolean;
22 }
23
24 const props = withDefaults(defineProps<Props>(), {
25 integrations: () => [],
26 isLoading: false
27 });
28
29 const emit = defineEmits<{
30 goToPlugin: [id: string];
31 disconnectPlugin: [id: string];
32 toggleFormStatus: [form: Form, status: boolean];
33 viewForm: [form: Form];
34 editForm: [form: Form];
35 }>();
36
37 const SKELETON_COUNT = 3;
38
39 const pluginEntries = computed((): PluginEntryData[] => {
40 if (props.isLoading) return [];
41
42 return props.integrations
43 .filter((integration) => integration.isPluginActive)
44 .map((integration) => {
45 const pluginInfo = getPluginInfo(integration);
46 const forms = integration.forms || [];
47 const totalSubmissions = forms.reduce((sum, form) => sum + (form.submissions || 0), 0);
48
49 return {
50 id: integration.id,
51 name: pluginInfo.title ? translate(pluginInfo.title) : integration.title,
52 icon: pluginInfo.icon || '',
53 entries: totalSubmissions,
54 status: integration.isActive ? PLUGIN_STATUSES.ACTIVE : PLUGIN_STATUSES.INACTIVE,
55 forms
56 } as PluginEntryData;
57 })
58 .filter((pluginEntry) => pluginEntry.status === PLUGIN_STATUSES.ACTIVE);
59 });
60 </script>
61
62 <template>
63 <div class="plugin-entries-table">
64 <div class="plugin-entries-table__container">
65 <div class="plugin-entries-table__header">
66 <div class="plugin-entries-table__header-cell plugin-entries-table__header-cell--plugin">
67 <span class="plugin-entries-table__column-title">
68 {{ translate('hostinger_reach_plugin_entries_table_plugin_header') }}
69 </span>
70 </div>
71 <div class="plugin-entries-table__header-cell plugin-entries-table__header-cell--entries">
72 <span class="plugin-entries-table__column-title">
73 {{ translate('hostinger_reach_plugin_entries_table_entries_header') }}
74 </span>
75 </div>
76 <div class="plugin-entries-table__header-cell plugin-entries-table__header-cell--status">
77 <span class="plugin-entries-table__column-title">
78 {{ translate('hostinger_reach_plugin_entries_table_status_header') }}
79 </span>
80 </div>
81 <div class="plugin-entries-table__header-cell plugin-entries-table__header-cell--actions" />
82 </div>
83
84 <template v-if="isLoading">
85 <PluginEntrySkeleton v-for="i in SKELETON_COUNT" :key="`skeleton-${i}`" />
86 </template>
87
88 <template v-else>
89 <PluginEntry
90 v-for="(pluginEntry, index) in pluginEntries"
91 :key="pluginEntry.id"
92 :plugin-id="pluginEntry.id"
93 :plugin-name="pluginEntry.name"
94 :plugin-icon="pluginEntry.icon"
95 :plugin-status="pluginEntry.status"
96 :total-entries="pluginEntry.entries"
97 :forms="pluginEntry.forms"
98 :class="{
99 'plugin-entries-table__entry-row--with-spacing':
100 pluginEntries.length > 1 && index !== pluginEntries.length - 1
101 }"
102 @toggle-form-status="(form: Form, status: boolean) => emit('toggleFormStatus', form, status)"
103 @view-form="emit('viewForm', $event)"
104 @edit-form="emit('editForm', $event)"
105 @go-to-plugin="emit('goToPlugin', $event)"
106 @disconnect-plugin="emit('disconnectPlugin', $event)"
107 />
108 </template>
109 </div>
110 </div>
111 </template>
112
113 <style scoped lang="scss">
114 .plugin-entries-table {
115 width: inherit;
116
117 &__container {
118 background: var(--neutral--0);
119 border-radius: 20px;
120 padding: 24px;
121 border: 1px solid var(--neutral--200);
122 overflow: hidden;
123 }
124
125 &__header {
126 display: flex;
127 }
128
129 &__header-cell {
130 padding: 12px 0px;
131
132 &--plugin {
133 width: 49%;
134 order: 1;
135 padding-right: 16px;
136 }
137
138 &--entries {
139 width: 19%;
140 order: 2;
141 }
142
143 &--status {
144 width: 21%;
145 order: 3;
146 }
147
148 &--actions {
149 width: 10%;
150 order: 4;
151 }
152 }
153
154 &__column-title {
155 font-weight: 700;
156 font-size: 14px;
157 color: var(--neutral--600);
158 }
159
160 &__row {
161 display: flex;
162 padding: 16px 0px;
163 }
164
165 &__cell {
166 display: flex;
167 align-items: center;
168
169 &--plugin {
170 width: 50%;
171 order: 1;
172 }
173
174 &--entries {
175 width: 20%;
176 order: 2;
177 }
178
179 &--status {
180 width: 20%;
181 order: 3;
182 }
183
184 &--actions {
185 width: 10%;
186 display: flex;
187 justify-content: flex-end;
188 padding-right: 16px;
189 order: 4;
190 }
191 }
192
193 &__plugin-content {
194 display: flex;
195 align-items: center;
196 gap: 8px;
197 cursor: pointer;
198 width: 100%;
199 }
200
201 &__expand-button {
202 background: none;
203 border: none;
204 cursor: pointer;
205 padding: 0;
206 display: flex;
207 align-items: center;
208 justify-content: center;
209 width: 16px;
210 height: 16px;
211 }
212
213 &__plugin-info {
214 display: flex;
215 align-items: center;
216 gap: 12px;
217 }
218
219 &__plugin-icon {
220 width: 28px;
221 height: 28px;
222 border-radius: 7px;
223 overflow: hidden;
224 display: flex;
225 align-items: center;
226 justify-content: center;
227 background: var(--neutral--100);
228
229 img {
230 width: 100%;
231 height: 100%;
232 object-fit: cover;
233 }
234 }
235
236 &__plugin-name {
237 font-weight: 700;
238 font-size: 14px;
239 color: var(--neutral--500);
240 }
241
242 &__entries-count {
243 font-weight: 400;
244 font-size: 14px;
245 color: var(--neutral--500);
246 }
247
248 &__status-content {
249 display: flex;
250 align-items: center;
251 }
252
253 &__status-label {
254 font-size: 12px;
255 }
256
257 &__action-button {
258 background: var(--neutral--0);
259 border: 1px solid var(--neutral--200);
260 border-radius: 8px;
261 padding: 0 8px;
262 height: 32px;
263 display: flex;
264 align-items: center;
265 justify-content: center;
266 cursor: pointer;
267 transition: all 0.2s ease;
268
269 &:hover {
270 border-color: var(--neutral--300);
271 }
272 }
273
274 &__expansion-row {
275 width: 100%;
276 }
277
278 &__expansion-content {
279 background-color: var(--neutral--50);
280 border-radius: 12px;
281 padding: 16px 0;
282 display: flex;
283 flex-direction: column;
284 gap: 8px;
285 }
286
287 &__plugin-toggle-row {
288 display: flex;
289 align-items: center;
290
291 .plugin-entries-table__cell--plugin {
292 width: 50%;
293 padding-left: 16px;
294 }
295
296 .plugin-entries-table__cell--entries {
297 width: 20%;
298 }
299
300 .plugin-entries-table__cell--status {
301 width: 20%;
302 }
303
304 .plugin-entries-table__cell--actions {
305 width: 10%;
306 display: flex;
307 justify-content: flex-end;
308 padding-right: 16px;
309 }
310 }
311
312 &__forms-list {
313 display: flex;
314 flex-direction: column;
315 gap: 4px;
316 }
317
318 &__form-item {
319 display: flex;
320 align-items: center;
321 padding: 16px 0px;
322 border-top: 1px solid var(--neutral--200);
323
324 &:first-child {
325 border-top: none;
326 }
327
328 .plugin-entries-table__cell--plugin {
329 width: 50%;
330 padding-left: 32px;
331 }
332
333 .plugin-entries-table__cell--entries {
334 width: 20%;
335 }
336
337 .plugin-entries-table__cell--status {
338 width: 20%;
339 }
340
341 .plugin-entries-table__cell--actions {
342 width: 10%;
343 display: flex;
344 justify-content: flex-end;
345 padding-right: 16px;
346 }
347 }
348
349 &__form-info {
350 display: flex;
351 flex-direction: column;
352 gap: 2px;
353 }
354
355 &__form-id {
356 font-weight: 500;
357 font-size: 12px;
358 color: var(--neutral--600);
359 font-family: monospace;
360 }
361
362 &__form-title {
363 font-weight: 400;
364 font-size: 11px;
365 color: var(--neutral--500);
366 opacity: 0.8;
367 }
368
369 &__toggle-container {
370 display: flex;
371 align-items: center;
372 gap: 16px;
373 }
374
375 &__toggle-label {
376 font-weight: 400;
377 font-size: 14px;
378 color: var(--neutral--500);
379 }
380
381 &__entries-text {
382 font-weight: 400;
383 font-size: 14px;
384 color: var(--neutral--500);
385 }
386
387 &__popover-menu {
388 padding: 4px;
389 min-width: 180px;
390 }
391
392 &__menu-item {
393 display: flex;
394 align-items: center;
395 gap: 8px;
396 padding: 12px;
397 cursor: pointer;
398 border-radius: 8px;
399 font-weight: 500;
400 font-size: 14px;
401 color: var(--neutral--600);
402 transition: background-color 0.2s ease;
403
404 &:hover {
405 background-color: var(--neutral--50);
406 }
407
408 span {
409 flex: 1;
410 }
411 }
412
413 @media (max-width: 1023px) {
414 &__header {
415 display: none;
416 }
417 }
418 }
419 </style>
420