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.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 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 2 months ago FormsSection.vue 11 months ago Hero.vue 1 month ago Integrations.vue 4 months ago Pagination.vue 10 months ago PluginEntriesTable.vue 4 months ago PluginEntry.vue 4 months ago PluginExpansion.vue 8 months ago ReachContactsSummary.vue 2 weeks 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 2 weeks ago WooCommerceEntriesTable.vue 4 months ago
FormItem.vue
317 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 >
116 <template #trigger>
117 <button class="form-item__action-button">
118 <HIcon name="ic-dots-vertical-16" />
119 </button>
120 </template>
121 <div class="form-item__popover-menu">
122 <div v-if="!integration.isViewFormHidden" class="form-item__menu-item" @click="emit('viewForm', props.form)">
123 <HIcon name="ic-arrow-up-right-square-16" />
124 <span>{{ translate('hostinger_reach_plugin_entries_table_view_form') }}</span>
125 </div>
126 <div v-if="!integration.isEditFormHidden" class="form-item__menu-item" @click="emit('editForm', props.form)">
127 <HIcon name="ic-edit-16" />
128 <span>{{ translate('hostinger_reach_plugin_entries_table_edit_form') }}</span>
129 </div>
130 <div
131 v-if="supportsImport && props.integration.id !== 'thrive-leads'"
132 class="form-item__menu-item"
133 @click="handleSync(props.integration, props.form)"
134 >
135 <HIcon name="ic-arrows-circle-16" />
136 <span>{{ translate('hostinger_reach_sync_contacts_button_text') }}</span>
137 </div>
138 </div>
139 </HPopover>
140 </div>
141 </div>
142 </template>
143
144 <style scoped lang="scss">
145 .form-item {
146 display: flex;
147 align-items: center;
148 border-top: 1px solid var(--neutral--200);
149
150 &:first-child {
151 border-top: none;
152 }
153
154 &__cell {
155 display: flex;
156 align-items: center;
157
158 &--plugin {
159 width: 40%;
160 }
161
162 &--forms {
163 width: 27%;
164 }
165
166 &--actions {
167 width: 40%;
168 display: flex;
169 justify-content: flex-end;
170 }
171 }
172
173 &__form-content {
174 display: flex;
175 align-items: center;
176 gap: 12px;
177 }
178
179 &__form-info {
180 display: flex;
181 flex-direction: column;
182 gap: 2px;
183 }
184
185 &__form-title {
186 font-weight: 500;
187 font-size: 14px;
188 color: var(--neutral--600);
189 }
190
191 &__form-title--link {
192 display: inline-flex;
193 align-items: center;
194 gap: 4px;
195 background: none;
196 border: none;
197 padding: 0;
198 text-align: left;
199 cursor: pointer;
200 font: inherit;
201 color: inherit;
202
203 &:hover {
204 color: var(--primary--500);
205
206 .form-item__form-title-icon {
207 color: var(--primary--500);
208 }
209
210 span {
211 text-decoration: underline;
212 }
213 }
214 }
215
216 &__form-title-icon {
217 width: 16px;
218 height: 16px;
219 color: var(--neutral--500);
220 flex-shrink: 0;
221 }
222
223 &__status-label {
224 font-size: 12px;
225 }
226
227 &__mobile-label {
228 font-weight: 500;
229 font-size: 14px;
230 color: var(--neutral--600);
231 margin-right: 8px;
232 display: none;
233 align-items: center;
234 cursor: help;
235 }
236
237 &__action-button {
238 background: var(--neutral--0);
239 border: 1px solid var(--neutral--200);
240 border-radius: 8px;
241 padding: 0 8px;
242 height: 32px;
243 display: flex;
244 align-items: center;
245 justify-content: center;
246 cursor: pointer;
247 transition: all 0.2s ease;
248
249 &:hover {
250 border-color: var(--neutral--300);
251 }
252 }
253
254 &__popover-menu {
255 padding: 4px;
256 min-width: 180px;
257 }
258
259 &__menu-item {
260 display: flex;
261 align-items: center;
262 gap: 8px;
263 padding: 12px;
264 cursor: pointer;
265 border-radius: 8px;
266 font-weight: 500;
267 font-size: 14px;
268 color: var(--neutral--600);
269 transition: background-color 0.2s ease;
270
271 &:hover {
272 background-color: var(--neutral--50);
273 }
274
275 span {
276 flex: 1;
277 }
278 }
279
280 @media (max-width: 1023px) {
281 flex-direction: column;
282 gap: 12px;
283 padding: 0;
284 border-radius: 0;
285 background: var(--neutral--50);
286 margin-bottom: 12px;
287
288 &__cell--plugin,
289 &__cell--forms,
290 &__cell--status {
291 width: 100%;
292 justify-content: flex-start;
293 }
294 &__cell--actions {
295 width: 100%;
296 }
297
298 &__cell--forms,
299 &__cell--status {
300 align-items: flex-start;
301 }
302
303 &__cell--actions {
304 padding-right: 0;
305 }
306
307 &__cell--plugin {
308 padding-left: 0;
309 }
310
311 &__mobile-label {
312 display: inline-flex;
313 }
314 }
315 }
316 </style>
317