Modals
10 months ago
skeletons
10 months ago
ActionButton.vue
10 months ago
ActionButtonsSection.vue
10 months ago
Banner.vue
10 months ago
FAQ.vue
10 months ago
FormItem.vue
10 months ago
FormsSection.vue
10 months ago
Hero.vue
10 months ago
PluginEntriesTable.vue
10 months ago
PluginEntry.vue
10 months ago
PluginExpansion.vue
10 months ago
Toggle.vue
10 months ago
UsageCard.vue
10 months ago
UsageCardsRow.vue
10 months ago
UsageCardsSection.vue
10 months ago
PluginEntry.vue
329 lines
| 1 | <script setup lang="ts"> |
| 2 | import { HIcon, HLabel, HPopover } from '@hostinger/hcomponents'; |
| 3 | import { computed, ref } from 'vue'; |
| 4 | |
| 5 | import PluginExpansion from '@/components/PluginExpansion.vue'; |
| 6 | import { INTEGRATION_TO_FORM_TYPE_MAP, PLUGIN_IDS, PLUGIN_STATUSES, type PluginStatus } from '@/data/pluginData'; |
| 7 | import type { Form } from '@/types/models'; |
| 8 | import { translate } from '@/utils/translate'; |
| 9 | |
| 10 | interface Props { |
| 11 | pluginId: string; |
| 12 | pluginName: string; |
| 13 | pluginIcon: string; |
| 14 | pluginStatus: PluginStatus; |
| 15 | totalEntries: number; |
| 16 | forms: Form[]; |
| 17 | } |
| 18 | |
| 19 | const props = defineProps<Props>(); |
| 20 | |
| 21 | const emit = defineEmits<{ |
| 22 | toggleFormStatus: [form: Form, status: boolean]; |
| 23 | goToPlugin: [id: string]; |
| 24 | disconnectPlugin: [id: string]; |
| 25 | viewForm: [form: Form]; |
| 26 | editForm: [form: Form]; |
| 27 | }>(); |
| 28 | |
| 29 | const isExpanded = ref(false); |
| 30 | |
| 31 | const toggleExpansion = () => { |
| 32 | isExpanded.value = !isExpanded.value; |
| 33 | }; |
| 34 | |
| 35 | const getStatusLabel = (status: PluginStatus) => |
| 36 | status === PLUGIN_STATUSES.ACTIVE |
| 37 | ? translate('hostinger_reach_plugin_entries_table_status_active') |
| 38 | : translate('hostinger_reach_plugin_entries_table_status_inactive'); |
| 39 | |
| 40 | const getStatusColor = (status: PluginStatus) => (status === PLUGIN_STATUSES.ACTIVE ? 'success' : 'gray'); |
| 41 | |
| 42 | const handleToggleFormStatus = (form: Form, status: boolean) => { |
| 43 | emit('toggleFormStatus', form, status); |
| 44 | }; |
| 45 | |
| 46 | const handleViewForm = (form: Form) => { |
| 47 | emit('viewForm', form); |
| 48 | }; |
| 49 | |
| 50 | const handleEditForm = (form: Form) => { |
| 51 | emit('editForm', form); |
| 52 | }; |
| 53 | |
| 54 | const expandButtonAriaLabel = computed(() => { |
| 55 | const translationKey = isExpanded.value |
| 56 | ? 'hostinger_reach_plugin_entries_table_collapse_aria' |
| 57 | : 'hostinger_reach_plugin_entries_table_expand_aria'; |
| 58 | |
| 59 | return translate(translationKey).replace('{pluginName}', props.pluginName); |
| 60 | }); |
| 61 | </script> |
| 62 | |
| 63 | <template> |
| 64 | <div class="plugin-entry-row"> |
| 65 | <div class="plugin-entry-row__main"> |
| 66 | <div class="plugin-entry-row__cell plugin-entry-row__cell--plugin"> |
| 67 | <div class="plugin-entry-row__plugin-content" @click="toggleExpansion"> |
| 68 | <button |
| 69 | class="plugin-entry-row__expand-button" |
| 70 | :aria-expanded="isExpanded" |
| 71 | :aria-controls="`plugin-expansion-${pluginId}`" |
| 72 | :aria-label="expandButtonAriaLabel" |
| 73 | @click.stop="toggleExpansion" |
| 74 | > |
| 75 | <HIcon :name="isExpanded ? 'ic-chevron-down-16' : 'ic-chevron-right-16'" dimensions="16px" /> |
| 76 | </button> |
| 77 | <div class="plugin-entry-row__plugin-info"> |
| 78 | <div class="plugin-entry-row__plugin-icon"> |
| 79 | <img :src="pluginIcon" :alt="pluginName" /> |
| 80 | </div> |
| 81 | <span class="plugin-entry-row__plugin-name">{{ pluginName }}</span> |
| 82 | </div> |
| 83 | </div> |
| 84 | </div> |
| 85 | <div class="plugin-entry-row__cell plugin-entry-row__cell--entries"> |
| 86 | <span class="plugin-entry-row__mobile-label"> |
| 87 | {{ translate('hostinger_reach_plugin_entries_table_entries_header') }}: |
| 88 | </span> |
| 89 | <span class="plugin-entry-row__entries-count">{{ totalEntries }}</span> |
| 90 | </div> |
| 91 | <div class="plugin-entry-row__cell plugin-entry-row__cell--status"> |
| 92 | <span class="plugin-entry-row__mobile-label"> |
| 93 | {{ translate('hostinger_reach_plugin_entries_table_status_header') }}: |
| 94 | </span> |
| 95 | <div class="plugin-entry-row__status-content"> |
| 96 | <HLabel variant="outline" :color="getStatusColor(pluginStatus)" class="plugin-entry-row__status-label"> |
| 97 | {{ getStatusLabel(pluginStatus) }} |
| 98 | </HLabel> |
| 99 | </div> |
| 100 | </div> |
| 101 | <div class="plugin-entry-row__cell plugin-entry-row__cell--actions"> |
| 102 | <HPopover |
| 103 | v-if="INTEGRATION_TO_FORM_TYPE_MAP[pluginId] !== PLUGIN_IDS.HOSTINGER_REACH" |
| 104 | placement="bottom-end" |
| 105 | :show-arrow="false" |
| 106 | background-color="neutral--0" |
| 107 | border-radius="12px" |
| 108 | :outside-click-enabled="true" |
| 109 | > |
| 110 | <template #trigger> |
| 111 | <button class="plugin-entry-row__action-button"> |
| 112 | <HIcon name="ic-dots-vertical-16" /> |
| 113 | </button> |
| 114 | </template> |
| 115 | <div class="plugin-entry-row__popover-menu"> |
| 116 | <div class="plugin-entry-row__menu-item" @click="emit('goToPlugin', props.pluginId)"> |
| 117 | <HIcon name="ic-blocks-plus-16" /> |
| 118 | <span>{{ translate('hostinger_reach_plugin_entries_table_go_to_plugin') }}</span> |
| 119 | <HIcon name="ic-arrow-up-right-square-16" /> |
| 120 | </div> |
| 121 | <div class="plugin-entry-row__menu-item" @click="emit('disconnectPlugin', props.pluginId)"> |
| 122 | <HIcon name="ic-cross-circle-16" /> |
| 123 | <span>{{ translate('hostinger_reach_plugin_entries_table_disconnect_plugin') }}</span> |
| 124 | </div> |
| 125 | </div> |
| 126 | </HPopover> |
| 127 | </div> |
| 128 | </div> |
| 129 | <div v-if="isExpanded" class="plugin-entry-row__expansion"> |
| 130 | <PluginExpansion |
| 131 | :plugin-id="pluginId" |
| 132 | :forms="forms" |
| 133 | @toggle-form-status="handleToggleFormStatus" |
| 134 | @view-form="handleViewForm" |
| 135 | @edit-form="handleEditForm" |
| 136 | /> |
| 137 | </div> |
| 138 | </div> |
| 139 | </template> |
| 140 | |
| 141 | <style scoped lang="scss"> |
| 142 | .plugin-entry-row { |
| 143 | &__main { |
| 144 | display: flex; |
| 145 | padding: 16px 16px 16px 0; |
| 146 | } |
| 147 | |
| 148 | &__cell { |
| 149 | display: flex; |
| 150 | align-items: center; |
| 151 | |
| 152 | &--plugin { |
| 153 | width: 50%; |
| 154 | order: 1; |
| 155 | } |
| 156 | |
| 157 | &--entries { |
| 158 | width: 20%; |
| 159 | order: 2; |
| 160 | } |
| 161 | |
| 162 | &--status { |
| 163 | width: 20%; |
| 164 | order: 3; |
| 165 | } |
| 166 | |
| 167 | &--actions { |
| 168 | width: 10%; |
| 169 | display: flex; |
| 170 | justify-content: flex-end; |
| 171 | order: 4; |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | &__action-button { |
| 176 | background: var(--neutral--0); |
| 177 | border: 1px solid var(--neutral--200); |
| 178 | border-radius: 8px; |
| 179 | padding: 0 8px; |
| 180 | height: 32px; |
| 181 | display: flex; |
| 182 | align-items: center; |
| 183 | justify-content: center; |
| 184 | cursor: pointer; |
| 185 | transition: all 0.2s ease; |
| 186 | |
| 187 | &:hover { |
| 188 | border-color: var(--neutral--300); |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | &__plugin-content { |
| 193 | display: flex; |
| 194 | align-items: center; |
| 195 | gap: 8px; |
| 196 | cursor: pointer; |
| 197 | width: 100%; |
| 198 | } |
| 199 | |
| 200 | &__expand-button { |
| 201 | background: none; |
| 202 | border: none; |
| 203 | cursor: pointer; |
| 204 | padding: 0; |
| 205 | display: flex; |
| 206 | align-items: center; |
| 207 | justify-content: center; |
| 208 | width: 16px; |
| 209 | height: 16px; |
| 210 | } |
| 211 | |
| 212 | &__plugin-info { |
| 213 | display: flex; |
| 214 | align-items: center; |
| 215 | gap: 12px; |
| 216 | } |
| 217 | |
| 218 | &__plugin-icon { |
| 219 | width: 28px; |
| 220 | height: 28px; |
| 221 | border-radius: 7px; |
| 222 | overflow: hidden; |
| 223 | display: flex; |
| 224 | align-items: center; |
| 225 | justify-content: center; |
| 226 | background: var(--neutral--100); |
| 227 | |
| 228 | img { |
| 229 | width: 100%; |
| 230 | height: 100%; |
| 231 | object-fit: cover; |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | &__plugin-name { |
| 236 | font-weight: 700; |
| 237 | font-size: 14px; |
| 238 | color: var(--neutral--500); |
| 239 | } |
| 240 | |
| 241 | &__entries-count { |
| 242 | font-weight: 400; |
| 243 | font-size: 14px; |
| 244 | color: var(--neutral--500); |
| 245 | } |
| 246 | |
| 247 | &__status-content { |
| 248 | display: flex; |
| 249 | align-items: center; |
| 250 | } |
| 251 | |
| 252 | &__status-label { |
| 253 | font-size: 12px; |
| 254 | } |
| 255 | |
| 256 | &__mobile-label { |
| 257 | font-weight: 500; |
| 258 | font-size: 14px; |
| 259 | color: var(--neutral--600); |
| 260 | margin-right: 8px; |
| 261 | display: none; |
| 262 | } |
| 263 | |
| 264 | &__expansion { |
| 265 | width: 100%; |
| 266 | height: 100%; |
| 267 | } |
| 268 | |
| 269 | &__popover-menu { |
| 270 | padding: 4px; |
| 271 | min-width: 180px; |
| 272 | } |
| 273 | |
| 274 | &__menu-item { |
| 275 | display: flex; |
| 276 | align-items: center; |
| 277 | gap: 8px; |
| 278 | padding: 12px; |
| 279 | cursor: pointer; |
| 280 | border-radius: 8px; |
| 281 | font-weight: 500; |
| 282 | font-size: 14px; |
| 283 | color: var(--neutral--600); |
| 284 | transition: background-color 0.2s ease; |
| 285 | |
| 286 | &:hover { |
| 287 | background-color: var(--neutral--50); |
| 288 | } |
| 289 | |
| 290 | span { |
| 291 | flex: 1; |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | @media (max-width: 1023px) { |
| 296 | &__main { |
| 297 | flex-direction: column; |
| 298 | gap: 12px; |
| 299 | padding: 16px; |
| 300 | border-radius: 12px; |
| 301 | background: var(--neutral--100); |
| 302 | margin-bottom: 12px; |
| 303 | } |
| 304 | |
| 305 | &__cell--plugin, |
| 306 | &__cell--entries, |
| 307 | &__cell--status, |
| 308 | &__cell--actions { |
| 309 | width: 100%; |
| 310 | justify-content: flex-start; |
| 311 | } |
| 312 | |
| 313 | &__cell--entries, |
| 314 | &__cell--status { |
| 315 | align-items: flex-start; |
| 316 | } |
| 317 | |
| 318 | &__cell--actions { |
| 319 | padding-right: 0; |
| 320 | justify-content: flex-end; |
| 321 | } |
| 322 | |
| 323 | &__mobile-label { |
| 324 | display: inline-block; |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | </style> |
| 329 |