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 / FormItem.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
FormItem.vue
318 lines
1 <script setup lang="ts">
2 import { HIcon, HPopover } from '@hostinger/hcomponents';
3 import { computed } from 'vue';
4
5 import Toggle from '@/components/Toggle.vue';
6 import { useIntegrationsStore } from '@/stores';
7 import type { Form, Integration } from '@/types/models';
8 import { translate } from '@/utils/translate';
9
10 const { syncContacts } = useIntegrationsStore();
11
12 interface Props {
13 form: Form;
14 integration: Integration;
15 }
16
17 const props = defineProps<Props>();
18
19 const emit = defineEmits<{
20 toggleStatus: [form: Form, status: boolean];
21 viewForm: [form: Form];
22 editForm: [form: Form];
23 }>();
24
25 const TitleLinkAction = {
26 VIEW: 'view',
27 EDIT: 'edit'
28 } as const;
29
30 type TitleLinkActionType = (typeof TitleLinkAction)[keyof typeof TitleLinkAction];
31
32 const handleSync = async (integration: Integration, form: Form) => {
33 await syncContacts({ [integration.id]: new Set([form.formId]) });
34 };
35
36 const pluginTitle = computed(
37 () => props.form.formTitle || props.form.post?.postTitle || translate('hostinger_reach_forms_no_title')
38 );
39
40 const supportsImport = computed(() => props.integration.type !== 'ecommerce' && props.integration.importEnabled);
41 const hasActions = computed(() => !props.integration.isViewFormHidden || !props.integration.isEditFormHidden);
42 const shouldHideToggle = computed(
43 () => !props.integration.canToggleForms || props.form.formId?.startsWith('elementor-hostinger-reach-form')
44 );
45
46 const titleLinkAction = computed<TitleLinkActionType | null>(() => {
47 if (!props.integration.isViewFormHidden) return TitleLinkAction.VIEW;
48 if (!props.integration.isEditFormHidden) return TitleLinkAction.EDIT;
49
50 return null;
51 });
52
53 const handleTitleClick = () => {
54 if (titleLinkAction.value === TitleLinkAction.VIEW) {
55 emit('viewForm', props.form);
56 } else if (titleLinkAction.value === TitleLinkAction.EDIT) {
57 emit('editForm', props.form);
58 }
59 };
60 </script>
61
62 <template>
63 <div class="form-item">
64 <div class="form-item__cell form-item__cell--plugin">
65 <div class="form-item__form-content">
66 <div class="form-item__form-info">
67 <button
68 v-if="titleLinkAction"
69 type="button"
70 class="form-item__form-title form-item__form-title--link"
71 :aria-label="`${pluginTitle} (${translate('hostinger_reach_ui_opens_in_new_tab')})`"
72 @click="handleTitleClick"
73 >
74 <span>{{ pluginTitle }}</span>
75 <HIcon name="ic-arrow-up-right-square-16" class="form-item__form-title-icon" aria-hidden="true" />
76 </button>
77 <span v-else class="form-item__form-title">
78 {{ pluginTitle }}
79 </span>
80 </div>
81 </div>
82 </div>
83 <div class="form-item__cell form-item__cell--forms">
84 <span
85 v-tooltip.top="translate('hostinger_reach_plugin_entries_table_syncing_tooltip')"
86 class="form-item__mobile-label"
87 >
88 {{ translate('hostinger_reach_plugin_entries_table_syncing_header') }}:
89 </span>
90 <Toggle
91 v-if="shouldHideToggle"
92 v-tooltip="{
93 content: translate('hostinger_reach_plugin_cannot_disable'),
94 placement: 'top'
95 }"
96 :value="props.form.isActive"
97 :is-disabled="true"
98 @toggle="(status) => emit('toggleStatus', props.form, status)"
99 />
100 <Toggle
101 v-else
102 :value="props.form.isActive"
103 :is-disabled="form.isLoading"
104 @toggle="(status) => emit('toggleStatus', props.form, status)"
105 />
106 </div>
107 <div class="form-item__cell form-item__cell--actions">
108 <HPopover
109 v-if="hasActions"
110 placement="bottom-end"
111 :show-arrow="false"
112 background-color="neutral--0"
113 border-radius="12px"
114 :outside-click-enabled="true"
115 :close-other-popovers-on-open="true"
116 >
117 <template #trigger>
118 <button class="form-item__action-button">
119 <HIcon name="ic-dots-vertical-16" />
120 </button>
121 </template>
122 <div class="form-item__popover-menu">
123 <div v-if="!integration.isViewFormHidden" class="form-item__menu-item" @click="emit('viewForm', props.form)">
124 <HIcon name="ic-arrow-up-right-square-16" />
125 <span>{{ translate('hostinger_reach_plugin_entries_table_view_form') }}</span>
126 </div>
127 <div v-if="!integration.isEditFormHidden" class="form-item__menu-item" @click="emit('editForm', props.form)">
128 <HIcon name="ic-edit-16" />
129 <span>{{ translate('hostinger_reach_plugin_entries_table_edit_form') }}</span>
130 </div>
131 <div
132 v-if="supportsImport && props.integration.id !== 'thrive-leads'"
133 class="form-item__menu-item"
134 @click="handleSync(props.integration, props.form)"
135 >
136 <HIcon name="ic-arrows-circle-16" />
137 <span>{{ translate('hostinger_reach_sync_contacts_button_text') }}</span>
138 </div>
139 </div>
140 </HPopover>
141 </div>
142 </div>
143 </template>
144
145 <style scoped lang="scss">
146 .form-item {
147 display: flex;
148 align-items: center;
149 border-top: 1px solid var(--neutral--200);
150
151 &:first-child {
152 border-top: none;
153 }
154
155 &__cell {
156 display: flex;
157 align-items: center;
158
159 &--plugin {
160 width: 40%;
161 }
162
163 &--forms {
164 width: 27%;
165 }
166
167 &--actions {
168 width: 40%;
169 display: flex;
170 justify-content: flex-end;
171 }
172 }
173
174 &__form-content {
175 display: flex;
176 align-items: center;
177 gap: 12px;
178 }
179
180 &__form-info {
181 display: flex;
182 flex-direction: column;
183 gap: 2px;
184 }
185
186 &__form-title {
187 font-weight: 500;
188 font-size: 14px;
189 color: var(--neutral--600);
190 }
191
192 &__form-title--link {
193 display: inline-flex;
194 align-items: center;
195 gap: 4px;
196 background: none;
197 border: none;
198 padding: 0;
199 text-align: left;
200 cursor: pointer;
201 font: inherit;
202 color: inherit;
203
204 &:hover {
205 color: var(--primary--500);
206
207 .form-item__form-title-icon {
208 color: var(--primary--500);
209 }
210
211 span {
212 text-decoration: underline;
213 }
214 }
215 }
216
217 &__form-title-icon {
218 width: 16px;
219 height: 16px;
220 color: var(--neutral--500);
221 flex-shrink: 0;
222 }
223
224 &__status-label {
225 font-size: 12px;
226 }
227
228 &__mobile-label {
229 font-weight: 500;
230 font-size: 14px;
231 color: var(--neutral--600);
232 margin-right: 8px;
233 display: none;
234 align-items: center;
235 cursor: help;
236 }
237
238 &__action-button {
239 background: var(--neutral--0);
240 border: 1px solid var(--neutral--200);
241 border-radius: 8px;
242 padding: 0 8px;
243 height: 32px;
244 display: flex;
245 align-items: center;
246 justify-content: center;
247 cursor: pointer;
248 transition: all 0.2s ease;
249
250 &:hover {
251 border-color: var(--neutral--300);
252 }
253 }
254
255 &__popover-menu {
256 padding: 4px;
257 min-width: 180px;
258 }
259
260 &__menu-item {
261 display: flex;
262 align-items: center;
263 gap: 8px;
264 padding: 12px;
265 cursor: pointer;
266 border-radius: 8px;
267 font-weight: 500;
268 font-size: 14px;
269 color: var(--neutral--600);
270 transition: background-color 0.2s ease;
271
272 &:hover {
273 background-color: var(--neutral--50);
274 }
275
276 span {
277 flex: 1;
278 }
279 }
280
281 @media (max-width: 1023px) {
282 flex-direction: column;
283 gap: 12px;
284 padding: 0;
285 border-radius: 0;
286 background: var(--neutral--50);
287 margin-bottom: 12px;
288
289 &__cell--plugin,
290 &__cell--forms,
291 &__cell--status {
292 width: 100%;
293 justify-content: flex-start;
294 }
295 &__cell--actions {
296 width: 100%;
297 }
298
299 &__cell--forms,
300 &__cell--status {
301 align-items: flex-start;
302 }
303
304 &__cell--actions {
305 padding-right: 0;
306 }
307
308 &__cell--plugin {
309 padding-left: 0;
310 }
311
312 &__mobile-label {
313 display: inline-flex;
314 }
315 }
316 }
317 </style>
318