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 / Integrations.vue
hostinger-reach / frontend / vue / components Last commit date
Modals 1 month ago skeletons 5 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 5 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
Integrations.vue
226 lines
1 <script setup lang="ts">
2 import { computed } from 'vue';
3
4 import Banner from '@/components/Banner.vue';
5 import PluginEntriesTable from '@/components/PluginEntriesTable.vue';
6 import { useModal } from '@/composables';
7 import { TABS_KEYS } from '@/data/tabs';
8 import { useIntegrationsStore } from '@/stores/integrationsStore';
9 import { ModalName } from '@/types';
10 import type { Form, Integration } from '@/types/models';
11 import { translate } from '@/utils/translate';
12
13 type IntegrationsProps = {
14 onBannerButtonClick?: () => void;
15 };
16
17 const props = defineProps<IntegrationsProps>();
18
19 const emit = defineEmits({
20 goToPlugin: (_id: string) => true,
21 disconnectPlugin: (_id: string) => true,
22 toggleFormStatus: (_form: Form, _status: boolean) => true,
23 viewForm: (_form: Form) => true,
24 editForm: (_form: Form) => true,
25 addForm: (_id: string) => true
26 });
27
28 const { openModal } = useModal();
29 const integrationsStore = useIntegrationsStore();
30
31 const shouldShowTable = computed(
32 () =>
33 integrationsStore?.activeIntegrations?.filter((integration) => integration.type === TABS_KEYS.OVERVIEW_TAB_FORMS)
34 ?.length > 1 || integrationsStore.hasAnyForms(TABS_KEYS.OVERVIEW_TAB_FORMS)
35 );
36
37 const connectPlugin = async (integration: Integration) => {
38 const connected = await integrationsStore.toggleIntegrationStatus(integration.id, true);
39 const hasContacts = integration.importEnabled && integration.importStatus?.total > 0;
40 if (connected && hasContacts) {
41 openModal(
42 ModalName.SYNC_CONTACTS_MODAL,
43 {
44 title: translate('hostinger_reach_contacts_modal_title'),
45 subtitle: translate('hostinger_reach_contacts_modal_subtitle'),
46 data: { integrations: [integration] }
47 },
48 { hasCloseButton: true }
49 );
50 }
51 };
52
53 const onMoreClick = async () => {
54 openModal(ModalName.CONNECT_PLUGIN_MODAL, {}, { hasCloseButton: true, isLG: true });
55 };
56
57 const isBannerVisible = computed(() => !integrationsStore.isLoading && !shouldShowTable.value);
58 const isTableVisible = computed(() => integrationsStore.isLoading || shouldShowTable.value);
59 const recommendedPlugins = computed(() => integrationsStore.recommendedPlugins);
60 </script>
61
62 <template>
63 <div class="integrations">
64 <Banner
65 v-if="isBannerVisible"
66 :title="translate(`hostinger_reach_forms_banner_title`)"
67 :description="translate(`hostinger_reach_forms_banner_description`)"
68 :button-text="translate(`hostinger_reach_forms_banner_button_text`)"
69 :on-button-click="props.onBannerButtonClick"
70 :is-button-loading="integrationsStore.isLoading"
71 >
72 <template #extra>
73 <div class="divider-text">
74 <span>{{ translate('hostinger_reach_forms_banner_connect_text') }}</span>
75 </div>
76 <div v-if="recommendedPlugins.length" class="recommended-plugins-wrap" role="list">
77 <div class="recommended-plugins" role="list">
78 <button
79 v-for="plugin in recommendedPlugins"
80 :key="plugin.id"
81 class="recommended-plugins__pill"
82 type="button"
83 role="listitem"
84 :aria-label="plugin.title"
85 @click="connectPlugin(plugin)"
86 >
87 <span class="recommended-plugins__icon-wrap" aria-hidden="true">
88 <img class="recommended-plugins__icon" :src="plugin.icon" :alt="plugin.title" loading="lazy" />
89 </span>
90 <span class="recommended-plugins__name">
91 {{ plugin.title }}
92 </span>
93 </button>
94 <HButton
95 size="small"
96 color="primary"
97 variant="text"
98 icon-prepend="ic-magnifying-glass-16"
99 class="recommended-plugins-more"
100 @click="onMoreClick"
101 >
102 {{ translate('hostinger_reach_forms_banner_more_text') }}
103 </HButton>
104 </div>
105 </div>
106 </template>
107 </Banner>
108
109 <PluginEntriesTable
110 v-if="isTableVisible"
111 :integrations="integrationsStore.fromType(TABS_KEYS.OVERVIEW_TAB_FORMS) as Integration[]"
112 :is-loading="integrationsStore.isLoading"
113 @go-to-plugin="emit('goToPlugin', $event)"
114 @disconnect-plugin="emit('disconnectPlugin', $event)"
115 @toggle-form-status="(form, status) => emit('toggleFormStatus', form, status)"
116 @view-form="emit('viewForm', $event)"
117 @edit-form="emit('editForm', $event)"
118 @add-form="emit('addForm', $event)"
119 />
120 </div>
121 </template>
122
123 <style scoped lang="scss">
124 .integrations {
125 display: flex;
126 flex-direction: column;
127 align-self: stretch;
128 gap: 20px;
129 margin-bottom: 40px;
130 }
131
132 .divider-text {
133 display: flex;
134 align-items: center;
135 gap: 16px;
136 width: 100%;
137 color: #6b7280;
138 font-size: 16px;
139 line-height: 1.2;
140 }
141
142 .divider-text::before,
143 .divider-text::after {
144 content: '';
145 flex: 1;
146 height: 1px;
147 width: 175px;
148 background: #dadce0;
149 }
150
151 .divider-text > span {
152 white-space: nowrap;
153 font-weight: 400;
154 font-size: 12px;
155 line-height: 1.67;
156 }
157
158 .recommended-plugins {
159 display: flex;
160 align-items: center;
161 justify-content: center;
162 gap: 12px;
163 flex-wrap: nowrap;
164 width: 100%;
165 margin-top: 4px;
166
167 @media (max-width: 992px) {
168 flex-wrap: wrap;
169 }
170 }
171
172 .recommended-plugins__pill {
173 display: inline-flex;
174 align-items: center;
175 gap: 5px;
176 padding: 5px 15px;
177 background: var(--neutral--0);
178 border: 1px solid var(--neutral--200);
179 border-radius: 14px;
180 cursor: pointer;
181 transition:
182 border-color 0.2s ease,
183 box-shadow 0.2s ease;
184
185 &:hover {
186 border-color: var(--neutral--300);
187 }
188
189 &:focus-visible {
190 outline: none;
191 box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.15);
192 }
193 }
194
195 .recommended-plugins-wrap {
196 margin-top: 20px;
197 display: flex;
198 align-items: center;
199 }
200
201 .recommended-plugins__icon-wrap {
202 width: 28px;
203 height: 28px;
204 border-radius: 999px;
205 display: inline-flex;
206 align-items: center;
207 justify-content: center;
208 background: var(--neutral--50);
209 flex: 0 0 auto;
210 }
211
212 .recommended-plugins__icon {
213 width: 18px;
214 height: 18px;
215 object-fit: contain;
216 }
217
218 .recommended-plugins__name {
219 font-weight: 600;
220 font-size: 12px;
221 line-height: 1.2;
222 color: var(--neutral--600);
223 white-space: nowrap;
224 }
225 </style>
226