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.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 / Modals / SelectPageModal.vue
hostinger-reach / frontend / vue / components / Modals Last commit date
Base 10 months ago Types 1 month ago AddFormModal.vue 10 months ago ConfirmDisconnectModal.vue 10 months ago Modals.vue 9 months ago SelectPageModal.vue 10 months ago
SelectPageModal.vue
340 lines
1 <script lang="ts" setup>
2 import { HButton, HIcon, HSkeletonLoader, HText } from '@hostinger/hcomponents';
3 import { computed, onMounted, ref } from 'vue';
4
5 import BaseModal from '@/components/Modals/Base/BaseModal.vue';
6 import Pagination from '@/components/Pagination.vue';
7 import { usePagesStore } from '@/stores/pagesStore';
8 import type { Page, WordPressPage } from '@/types/models/pagesModels';
9 import { translate } from '@/utils/translate';
10
11 interface Props {
12 title?: string;
13 pages?: Page[];
14 data?: Record<string, unknown>;
15 }
16
17 const props = defineProps<Props>();
18
19 const pagesStore = usePagesStore();
20
21 const NEW_FORM_BUTTON_LINK = '/wp-admin/post-new.php?post_type=page&hostinger_reach_add_block=1';
22
23 const loadingPageId = ref<string | null>(null);
24 const isNewFormButtonLoading = ref(false);
25
26 const currentPages = computed(() => {
27 const storePages = pagesStore.items;
28
29 return storePages && storePages.length > 0 ? storePages : props.pages || [];
30 });
31
32 const getPageDisplayName = (page: Page | WordPressPage): string => {
33 if ('name' in page && page.name) {
34 return page.name;
35 }
36
37 if ('title' in page && page.title?.rendered) {
38 return page.title.rendered;
39 }
40
41 return translate('hostinger_reach_forms_no_title');
42 };
43
44 const isPageAdded = (page: Page | WordPressPage): boolean => ('isAdded' in page ? page.isAdded || false : false);
45
46 const isLoading = computed(() => pagesStore.isLoading);
47 const currentPage = computed(() => pagesStore.currentPage);
48 const totalItems = computed(() => pagesStore.totalItems);
49 const itemsPerPage = computed(() => pagesStore.itemsPerPage);
50
51 const handlePageChange = async (page: number) => {
52 await pagesStore.goToPage(page);
53 };
54
55 const handlePageClick = (page: Page | WordPressPage) => {
56 if (loadingPageId.value) return;
57
58 loadingPageId.value = String(page.id);
59
60 const pageUrl = page.link;
61 if (pageUrl) {
62 window.location.href = pageUrl;
63 }
64 };
65
66 const handleNewFormClick = () => {
67 if (isNewFormButtonLoading.value) return;
68
69 isNewFormButtonLoading.value = true;
70
71 window.location.href = NEW_FORM_BUTTON_LINK;
72 };
73
74 const handleBackClick = () => {
75 const backButtonAction = props.data?.backButtonRedirectAction as (() => void) | undefined;
76 if (backButtonAction) {
77 backButtonAction();
78 }
79 };
80
81 onMounted(async () => {
82 await pagesStore.resetToFirstPage();
83 });
84 </script>
85
86 <template>
87 <BaseModal title-alignment="centered" :title="title">
88 <template v-if="data?.backButtonRedirectAction" #back-button>
89 <button class="select-page-modal__back-button" type="button" @click="handleBackClick">
90 <HIcon name="ic-chevron-left-16" color="neutral--600" />
91 </button>
92 </template>
93
94 <div class="select-page-modal">
95 <div class="select-page-modal__content">
96 <div class="select-page-modal__pages">
97 <template v-if="isLoading">
98 <div
99 v-for="n in itemsPerPage"
100 :key="`skeleton-${n}`"
101 class="select-page-modal__page-item select-page-modal__page-item--loading"
102 >
103 <div class="select-page-modal__page-loading">
104 <HSkeletonLoader width="60%" height="20px" border-radius="sm" />
105 </div>
106 </div>
107 </template>
108
109 <template v-else-if="currentPages && currentPages.length > 0">
110 <div
111 v-for="page in currentPages"
112 :key="page.id"
113 class="select-page-modal__page-item"
114 :class="{
115 'select-page-modal__page-item--selected': isPageAdded(page),
116 'select-page-modal__page-item--loading': loadingPageId === String(page.id)
117 }"
118 @click="handlePageClick(page)"
119 >
120 <div v-if="loadingPageId === String(page.id)" class="select-page-modal__page-loading">
121 <HSkeletonLoader width="60%" height="20px" border-radius="sm" />
122 </div>
123 <template v-else>
124 <div class="select-page-modal__page-content">
125 <HText variant="body-2-bold" as="span" class="select-page-modal__page-name">
126 {{ getPageDisplayName(page) }}
127 </HText>
128 </div>
129
130 <div>
131 <HIcon
132 :name="isPageAdded(page) ? 'ic-checkmark-circle-filled-24' : 'ic-circle-empty-24'"
133 :color="isPageAdded(page) ? 'primary--500' : 'neutral--200'"
134 />
135 </div>
136 </template>
137 </div>
138 </template>
139
140 <template v-else-if="!isLoading">
141 <div class="select-page-modal__no-pages">
142 <HText variant="body-2" as="p" class="select-page-modal__no-pages-text">
143 {{ translate('hostinger_reach_forms_no_pages_available') }}
144 </HText>
145 </div>
146 </template>
147 </div>
148 </div>
149
150 <div class="select-page-modal__pagination">
151 <Pagination
152 :current-page="currentPage"
153 :total-items="totalItems"
154 :items-per-page="itemsPerPage"
155 :total-visible="5"
156 :disabled="isLoading"
157 @page-change="handlePageChange"
158 @update:current-page="handlePageChange"
159 />
160 </div>
161
162 <div class="select-page-modal__footer">
163 <HButton
164 variant="outline"
165 color="neutral"
166 size="small"
167 :icon-prepend="isNewFormButtonLoading ? undefined : 'ic-add-16'"
168 :is-loading="isNewFormButtonLoading"
169 @click="handleNewFormClick"
170 >
171 {{ translate('hostinger_reach_forms_new_page_text') }}
172 </HButton>
173 </div>
174 </div>
175 </BaseModal>
176 </template>
177
178 <style lang="scss" scoped>
179 .select-page-modal {
180 margin-top: 24px;
181
182 &__back-button {
183 position: absolute;
184 top: 0;
185 left: 0;
186 display: flex;
187 align-items: center;
188 justify-content: center;
189 width: 32px;
190 height: 32px;
191 border: none;
192 background: transparent;
193 border-radius: 8px;
194 cursor: pointer;
195 transition: background-color 0.2s ease;
196
197 &:hover {
198 background-color: var(--neutral--100);
199 }
200
201 &:active {
202 background-color: var(--neutral--200);
203 }
204 }
205
206 &__content {
207 display: flex;
208 flex-direction: column;
209 gap: 20px;
210 align-items: center;
211 margin-bottom: 24px;
212 }
213
214 &__pages {
215 display: flex;
216 flex-direction: column;
217 gap: 8px;
218 width: 100%;
219 max-width: 100%;
220 }
221
222 &__page-item {
223 display: flex;
224 align-items: center;
225 justify-content: space-between;
226 height: 60px;
227 gap: 16px;
228 padding: 20px;
229 border: 1px solid var(--neutral--200);
230 border-radius: 16px;
231 background: var(--neutral--0);
232 cursor: pointer;
233 transition:
234 border-color 0.2s ease,
235 opacity 0.2s ease;
236
237 &:hover:not(&--loading) {
238 border-color: var(--primary--500);
239 }
240
241 &--selected {
242 border-color: var(--primary--500);
243 }
244
245 &--loading {
246 cursor: not-allowed;
247 opacity: 0.7;
248 border-color: var(--neutral--300);
249 }
250 }
251
252 &__page-loading {
253 display: flex;
254 align-items: center;
255 width: 100%;
256 gap: 12px;
257 }
258
259 &__page-content {
260 display: flex;
261 align-items: center;
262 gap: 12px;
263 flex: 1;
264 }
265
266 &__page-name {
267 font-weight: 700;
268 }
269
270 &__checkmark {
271 width: 20px;
272 height: 20px;
273 display: flex;
274 align-items: center;
275 justify-content: center;
276 flex-shrink: 0;
277 }
278
279 &__no-pages {
280 display: flex;
281 flex-direction: column;
282 align-items: center;
283 justify-content: center;
284 gap: 12px;
285 padding: 40px 20px;
286 text-align: center;
287 }
288
289 &__no-pages-icon {
290 width: 32px;
291 height: 32px;
292 color: var(--neutral--400);
293 }
294
295 &__no-pages-text {
296 color: var(--neutral--500);
297 margin: 0;
298 }
299
300 &__pagination {
301 display: flex;
302 justify-content: center;
303 align-items: center;
304 padding: 16px 0;
305 margin-bottom: 8px;
306 }
307
308 &__footer {
309 display: flex;
310 justify-content: flex-end;
311 gap: 8px;
312 }
313
314 @media (max-width: 640px) {
315 &__page-item {
316 padding: 16px;
317 }
318
319 &__footer {
320 flex-direction: column-reverse;
321 gap: 12px;
322
323 :deep(.h-button) {
324 width: 100%;
325 }
326 }
327 }
328
329 @media (max-width: 480px) {
330 &__pages {
331 gap: 6px;
332 }
333
334 &__page-item {
335 padding: 14px;
336 }
337 }
338 }
339 </style>
340