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 / stores / integrationsStore.ts
hostinger-reach / frontend / vue / stores Last commit date
generalDataStore.ts 4 months ago index.ts 1 year ago integrationsStore.ts 4 months ago modalStore.ts 1 year ago pagesStore.ts 5 months ago
integrationsStore.ts
231 lines
1 import { defineStore } from 'pinia';
2 import { computed, ref } from 'vue';
3
4 import { useToast } from '@/composables/useToast';
5 import { HOSTINGER_REACH_ID } from '@/data/pluginData';
6 import { formsRepo } from '@/data/repositories/formsRepo';
7 import { TABS_KEYS } from '@/data/tabs';
8 import { STORE_PERSISTENT_KEYS } from '@/types/enums';
9 import type { Form, Integration } from '@/types/models';
10 import { IMPORT_STATUSES } from '@/types/models';
11 import { translate } from '@/utils/translate';
12
13 export const useIntegrationsStore = defineStore(
14 'integrationsStore',
15 () => {
16 const { showSuccess, showError } = useToast();
17
18 const integrations = ref<Integration[]>([]);
19 const isLoading = ref(false);
20 const error = ref<string | null>(null);
21 const loadingIntegrations = ref<Record<string, boolean>>({});
22 const importStatusPollers = ref<Record<string, number>>({});
23 const activeIntegrations = computed(() => integrations.value.filter((integration) => integration.isActive));
24 const syncableIntegrations = computed(() =>
25 activeIntegrations.value.filter((integration) => integration.importEnabled && integration.forms.length > 0)
26 );
27 const importAvailableIntegrations = computed(() =>
28 syncableIntegrations.value.filter((integration) => integration.importStatus.total > 0)
29 );
30
31 const availableIntegrations = computed(() => integrations.value.filter(({ id }) => id !== HOSTINGER_REACH_ID));
32
33 const recommendedPlugins = computed<Integration[]>(() =>
34 availableIntegrations.value
35 .filter(
36 (integration) =>
37 integration.type === TABS_KEYS.OVERVIEW_TAB_FORMS &&
38 integration.isActive === false &&
39 integration.isInstallable === true
40 )
41 .slice()
42 .sort((a, b) => {
43 const scoreA = a.isPluginActive ? 1 : 0;
44 const scoreB = b.isPluginActive ? 1 : 0;
45
46 if (scoreA !== scoreB) return scoreB - scoreA;
47
48 const scoreAContacts = a.importStatus?.total || 0;
49 const scoreBContacts = b.importStatus?.total || 0;
50
51 return scoreBContacts - scoreAContacts;
52 })
53 .slice(0, 3)
54 );
55
56 const hasAnyForms = (type: string = 'forms') =>
57 integrations.value.some(
58 (integration) => integration.type === type && integration.forms && integration.forms.length > 0
59 );
60
61 const fromType = (type: string = 'forms'): Integration[] =>
62 integrations.value.filter((integration) => integration.type === type);
63
64 const isIntegrationLoading = (integrationId: string) => loadingIntegrations.value[integrationId] || false;
65
66 const mapAndFilterForms = (formsData: Form[], integration: Integration) =>
67 formsData
68 ?.filter((form) => integration.id === form.type)
69 .map((form) => ({
70 ...form,
71 integration
72 })) || [];
73
74 const loadIntegrations = async () => {
75 isLoading.value = true;
76 error.value = null;
77
78 const [integrationsData, integrationsError] = await formsRepo.getIntegrations();
79 const [formsData] = await formsRepo.getForms();
80
81 if (integrationsError) {
82 error.value = integrationsError.message || 'Failed to load integrations';
83 isLoading.value = false;
84
85 return;
86 }
87
88 if (integrationsData) {
89 integrations.value = Object.values(integrationsData).map((integration) => ({
90 ...integration,
91 forms: mapAndFilterForms(formsData || [], integration)
92 }));
93 }
94
95 isLoading.value = false;
96 };
97
98 const isImportProcessFinished = (status?: string) =>
99 status === IMPORT_STATUSES.IMPORTED ||
100 status === IMPORT_STATUSES.NOT_IMPORTED ||
101 status === IMPORT_STATUSES.PARTIALLY_IMPORTED ||
102 status === IMPORT_STATUSES.OFF;
103
104 const stopImportStatusPolling = (integrationId: string) => {
105 const timerId = importStatusPollers.value[integrationId];
106 if (!timerId) {
107 return;
108 }
109
110 window.clearTimeout(timerId);
111 delete importStatusPollers.value[integrationId];
112 };
113
114 const refreshImportStatus = async (integrationId: string) => {
115 const integration = integrations.value.find((i) => i.id === integrationId);
116 if (!integration) {
117 stopImportStatusPolling(integrationId);
118
119 return;
120 }
121
122 try {
123 const [statusData, statusError] = await formsRepo.getImportStatus(integrationId);
124 if (statusError) {
125 stopImportStatusPolling(integrationId);
126
127 return;
128 }
129
130 if (statusData) {
131 integration.importStatus = statusData as Integration['importStatus'];
132 if (isImportProcessFinished(integration.importStatus?.status)) {
133 stopImportStatusPolling(integrationId);
134 }
135 }
136 } catch {
137 stopImportStatusPolling(integrationId);
138 }
139 };
140
141 const startImportStatusPolling = (integrationId: string) => {
142 if (importStatusPollers.value[integrationId]) {
143 return;
144 }
145
146 const tick = async () => {
147 await refreshImportStatus(integrationId);
148
149 if (!importStatusPollers.value[integrationId]) {
150 return;
151 }
152
153 importStatusPollers.value[integrationId] = window.setTimeout(tick, 2000);
154 };
155
156 importStatusPollers.value[integrationId] = window.setTimeout(tick, 0);
157 };
158
159 const syncContacts = async (importRequest: Record<string, Set<string>>) => {
160 isLoading.value = true;
161
162 try {
163 await formsRepo.sync(importRequest);
164 showSuccess(translate('hostinger_reach_contacts_import_success'));
165 } catch (error) {
166 showError(error.message || translate('hostinger_reach_contacts_import_error'));
167 } finally {
168 const integrationIds = Object.keys(importRequest);
169 integrationIds.forEach((id) => stopImportStatusPolling(id));
170 await loadIntegrations();
171 integrationIds.forEach((id) => startImportStatusPolling(id));
172 isLoading.value = false;
173 }
174 };
175
176 const toggleIntegrationStatus = async (integrationId: string, isActive: boolean) => {
177 isLoading.value = true;
178 loadingIntegrations.value[integrationId] = true;
179
180 const [, error] = await formsRepo.toggleIntegrationStatus(integrationId, isActive);
181 isLoading.value = false;
182
183 if (error) {
184 loadingIntegrations.value[integrationId] = false;
185
186 return false;
187 }
188
189 const integration = integrations.value.find((i) => i.id === integrationId);
190
191 if (integration) {
192 integration.isActive = isActive;
193 }
194
195 await loadIntegrations();
196 loadingIntegrations.value[integrationId] = false;
197
198 showSuccess(
199 translate(
200 isActive
201 ? 'hostinger_reach_forms_plugin_connected_success'
202 : 'hostinger_reach_forms_plugin_disconnected_success'
203 )
204 );
205
206 return true;
207 };
208
209 return {
210 integrations,
211 isLoading,
212 error,
213 loadingIntegrations,
214 activeIntegrations,
215 syncableIntegrations,
216 importAvailableIntegrations,
217 availableIntegrations,
218 recommendedPlugins,
219 hasAnyForms,
220 isIntegrationLoading,
221 loadIntegrations,
222 toggleIntegrationStatus,
223 syncContacts,
224 fromType
225 };
226 },
227 {
228 persist: { key: STORE_PERSISTENT_KEYS.INTEGRATIONS_STORE }
229 }
230 );
231