@saasmakers/ui 2.0.53 → 2.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/auth/AuthEmailConnect.vue +22 -4
- package/app/components/bases/BaseAction.vue +1 -1
- package/app/components/bases/BaseCircle.vue +4 -4
- package/app/components/bases/BaseTag.vue +4 -2
- package/app/components/bases/BaseTags.vue +35 -0
- package/app/types/bases.d.ts +21 -10
- package/app/types/global.d.ts +2 -0
- package/app/utils/auth.ts +20 -0
- package/package.json +1 -1
|
@@ -20,6 +20,17 @@ const turnstile = ref<{
|
|
|
20
20
|
reset: () => void
|
|
21
21
|
}>()
|
|
22
22
|
|
|
23
|
+
const turnstileConfig = {
|
|
24
|
+
// Cloudflare Turnstile keeps internal requests in-flight (e.g. clearance redemption). When the
|
|
25
|
+
// widget errors we swallow it here so it does not surface as an unhandled rejection.
|
|
26
|
+
options: { 'error-callback': () => true },
|
|
27
|
+
|
|
28
|
+
// The wrapper's default 250s auto-reset re-runs the challenge while the user is idle, which aborts
|
|
29
|
+
// in-flight requests (surfaces as "The operation was cancelled." on WebKit). Disable it and rely
|
|
30
|
+
// on Turnstile's own refresh-expired handling instead.
|
|
31
|
+
resetInterval: 1000 * 60 * 60 * 24,
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
const { t } = useI18n()
|
|
24
35
|
|
|
25
36
|
const form = reactive({
|
|
@@ -87,6 +98,7 @@ async function onLogin() {
|
|
|
87
98
|
|
|
88
99
|
if (result.isClosed) {
|
|
89
100
|
emit('accountClosed', result.email ?? '')
|
|
101
|
+
resetTurnstile()
|
|
90
102
|
}
|
|
91
103
|
else {
|
|
92
104
|
v$.value.$reset()
|
|
@@ -95,12 +107,10 @@ async function onLogin() {
|
|
|
95
107
|
}
|
|
96
108
|
catch (error) {
|
|
97
109
|
console.error(error)
|
|
110
|
+
|
|
111
|
+
resetTurnstile()
|
|
98
112
|
}
|
|
99
113
|
finally {
|
|
100
|
-
form.captchaToken = ''
|
|
101
|
-
|
|
102
|
-
turnstile.value?.reset()
|
|
103
|
-
|
|
104
114
|
loggingWithEmail.value = false
|
|
105
115
|
}
|
|
106
116
|
}
|
|
@@ -110,6 +120,12 @@ function onUpdateCurrentWindow(newCurrentWindow: AuthEmailConnectCurrentWindow)
|
|
|
110
120
|
|
|
111
121
|
v$.value.$reset()
|
|
112
122
|
}
|
|
123
|
+
|
|
124
|
+
function resetTurnstile() {
|
|
125
|
+
form.captchaToken = ''
|
|
126
|
+
|
|
127
|
+
turnstile.value?.reset()
|
|
128
|
+
}
|
|
113
129
|
</script>
|
|
114
130
|
|
|
115
131
|
<template>
|
|
@@ -165,6 +181,8 @@ function onUpdateCurrentWindow(newCurrentWindow: AuthEmailConnectCurrentWindow)
|
|
|
165
181
|
<NuxtTurnstile
|
|
166
182
|
ref="turnstile"
|
|
167
183
|
v-model="form.captchaToken"
|
|
184
|
+
:options="turnstileConfig.options"
|
|
185
|
+
:reset-interval="turnstileConfig.resetInterval"
|
|
168
186
|
/>
|
|
169
187
|
</div>
|
|
170
188
|
|
|
@@ -44,7 +44,7 @@ function onClick(event: MouseEvent) {
|
|
|
44
44
|
|
|
45
45
|
<template>
|
|
46
46
|
<button
|
|
47
|
-
class="relative flex flex-col items-center rounded-lg text-center transition-colors"
|
|
47
|
+
class="relative flex flex-col select-none items-center rounded-lg text-center transition-colors"
|
|
48
48
|
:aria-busy="loading"
|
|
49
49
|
:class="{
|
|
50
50
|
'cursor-not-allowed opacity-50': disabled,
|
|
@@ -8,10 +8,10 @@ withDefaults(defineProps<BaseCircle>(), { color: 'indigo' })
|
|
|
8
8
|
<span
|
|
9
9
|
class="inline-block size-2.5 shrink-0 rounded-full"
|
|
10
10
|
:class="{
|
|
11
|
-
'bg-red-
|
|
12
|
-
'bg-amber-
|
|
13
|
-
'bg-indigo-
|
|
14
|
-
'bg-green-
|
|
11
|
+
'bg-red-600 dark:bg-red-400': color === 'red',
|
|
12
|
+
'bg-amber-600 dark:bg-amber-400': color === 'amber',
|
|
13
|
+
'bg-indigo-600 dark:bg-indigo-400': color === 'indigo',
|
|
14
|
+
'bg-green-600 dark:bg-green-400': color === 'green',
|
|
15
15
|
}"
|
|
16
16
|
/>
|
|
17
17
|
</template>
|
|
@@ -15,8 +15,10 @@ withDefaults(defineProps<BaseTag>(), {
|
|
|
15
15
|
'px-0.5 py-px text-3xs': size === 'xs',
|
|
16
16
|
'px-1 py-px text-3xs': size === 'sm',
|
|
17
17
|
'px-1.5 py-0.5 text-2xs': size === 'base',
|
|
18
|
-
'bg-orange-
|
|
19
|
-
'bg-
|
|
18
|
+
'bg-orange-500/15 text-orange-800 dark:text-orange-300': color === 'orange',
|
|
19
|
+
'bg-indigo-500/15 text-indigo-800 dark:text-indigo-300': color === 'indigo',
|
|
20
|
+
'bg-green-500/15 text-green-800 dark:text-green-300': color === 'green',
|
|
21
|
+
'bg-gray-500/20 text-gray-700 dark:text-gray-300': color === 'gray',
|
|
20
22
|
}"
|
|
21
23
|
>
|
|
22
24
|
<slot>{{ text }}</slot>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { BaseTags } from '../../types/bases'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<BaseTags>(), {
|
|
5
|
+
size: 'base',
|
|
6
|
+
tags: () => [],
|
|
7
|
+
wrap: false,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const sortedTags = computed(() => {
|
|
11
|
+
return [...props.tags].toSorted((tagA, tagB) => {
|
|
12
|
+
return (tagA.order ?? 0) - (tagB.order ?? 0)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<div
|
|
19
|
+
class="flex items-center"
|
|
20
|
+
:class="{
|
|
21
|
+
'flex-wrap gap-1': wrap && size !== 'base',
|
|
22
|
+
'flex-wrap gap-1.5': wrap && size === 'base',
|
|
23
|
+
'w-full gap-1 overflow-x-auto': !wrap && size !== 'base',
|
|
24
|
+
'w-full gap-1.5 overflow-x-auto': !wrap && size === 'base',
|
|
25
|
+
}"
|
|
26
|
+
>
|
|
27
|
+
<BaseTag
|
|
28
|
+
v-for="(tag, tagIndex) in sortedTags"
|
|
29
|
+
:key="tag.id ?? tagIndex"
|
|
30
|
+
:color="tag.color"
|
|
31
|
+
:size="tag.size ?? size"
|
|
32
|
+
:text="tag.text"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
package/app/types/bases.d.ts
CHANGED
|
@@ -127,16 +127,6 @@ export interface BaseCircle {
|
|
|
127
127
|
|
|
128
128
|
export type BaseCircleColor = 'amber' | 'green' | 'indigo' | 'red'
|
|
129
129
|
|
|
130
|
-
export interface BaseTag {
|
|
131
|
-
color?: BaseTagColor
|
|
132
|
-
size?: BaseTagSize
|
|
133
|
-
text?: BaseTextText
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export type BaseTagColor = 'gray' | 'orange'
|
|
137
|
-
|
|
138
|
-
export type BaseTagSize = 'base' | 'sm' | 'xs'
|
|
139
|
-
|
|
140
130
|
export type BaseColor
|
|
141
131
|
= | 'black'
|
|
142
132
|
| 'gray'
|
|
@@ -337,6 +327,27 @@ export interface BaseSpinner {
|
|
|
337
327
|
|
|
338
328
|
export type BaseStatus = | 'error' | 'info' | 'success' | 'warning'
|
|
339
329
|
|
|
330
|
+
export interface BaseTag {
|
|
331
|
+
color?: BaseTagColor
|
|
332
|
+
size?: BaseTagSize
|
|
333
|
+
text?: BaseTextText
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export type BaseTagColor = 'gray' | 'green' | 'indigo' | 'orange'
|
|
337
|
+
|
|
338
|
+
export interface BaseTagItem extends BaseTag {
|
|
339
|
+
id?: number | string
|
|
340
|
+
order?: number
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export interface BaseTags {
|
|
344
|
+
size?: BaseTagSize
|
|
345
|
+
tags?: BaseTagItem[]
|
|
346
|
+
wrap?: boolean
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export type BaseTagSize = 'base' | 'sm' | 'xs'
|
|
350
|
+
|
|
340
351
|
export interface BaseText {
|
|
341
352
|
alignment?: BaseAlignment
|
|
342
353
|
background?: BaseBackground
|
package/app/types/global.d.ts
CHANGED
|
@@ -70,6 +70,8 @@ declare global {
|
|
|
70
70
|
type BaseStatus = import('./bases').BaseStatus
|
|
71
71
|
type BaseTag = import('./bases').BaseTag
|
|
72
72
|
type BaseTagColor = import('./bases').BaseTagColor
|
|
73
|
+
type BaseTagItem = import('./bases').BaseTagItem
|
|
74
|
+
type BaseTags = import('./bases').BaseTags
|
|
73
75
|
type BaseTagSize = import('./bases').BaseTagSize
|
|
74
76
|
type BaseText = import('./bases').BaseText
|
|
75
77
|
type BaseTextText = import('./bases').BaseTextText
|
package/app/utils/auth.ts
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
export function isRequestCancellation(error: unknown): boolean {
|
|
2
|
+
if (!error || typeof error !== 'object') {
|
|
3
|
+
return false
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const candidate = error as {
|
|
7
|
+
code?: number
|
|
8
|
+
message?: string
|
|
9
|
+
name?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// WebKit (Safari/iOS/WKWebView) rejects aborted requests with NSURLErrorCancelled (-999) and this
|
|
13
|
+
// exact message when Turnstile's in-flight request is torn down on navigate/unmount after login.
|
|
14
|
+
return (
|
|
15
|
+
candidate.name === 'AbortError'
|
|
16
|
+
|| candidate.code === -999
|
|
17
|
+
|| candidate.message === 'The operation was cancelled.'
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
export function isSocialLoginCancellation(error: unknown): boolean {
|
|
2
22
|
if (!error || typeof error !== 'object') {
|
|
3
23
|
return false
|