PluginProbe
The Innovative Form Builder – IvyForms / 0.9
The Innovative Form Builder – IvyForms v0.9
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / _components / hcaptcha / HCaptcha.vue

HCaptcha.vue in The Innovative Form Builder – IvyForms 0.9, at frontend/src/views/_components/hcaptcha/HCaptcha.vue

199 lines 4.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <!-- eslint-disable vue/multi-word-component-names -->
2 <template>
3 <div
4 ref="hcaptchaContainer"
5 class="ivyforms-hcaptcha-container ivyforms-flex ivyforms-justify-content-start ivyforms-align-items-center"
6 :class="{
7 'ivyforms-hcaptcha-container--invisible': type === 'invisible',
8 'ivyforms-mb-18': !props.error,
9 }"
10 ></div>
11 </template>
12
13 <script setup lang="ts">
14 import { ref, onMounted, onUnmounted, watch } from 'vue'
15 import { hcaptchaService } from '@/services/hcaptcha'
16 import { useLabels } from '@/composables/useLabels'
17 import type { HCaptchaType } from '@/types/hcaptcha/hcaptcha-type'
18
19 interface Props {
20 siteKey: string
21 type: HCaptchaType
22 modelValue?: string
23 error?: string
24 }
25
26 interface Emits {
27 (event: 'update:modelValue', value: string): void
28 (event: 'verify', token: string): void
29 (event: 'expire'): void
30 (event: 'error', error: string): void
31 }
32
33 const props = defineProps<Props>()
34 const emit = defineEmits<Emits>()
35
36 const hcaptchaContainer = ref<HTMLDivElement>()
37 const widgetId = ref<string | null>(null)
38 const isLoading = ref(true)
39 const internalError = ref<string | null>(null)
40
41 const { getLabel } = useLabels()
42
43 // Initialize hCaptcha when component mounts
44 onMounted(async () => {
45 try {
46 await initHCaptcha()
47 } catch (err) {
48 internalError.value = err instanceof Error ? err.message : getLabel('hcaptcha_init_failed')
49 emit('error', internalError.value)
50 }
51 })
52
53 // Clean up when component unmounts
54 onUnmounted(() => {
55 if (widgetId.value !== null) {
56 hcaptchaService.reset()
57 }
58 })
59
60 // Watch for config changes and reinitialize
61 watch(
62 () => [props.siteKey, props.type],
63 async () => {
64 await initHCaptcha()
65 },
66 )
67
68 /**
69 * Initialize hCaptcha widget
70 */
71 async function initHCaptcha() {
72 if (!props.siteKey || !hcaptchaContainer.value) {
73 return
74 }
75
76 try {
77 isLoading.value = true
78 internalError.value = null
79
80 // Initialize the service
81 await hcaptchaService.init(props.siteKey)
82
83 // Render the widget
84 const options = {
85 callback: onVerify,
86 'expired-callback': onExpire,
87 'error-callback': onError,
88 size: 'normal' as const,
89 }
90
91 widgetId.value = await hcaptchaService.render(hcaptchaContainer.value, options)
92 isLoading.value = false
93 } catch (err) {
94 internalError.value =
95 err instanceof Error ? err.message : getLabel('hcaptcha_initialization_failed')
96 isLoading.value = false
97 emit('error', internalError.value)
98 }
99 }
100
101 /**
102 * Handle successful verification
103 */
104 function onVerify(token: string) {
105 emit('update:modelValue', token)
106 emit('verify', token)
107 }
108
109 /**
110 * Handle token expiration
111 */
112 function onExpire() {
113 emit('update:modelValue', '')
114 emit('expire')
115 }
116
117 /**
118 * Handle hCaptcha errors
119 */
120 function onError() {
121 const errorMsg = getLabel('hcaptcha_verification_failed')
122 emit('update:modelValue', '')
123 emit('error', errorMsg)
124 }
125
126 /**
127 * Execute hCaptcha (for invisible)
128 */
129 async function execute(): Promise<string> {
130 try {
131 const token = await hcaptchaService.execute()
132 emit('update:modelValue', token)
133 emit('verify', token)
134 return token
135 } catch (err) {
136 const errorMsg = err instanceof Error ? err.message : getLabel('hcaptcha_execution_failed')
137 emit('error', errorMsg)
138 throw err
139 }
140 }
141
142 /**
143 * Reset hCaptcha widget
144 */
145 function reset() {
146 hcaptchaService.reset()
147 emit('update:modelValue', '')
148 }
149
150 /**
151 * Get current response token
152 */
153 function getResponse(): string {
154 return hcaptchaService.getResponse()
155 }
156
157 // Expose methods for parent components
158 defineExpose({
159 execute,
160 reset,
161 getResponse,
162 isLoading: () => isLoading.value,
163 hasError: () => !!internalError.value,
164 getError: () => internalError.value,
165 })
166 </script>
167
168 <style lang="scss" scoped>
169 .ivyforms-hcaptcha-container {
170 min-height: 78px; // Standard hCaptcha height
171
172 // Hide for invisible
173 &:empty {
174 min-height: 0;
175 display: none;
176 }
177
178 // Hide invisible hCaptcha containers
179 &--invisible {
180 min-height: 0;
181 display: none;
182 }
183
184 :deep(.h-captcha) {
185 transform-origin: 0 0;
186 }
187
188 @keyframes pulse {
189 0%,
190 100% {
191 opacity: 1;
192 }
193 50% {
194 opacity: 0.5;
195 }
196 }
197 }
198 </style>
199