@saasmakers/ui 2.0.0 → 2.0.3
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/AuthSocialConnect.vue +237 -0
- package/app/components/bases/BaseAlert.vue +1 -2
- package/app/components/bases/BaseCard.vue +2 -4
- package/app/components/bases/BaseDivider.vue +2 -3
- package/app/components/bases/BaseIcon.vue +4 -5
- package/app/components/bases/BaseMetric.vue +4 -6
- package/app/components/bases/BaseOverlay.vue +1 -5
- package/app/components/bases/BaseQuote.vue +1 -2
- package/app/components/bases/BaseShortcut.vue +1 -3
- package/app/components/bases/BaseTag.vue +2 -3
- package/app/components/bases/BaseTags.vue +2 -3
- package/app/components/bases/BaseToast.vue +1 -3
- package/app/components/fields/FieldEmojis.vue +0 -1
- package/app/components/fields/FieldSelect.vue +2 -5
- package/app/components/fields/FieldTime.vue +1 -2
- package/app/components/layout/LayoutToasts.vue +0 -1
- package/app/composables/useCapacitor.ts +47 -0
- package/app/types/auth.d.ts +42 -0
- package/app/types/global.d.ts +11 -2
- package/app/utils/animations.ts +31 -0
- package/app/utils/chartist.ts +126 -0
- package/app/{composables/useLayerUtils.ts → utils/formatting.ts} +2 -9
- package/app/{composables/useLayerIcons.ts → utils/layerIcons.ts} +6 -13
- package/nuxt.config.ts +8 -0
- package/package.json +6 -3
- package/public/images/auth/AppleConnect/apple-dark.svg +3 -0
- package/public/images/auth/AppleConnect/apple-light.svg +3 -0
- package/public/images/auth/GoogleConnect/google.svg +18 -0
- package/app/composables/useChartist.ts +0 -130
- package/app/composables/useMotion.ts +0 -39
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { SocialLogin } from '@capgo/capacitor-social-login'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<AuthSocialConnect>(), {
|
|
5
|
+
loading: false,
|
|
6
|
+
provider: 'apple',
|
|
7
|
+
size: 'base',
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const emit = defineEmits<{
|
|
11
|
+
error: [error: unknown]
|
|
12
|
+
success: [payload: AuthSocialConnectSuccess]
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
const { t } = useI18n()
|
|
16
|
+
const { isAndroid, platform } = useCapacitor()
|
|
17
|
+
const colorMode = useColorMode()
|
|
18
|
+
const oauthAuthenticating = ref(false)
|
|
19
|
+
|
|
20
|
+
const buttonLoading = computed(() => {
|
|
21
|
+
return oauthAuthenticating.value || props.loading
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
onMounted(() => {
|
|
25
|
+
if (props.provider === 'google') {
|
|
26
|
+
window.addEventListener('message', processGoogleCallback)
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
onBeforeUnmount(() => {
|
|
31
|
+
if (props.provider === 'google') {
|
|
32
|
+
window.removeEventListener('message', processGoogleCallback)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
async function onStartAuthentication() {
|
|
37
|
+
oauthAuthenticating.value = true
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
if (props.provider === 'apple') {
|
|
41
|
+
if (!props.appleConfig) {
|
|
42
|
+
throw new Error('appleConfig is required when provider is apple')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const platformKey = platform.value === 'ios' ? 'ios' : 'web'
|
|
46
|
+
|
|
47
|
+
await SocialLogin.initialize({
|
|
48
|
+
apple: {
|
|
49
|
+
clientId: props.appleConfig[platformKey].appId,
|
|
50
|
+
redirectUrl: props.appleConfig[platformKey].redirectUrl,
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const { result } = await SocialLogin.login({
|
|
55
|
+
options: { scopes: ['email', 'name'] },
|
|
56
|
+
provider: 'apple',
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
emit('success', {
|
|
60
|
+
firstName: `${result.profile.givenName}`,
|
|
61
|
+
idToken: result.idToken!,
|
|
62
|
+
lastName: `${result.profile.familyName}`,
|
|
63
|
+
provider: 'apple',
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
else if (props.provider === 'google') {
|
|
67
|
+
if (!props.googleConfig) {
|
|
68
|
+
throw new Error('googleConfig is required when provider is google')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await SocialLogin.initialize({
|
|
72
|
+
google: {
|
|
73
|
+
iOSClientId: props.googleConfig.ios.clientId,
|
|
74
|
+
redirectUrl: props.googleConfig.web.redirectUrl,
|
|
75
|
+
webClientId: props.googleConfig.web.clientId,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const response = await SocialLogin.login({
|
|
80
|
+
options: { scopes: ['email', 'profile'] },
|
|
81
|
+
provider: 'google',
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const { idToken } = response.result as GoogleLoginResponseOnline
|
|
85
|
+
|
|
86
|
+
emit('success', {
|
|
87
|
+
idToken: idToken!,
|
|
88
|
+
provider: 'google',
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
emit('error', error)
|
|
94
|
+
|
|
95
|
+
console.error(error)
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
oauthAuthenticating.value = false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function processGoogleCallback(event: MessageEvent) {
|
|
103
|
+
if (event.origin !== globalThis.location.origin) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (event.data.type === 'auth:google:success') {
|
|
108
|
+
try {
|
|
109
|
+
oauthAuthenticating.value = true
|
|
110
|
+
|
|
111
|
+
emit('success', {
|
|
112
|
+
idToken: event.data.idToken,
|
|
113
|
+
provider: 'google',
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
emit('error', error)
|
|
118
|
+
|
|
119
|
+
console.error(error)
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
oauthAuthenticating.value = false
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<BaseButton
|
|
130
|
+
:class="{ hidden: isAndroid && provider === 'apple' }"
|
|
131
|
+
:color="provider === 'apple' ? 'black' : 'white'"
|
|
132
|
+
full-width
|
|
133
|
+
:loading="buttonLoading"
|
|
134
|
+
:size="size"
|
|
135
|
+
:text="provider === 'apple' ? t('continueWithApple') : t('continueWithGoogle')"
|
|
136
|
+
@click="onStartAuthentication"
|
|
137
|
+
>
|
|
138
|
+
<ClientOnly>
|
|
139
|
+
<img
|
|
140
|
+
v-if="provider === 'apple'"
|
|
141
|
+
:alt="t('appleLogo')"
|
|
142
|
+
class="mr-2.5 w-4.5"
|
|
143
|
+
:src="`/images/auth/AppleConnect/apple-${colorMode.value}.svg`"
|
|
144
|
+
>
|
|
145
|
+
|
|
146
|
+
<img
|
|
147
|
+
v-else-if="provider === 'google'"
|
|
148
|
+
:alt="t('googleLogo')"
|
|
149
|
+
class="mr-2.5 w-5"
|
|
150
|
+
src="/images/auth/GoogleConnect/google.svg"
|
|
151
|
+
>
|
|
152
|
+
</ClientOnly>
|
|
153
|
+
</BaseButton>
|
|
154
|
+
</template>
|
|
155
|
+
|
|
156
|
+
<i18n lang="json">
|
|
157
|
+
{
|
|
158
|
+
"de": {
|
|
159
|
+
"appleLogo": "Apple-Logo",
|
|
160
|
+
"continueWithApple": "Mit Apple fortfahren",
|
|
161
|
+
"continueWithGoogle": "Mit Google fortfahren",
|
|
162
|
+
"googleLogo": "Google-Logo"
|
|
163
|
+
},
|
|
164
|
+
"en": {
|
|
165
|
+
"appleLogo": "Apple logo",
|
|
166
|
+
"continueWithApple": "Continue with Apple",
|
|
167
|
+
"continueWithGoogle": "Continue with Google",
|
|
168
|
+
"googleLogo": "Google logo"
|
|
169
|
+
},
|
|
170
|
+
"es": {
|
|
171
|
+
"appleLogo": "Logo de Apple",
|
|
172
|
+
"continueWithApple": "Continuar con Apple",
|
|
173
|
+
"continueWithGoogle": "Continuar con Google",
|
|
174
|
+
"googleLogo": "Logo de Google"
|
|
175
|
+
},
|
|
176
|
+
"fr": {
|
|
177
|
+
"appleLogo": "Logo Apple",
|
|
178
|
+
"continueWithApple": "Continuer avec Apple",
|
|
179
|
+
"continueWithGoogle": "Continuer avec Google",
|
|
180
|
+
"googleLogo": "Logo Google"
|
|
181
|
+
},
|
|
182
|
+
"id": {
|
|
183
|
+
"appleLogo": "Logo Apple",
|
|
184
|
+
"continueWithApple": "Lanjutkan dengan Apple",
|
|
185
|
+
"continueWithGoogle": "Lanjutkan dengan Google",
|
|
186
|
+
"googleLogo": "Logo Google"
|
|
187
|
+
},
|
|
188
|
+
"it": {
|
|
189
|
+
"appleLogo": "Logo Apple",
|
|
190
|
+
"continueWithApple": "Continua con Apple",
|
|
191
|
+
"continueWithGoogle": "Continua con Google",
|
|
192
|
+
"googleLogo": "Logo Google"
|
|
193
|
+
},
|
|
194
|
+
"ja": {
|
|
195
|
+
"appleLogo": "Appleロゴ",
|
|
196
|
+
"continueWithApple": "Appleで続ける",
|
|
197
|
+
"continueWithGoogle": "Googleで続ける",
|
|
198
|
+
"googleLogo": "Googleロゴ"
|
|
199
|
+
},
|
|
200
|
+
"ko": {
|
|
201
|
+
"appleLogo": "Apple 로고",
|
|
202
|
+
"continueWithApple": "Apple로 계속하기",
|
|
203
|
+
"continueWithGoogle": "Google로 계속하기",
|
|
204
|
+
"googleLogo": "Google 로고"
|
|
205
|
+
},
|
|
206
|
+
"nl": {
|
|
207
|
+
"appleLogo": "Apple-logo",
|
|
208
|
+
"continueWithApple": "Doorgaan met Apple",
|
|
209
|
+
"continueWithGoogle": "Doorgaan met Google",
|
|
210
|
+
"googleLogo": "Google-logo"
|
|
211
|
+
},
|
|
212
|
+
"pl": {
|
|
213
|
+
"appleLogo": "Logo Apple",
|
|
214
|
+
"continueWithApple": "Kontynuuj z Apple",
|
|
215
|
+
"continueWithGoogle": "Kontynuuj z Google",
|
|
216
|
+
"googleLogo": "Logo Google"
|
|
217
|
+
},
|
|
218
|
+
"pt": {
|
|
219
|
+
"appleLogo": "Logótipo da Apple",
|
|
220
|
+
"continueWithApple": "Continuar com Apple",
|
|
221
|
+
"continueWithGoogle": "Continuar com Google",
|
|
222
|
+
"googleLogo": "Logótipo do Google"
|
|
223
|
+
},
|
|
224
|
+
"pt-BR": {
|
|
225
|
+
"appleLogo": "Logo da Apple",
|
|
226
|
+
"continueWithApple": "Continuar com Apple",
|
|
227
|
+
"continueWithGoogle": "Continuar com Google",
|
|
228
|
+
"googleLogo": "Logo do Google"
|
|
229
|
+
},
|
|
230
|
+
"vi": {
|
|
231
|
+
"appleLogo": "Logo Apple",
|
|
232
|
+
"continueWithApple": "Tiếp tục với Apple",
|
|
233
|
+
"continueWithGoogle": "Tiếp tục với Google",
|
|
234
|
+
"googleLogo": "Logo Google"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
</i18n>
|
|
@@ -20,7 +20,6 @@ defineSlots<{
|
|
|
20
20
|
}>()
|
|
21
21
|
|
|
22
22
|
const { t } = useI18n()
|
|
23
|
-
const { getIcon } = useLayerIcons()
|
|
24
23
|
const isClosed = ref(false)
|
|
25
24
|
|
|
26
25
|
const buttonColor = computed<BaseColor>(() => {
|
|
@@ -89,7 +88,7 @@ async function onClose(event: MouseEvent) {
|
|
|
89
88
|
<BaseButton
|
|
90
89
|
v-if="isClosable"
|
|
91
90
|
:color="buttonColor"
|
|
92
|
-
:icon="
|
|
91
|
+
:icon="getLayerIcon('closeCircle')"
|
|
93
92
|
light
|
|
94
93
|
size="xs"
|
|
95
94
|
:text="t('closeThisMessage')"
|
|
@@ -35,8 +35,6 @@ defineSlots<{
|
|
|
35
35
|
right?: () => VNode[]
|
|
36
36
|
}>()
|
|
37
37
|
|
|
38
|
-
const { getIcon } = useLayerIcons()
|
|
39
|
-
|
|
40
38
|
const hasAvatarBox = computed<boolean>(() => {
|
|
41
39
|
return !!(props.avatar || props.emoji || props.icon || props.image)
|
|
42
40
|
})
|
|
@@ -199,7 +197,7 @@ function onClick(event: MouseEvent) {
|
|
|
199
197
|
'text-2xl': size === 'lg',
|
|
200
198
|
}"
|
|
201
199
|
color="green"
|
|
202
|
-
:icon="
|
|
200
|
+
:icon="getLayerIcon('checkCircle')"
|
|
203
201
|
/>
|
|
204
202
|
|
|
205
203
|
<BaseIcon
|
|
@@ -210,7 +208,7 @@ function onClick(event: MouseEvent) {
|
|
|
210
208
|
'text-xl': size === 'base',
|
|
211
209
|
'text-2xl': size === 'lg',
|
|
212
210
|
}"
|
|
213
|
-
:icon="
|
|
211
|
+
:icon="getLayerIcon('chevronRight')"
|
|
214
212
|
/>
|
|
215
213
|
|
|
216
214
|
<slot name="right" />
|
|
@@ -18,7 +18,6 @@ const emit = defineEmits<{
|
|
|
18
18
|
}>()
|
|
19
19
|
|
|
20
20
|
const { t } = useI18n()
|
|
21
|
-
const { getIcon } = useLayerIcons()
|
|
22
21
|
|
|
23
22
|
function onNavigate(event: MouseEvent, direction: BaseDividerNavigateDirection) {
|
|
24
23
|
if (!props.loading) {
|
|
@@ -59,7 +58,7 @@ function onNavigate(event: MouseEvent, direction: BaseDividerNavigateDirection)
|
|
|
59
58
|
v-if="navigable && !loading && !hidePrevious"
|
|
60
59
|
class="mr-2 shrink-0 hover:!no-underline"
|
|
61
60
|
clickable
|
|
62
|
-
:icon="
|
|
61
|
+
:icon="getLayerIcon('chevronLeft')"
|
|
63
62
|
size="xs"
|
|
64
63
|
:text="t('previous')"
|
|
65
64
|
@click="onNavigate($event, 'previous')"
|
|
@@ -127,7 +126,7 @@ function onNavigate(event: MouseEvent, direction: BaseDividerNavigateDirection)
|
|
|
127
126
|
class="ml-2 shrink-0 hover:!no-underline"
|
|
128
127
|
clickable
|
|
129
128
|
reverse
|
|
130
|
-
:icon="
|
|
129
|
+
:icon="getLayerIcon('chevronRight')"
|
|
131
130
|
size="xs"
|
|
132
131
|
:text="t('next')"
|
|
133
132
|
@click="onNavigate($event, 'next')"
|
|
@@ -25,7 +25,6 @@ const emit = defineEmits<{
|
|
|
25
25
|
|
|
26
26
|
const confirming = ref(false)
|
|
27
27
|
const { t } = useI18n()
|
|
28
|
-
const { getIcon } = useLayerIcons()
|
|
29
28
|
|
|
30
29
|
const isClickable = computed(() => {
|
|
31
30
|
return props.clickable || props.to || props.confirmation
|
|
@@ -34,16 +33,16 @@ const isClickable = computed(() => {
|
|
|
34
33
|
const statusIcon = computed<string | undefined>(() => {
|
|
35
34
|
switch (props.status) {
|
|
36
35
|
case 'error': {
|
|
37
|
-
return
|
|
36
|
+
return getLayerIcon('closeCircle')
|
|
38
37
|
}
|
|
39
38
|
case 'info': {
|
|
40
|
-
return
|
|
39
|
+
return getLayerIcon('infoCircle')
|
|
41
40
|
}
|
|
42
41
|
case 'success': {
|
|
43
|
-
return
|
|
42
|
+
return getLayerIcon('checkCircle')
|
|
44
43
|
}
|
|
45
44
|
case 'warning': {
|
|
46
|
-
return
|
|
45
|
+
return getLayerIcon('exclamationCircle')
|
|
47
46
|
}
|
|
48
47
|
default: {
|
|
49
48
|
return undefined
|
|
@@ -10,18 +10,16 @@ const props = withDefaults(defineProps<BaseMetric>(), {
|
|
|
10
10
|
value: undefined,
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
-
const { getIcon } = useLayerIcons()
|
|
14
|
-
|
|
15
13
|
const performanceIcon = computed<string | undefined>(() => {
|
|
16
14
|
switch (props.performance) {
|
|
17
15
|
case 'down': {
|
|
18
|
-
return
|
|
16
|
+
return getLayerIcon('arrowDown')
|
|
19
17
|
}
|
|
20
18
|
case 'equal': {
|
|
21
|
-
return
|
|
19
|
+
return getLayerIcon('arrowRight')
|
|
22
20
|
}
|
|
23
21
|
case 'up': {
|
|
24
|
-
return
|
|
22
|
+
return getLayerIcon('arrowUp')
|
|
25
23
|
}
|
|
26
24
|
default: {
|
|
27
25
|
return undefined
|
|
@@ -51,7 +49,7 @@ const performanceIcon = computed<string | undefined>(() => {
|
|
|
51
49
|
v-if="performanceTooltip"
|
|
52
50
|
v-tooltip="performanceTooltip"
|
|
53
51
|
class="mr-1"
|
|
54
|
-
:icon="
|
|
52
|
+
:icon="getLayerIcon('infoCircle')"
|
|
55
53
|
/>
|
|
56
54
|
|
|
57
55
|
<BaseText
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { onKeyStroke } from '@vueuse/core'
|
|
3
3
|
import { Motion } from 'motion-v'
|
|
4
4
|
import type { BaseOverlay } from '../../types/bases'
|
|
5
|
-
import useMotion from '../../composables/useMotion'
|
|
6
5
|
|
|
7
6
|
const props = withDefaults(defineProps<BaseOverlay>(), {
|
|
8
7
|
active: true,
|
|
@@ -22,9 +21,6 @@ defineSlots<{
|
|
|
22
21
|
default?: () => VNode[]
|
|
23
22
|
}>()
|
|
24
23
|
|
|
25
|
-
const { getIcon } = useLayerIcons()
|
|
26
|
-
const { fadeIn } = useMotion()
|
|
27
|
-
|
|
28
24
|
const isClickable = computed(() => {
|
|
29
25
|
return props.clickable || props.hasClose
|
|
30
26
|
})
|
|
@@ -65,7 +61,7 @@ onKeyStroke('Escape', (event) => {
|
|
|
65
61
|
v-if="hasClose"
|
|
66
62
|
class="pointer-events-auto absolute right-4 top-4 z-50 text-gray-200 dark:text-gray-800"
|
|
67
63
|
clickable
|
|
68
|
-
:icon="
|
|
64
|
+
:icon="getLayerIcon('close')"
|
|
69
65
|
@click="onClose"
|
|
70
66
|
/>
|
|
71
67
|
|
|
@@ -29,7 +29,6 @@ defineSlots<{
|
|
|
29
29
|
|
|
30
30
|
const { locale, t } = useI18n()
|
|
31
31
|
const { translatedContent } = useTranslation(useSlots(), locale)
|
|
32
|
-
const { getIcon } = useLayerIcons()
|
|
33
32
|
const closed = ref(false)
|
|
34
33
|
|
|
35
34
|
const finalBackground = computed(() => {
|
|
@@ -148,7 +147,7 @@ function onClose(event: MouseEvent) {
|
|
|
148
147
|
'ml-3': size === 'sm',
|
|
149
148
|
'ml-4': size === 'base',
|
|
150
149
|
}"
|
|
151
|
-
:icon="
|
|
150
|
+
:icon="getLayerIcon('close')"
|
|
152
151
|
@click="onClose"
|
|
153
152
|
/>
|
|
154
153
|
</div>
|
|
@@ -11,8 +11,6 @@ const emit = defineEmits<{
|
|
|
11
11
|
trigger: [event: KeyboardEvent]
|
|
12
12
|
}>()
|
|
13
13
|
|
|
14
|
-
const { getIcon } = useLayerIcons()
|
|
15
|
-
|
|
16
14
|
function isTypingTarget(target: EventTarget | null) {
|
|
17
15
|
const element = target as HTMLElement | null
|
|
18
16
|
const typingTags = ['INPUT', 'SELECT', 'TEXTAREA']
|
|
@@ -53,7 +51,7 @@ onKeyStroke(
|
|
|
53
51
|
<Icon
|
|
54
52
|
v-if="shortcut.toLowerCase() === 'enter'"
|
|
55
53
|
class="size-2"
|
|
56
|
-
:name="
|
|
54
|
+
:name="getLayerIcon('enter')"
|
|
57
55
|
/>
|
|
58
56
|
|
|
59
57
|
<template v-else>
|
|
@@ -35,7 +35,6 @@ defineSlots<{
|
|
|
35
35
|
default?: () => VNode[]
|
|
36
36
|
}>()
|
|
37
37
|
|
|
38
|
-
const { getIcon } = useLayerIcons()
|
|
39
38
|
const hovered = ref(false)
|
|
40
39
|
const form = reactive({ name: '' })
|
|
41
40
|
|
|
@@ -136,7 +135,7 @@ function onRemove(event: MouseEvent) {
|
|
|
136
135
|
class="js-drag-handle mr-2 cursor-move"
|
|
137
136
|
clickable
|
|
138
137
|
color="gray"
|
|
139
|
-
:icon="
|
|
138
|
+
:icon="getLayerIcon('drag')"
|
|
140
139
|
/>
|
|
141
140
|
|
|
142
141
|
<span
|
|
@@ -190,7 +189,7 @@ function onRemove(event: MouseEvent) {
|
|
|
190
189
|
<BaseIcon
|
|
191
190
|
v-if="removable"
|
|
192
191
|
class="ml-1.5 text-red-700 dark:text-red-300 hover:text-black dark:hover:text-white"
|
|
193
|
-
:icon="
|
|
192
|
+
:icon="getLayerIcon('closeCircle')"
|
|
194
193
|
@click.prevent.stop="onRemove"
|
|
195
194
|
/>
|
|
196
195
|
</component>
|
|
@@ -34,7 +34,6 @@ const root = ref<HTMLDivElement>()
|
|
|
34
34
|
const showingAllTags = ref(false)
|
|
35
35
|
const showingTagCreationField = ref(false)
|
|
36
36
|
const { t } = useI18n()
|
|
37
|
-
const { getIcon } = useLayerIcons()
|
|
38
37
|
|
|
39
38
|
const sortedTags = computed({
|
|
40
39
|
get() {
|
|
@@ -143,7 +142,7 @@ function onUpdateTag(event: FocusEvent | KeyboardEvent, name: string, tagId?: nu
|
|
|
143
142
|
'm-1': size === 'base',
|
|
144
143
|
}"
|
|
145
144
|
color="indigo"
|
|
146
|
-
:icon="
|
|
145
|
+
:icon="getLayerIcon('back')"
|
|
147
146
|
:size="size"
|
|
148
147
|
:text="t('cancel')"
|
|
149
148
|
@click="onGoBack"
|
|
@@ -158,7 +157,7 @@ function onUpdateTag(event: FocusEvent | KeyboardEvent, name: string, tagId?: nu
|
|
|
158
157
|
}"
|
|
159
158
|
color="indigo"
|
|
160
159
|
:editable="showingTagCreationField"
|
|
161
|
-
:icon="showingTagCreationField ?
|
|
160
|
+
:icon="showingTagCreationField ? getLayerIcon('tags') : getLayerIcon('plus')"
|
|
162
161
|
is-creation
|
|
163
162
|
:light="false"
|
|
164
163
|
:size="size"
|
|
@@ -14,8 +14,6 @@ const emit = defineEmits<{
|
|
|
14
14
|
close: [event: KeyboardEvent | MouseEvent, toast: BaseToast]
|
|
15
15
|
}>()
|
|
16
16
|
|
|
17
|
-
const { getIcon } = useLayerIcons()
|
|
18
|
-
|
|
19
17
|
function onAction(event: KeyboardEvent | MouseEvent) {
|
|
20
18
|
if (props.action) {
|
|
21
19
|
emit('action', event, props)
|
|
@@ -82,7 +80,7 @@ function onClose(event: KeyboardEvent | MouseEvent) {
|
|
|
82
80
|
'group-hover:text-green-600 dark:group-hover:text-green-400': status === 'success',
|
|
83
81
|
'group-hover:text-orange-600 dark:group-hover:text-orange-400': status === 'warning',
|
|
84
82
|
}"
|
|
85
|
-
:icon="
|
|
83
|
+
:icon="getLayerIcon('close')"
|
|
86
84
|
/>
|
|
87
85
|
</template>
|
|
88
86
|
</div>
|
|
@@ -10,7 +10,6 @@ const emit = defineEmits<{
|
|
|
10
10
|
}>()
|
|
11
11
|
|
|
12
12
|
const { locale, t } = useI18n()
|
|
13
|
-
const { normalizeText } = useLayerUtils()
|
|
14
13
|
const modelValue = defineModel<FieldEmojis['modelValue']>({ default: '' })
|
|
15
14
|
const searchRaw = ref('')
|
|
16
15
|
const searchQuery = refDebounced(searchRaw, 250)
|
|
@@ -31,9 +31,6 @@ const emit = defineEmits<{
|
|
|
31
31
|
optionClick: [event: MouseEvent, value: string]
|
|
32
32
|
}>()
|
|
33
33
|
|
|
34
|
-
const { getIcon } = useLayerIcons()
|
|
35
|
-
const { fadeIn } = useMotion()
|
|
36
|
-
const { normalizeText } = useLayerUtils()
|
|
37
34
|
const { t } = useI18n()
|
|
38
35
|
const id = useId()
|
|
39
36
|
const modelValue = defineModel<FieldSelect['modelValue']>({ default: '' })
|
|
@@ -264,7 +261,7 @@ function selectOption(event: MouseEvent, value: string) {
|
|
|
264
261
|
class="ml-2 flex-initial"
|
|
265
262
|
:class="{ 'rotate-180': opened }"
|
|
266
263
|
color="gray"
|
|
267
|
-
:icon="
|
|
264
|
+
:icon="getLayerIcon('arrowDown')"
|
|
268
265
|
/>
|
|
269
266
|
</div>
|
|
270
267
|
|
|
@@ -320,7 +317,7 @@ function selectOption(event: MouseEvent, value: string) {
|
|
|
320
317
|
<BaseIcon
|
|
321
318
|
class="pointer-events-none"
|
|
322
319
|
color="gray"
|
|
323
|
-
:icon="
|
|
320
|
+
:icon="getLayerIcon('infoCircle')"
|
|
324
321
|
:size="size"
|
|
325
322
|
:text="t('noResults')"
|
|
326
323
|
/>
|
|
@@ -19,7 +19,6 @@ const emit = defineEmits<{
|
|
|
19
19
|
blur: [event: FocusEvent, value: string, name?: string]
|
|
20
20
|
}>()
|
|
21
21
|
|
|
22
|
-
const { getIcon } = useLayerIcons()
|
|
23
22
|
const id = useId()
|
|
24
23
|
const modelValue = defineModel<FieldTime['modelValue']>({ default: '' })
|
|
25
24
|
const inputRef = ref<HTMLInputElement>()
|
|
@@ -75,7 +74,7 @@ function onFieldBlur(event: FocusEvent) {
|
|
|
75
74
|
@keydown.space.prevent="onContainerKeyDown"
|
|
76
75
|
>
|
|
77
76
|
<BaseIcon
|
|
78
|
-
:icon="
|
|
77
|
+
:icon="getLayerIcon('clock')"
|
|
79
78
|
:size="size"
|
|
80
79
|
/>
|
|
81
80
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Capacitor } from '@capacitor/core'
|
|
2
|
+
|
|
3
|
+
export default function useCapacitor() {
|
|
4
|
+
const platform = ref(Capacitor.getPlatform() as Platforms)
|
|
5
|
+
|
|
6
|
+
const isAndroid = computed(() => {
|
|
7
|
+
return platform.value === 'android'
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const isIOS = computed(() => {
|
|
11
|
+
return platform.value === 'ios'
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const isNative = computed(() => {
|
|
15
|
+
return Capacitor.isNativePlatform()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const isWeb = computed(() => {
|
|
19
|
+
return platform.value === 'web'
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const getPlatform = () => {
|
|
23
|
+
if (isAndroid.value) {
|
|
24
|
+
return 'android'
|
|
25
|
+
}
|
|
26
|
+
else if (isIOS.value) {
|
|
27
|
+
return 'ios'
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return 'web'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const isPluginAvailable = (pluginName: string) => {
|
|
35
|
+
return Capacitor.isPluginAvailable(pluginName)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
getPlatform,
|
|
40
|
+
isAndroid,
|
|
41
|
+
isIOS,
|
|
42
|
+
isNative,
|
|
43
|
+
isPluginAvailable,
|
|
44
|
+
isWeb,
|
|
45
|
+
platform,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface AuthSocialConnect {
|
|
2
|
+
appleConfig?: AuthSocialConnectAppleConfig
|
|
3
|
+
googleConfig?: AuthSocialConnectGoogleConfig
|
|
4
|
+
loading?: boolean
|
|
5
|
+
provider: AuthSocialConnectProvider
|
|
6
|
+
size?: BaseButtonSize
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AuthSocialConnectAppleConfig {
|
|
10
|
+
ios: {
|
|
11
|
+
appId: string
|
|
12
|
+
redirectUrl: string
|
|
13
|
+
}
|
|
14
|
+
web: {
|
|
15
|
+
appId: string
|
|
16
|
+
redirectUrl: string
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AuthSocialConnectGoogleConfig {
|
|
21
|
+
ios: {
|
|
22
|
+
clientId: string
|
|
23
|
+
}
|
|
24
|
+
web: {
|
|
25
|
+
clientId: string
|
|
26
|
+
redirectUrl: string
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AuthSocialConnectProvider = 'apple' | 'google'
|
|
31
|
+
|
|
32
|
+
export type AuthSocialConnectSuccess
|
|
33
|
+
= | {
|
|
34
|
+
firstName?: string
|
|
35
|
+
idToken: string
|
|
36
|
+
lastName?: string
|
|
37
|
+
provider: 'apple'
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
idToken: string
|
|
41
|
+
provider: 'google'
|
|
42
|
+
}
|
package/app/types/global.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { Directive } from 'vue'
|
|
2
2
|
|
|
3
3
|
declare global {
|
|
4
|
+
// Auth
|
|
5
|
+
type AuthSocialConnect = import('./auth').AuthSocialConnect
|
|
6
|
+
type AuthSocialConnectAppleConfig = import('./auth').AuthSocialConnectAppleConfig
|
|
7
|
+
type AuthSocialConnectGoogleConfig = import('./auth').AuthSocialConnectGoogleConfig
|
|
8
|
+
type AuthSocialConnectProvider = import('./auth').AuthSocialConnectProvider
|
|
9
|
+
type AuthSocialConnectSuccess = import('./auth').AuthSocialConnectSuccess
|
|
10
|
+
|
|
4
11
|
// Bases
|
|
5
12
|
type BaseAlert = import('./bases').BaseAlert
|
|
6
13
|
type BaseAlignment = import('./bases').BaseAlignment
|
|
@@ -67,6 +74,8 @@ declare global {
|
|
|
67
74
|
type ChartistLineChartData = import('chartist').LineChartData
|
|
68
75
|
type ChartistOptions = import('chartist').Options
|
|
69
76
|
type ChartistPieChartData = import('chartist').PieChartData
|
|
77
|
+
type GoogleLoginResponseOnline = import('@capgo/capacitor-social-login').GoogleLoginResponseOnline
|
|
78
|
+
type Platforms = import('@saasmakers/shared').Platforms
|
|
70
79
|
|
|
71
80
|
// Fields
|
|
72
81
|
type FieldAvatar = import('./fields').FieldAvatar
|
|
@@ -101,8 +110,8 @@ declare global {
|
|
|
101
110
|
type LayoutModal = import('./layout').LayoutModal
|
|
102
111
|
|
|
103
112
|
// Project
|
|
104
|
-
type LayerIconIcon = import('../
|
|
105
|
-
type LayerIconValue = import('../
|
|
113
|
+
type LayerIconIcon = import('../utils/layerIcons').LayerIconIcon
|
|
114
|
+
type LayerIconValue = import('../utils/layerIcons').LayerIconValue
|
|
106
115
|
|
|
107
116
|
// Navigator
|
|
108
117
|
interface Navigator {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const fadeIn = {
|
|
2
|
+
animate: {
|
|
3
|
+
opacity: 1,
|
|
4
|
+
transition: { duration: 0.25 },
|
|
5
|
+
},
|
|
6
|
+
initial: { opacity: 0 },
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const fadeInLeft = {
|
|
10
|
+
animate: {
|
|
11
|
+
opacity: 1,
|
|
12
|
+
transition: { duration: 0.25 },
|
|
13
|
+
x: 0,
|
|
14
|
+
},
|
|
15
|
+
initial: {
|
|
16
|
+
opacity: 0,
|
|
17
|
+
x: -25,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const fadeInUp = {
|
|
22
|
+
animate: {
|
|
23
|
+
opacity: 1,
|
|
24
|
+
transition: { duration: 0.25 },
|
|
25
|
+
y: 0,
|
|
26
|
+
},
|
|
27
|
+
initial: {
|
|
28
|
+
opacity: 0,
|
|
29
|
+
y: 25,
|
|
30
|
+
},
|
|
31
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { AreaDrawEvent, BarChartOptions, BarDrawEvent, BaseChart, DrawEvent, LineDrawEvent, Options } from 'chartist'
|
|
2
|
+
import { easings } from 'chartist'
|
|
3
|
+
|
|
4
|
+
type EasingType = keyof typeof easings | number[]
|
|
5
|
+
|
|
6
|
+
export function progressiveLinePlugin(params: {
|
|
7
|
+
animateArea?: boolean
|
|
8
|
+
delay?: number
|
|
9
|
+
duration?: number
|
|
10
|
+
easing?: EasingType
|
|
11
|
+
stagger?: number
|
|
12
|
+
} = {}) {
|
|
13
|
+
const animateArea = params.animateArea ?? true
|
|
14
|
+
const delay = params.delay ?? 0
|
|
15
|
+
const duration = params.duration ?? 500
|
|
16
|
+
const easing = params.easing ?? easings.easeOutQuart
|
|
17
|
+
const stagger = params.stagger ?? 0
|
|
18
|
+
|
|
19
|
+
return (chart: BaseChart) => {
|
|
20
|
+
chart.on('draw', (ctx: DrawEvent) => {
|
|
21
|
+
const begin = delay + (stagger ? stagger * (ctx.seriesIndex ?? 0) : 0)
|
|
22
|
+
|
|
23
|
+
// Animation for line charts
|
|
24
|
+
if (ctx.type === 'line') {
|
|
25
|
+
const lineCtx = ctx as LineDrawEvent
|
|
26
|
+
const node = lineCtx.element.getNode<SVGPathElement>()
|
|
27
|
+
const length = node.getTotalLength()
|
|
28
|
+
|
|
29
|
+
// Set the stroke dasharray and dashoffset
|
|
30
|
+
lineCtx.element.attr({
|
|
31
|
+
'stroke-dasharray': `${length}px ${length}px`,
|
|
32
|
+
'stroke-dashoffset': `${length}px`,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Animate the stroke dashoffset to 0
|
|
36
|
+
lineCtx.element.animate({
|
|
37
|
+
'stroke-dashoffset': {
|
|
38
|
+
begin,
|
|
39
|
+
dur: duration,
|
|
40
|
+
easing,
|
|
41
|
+
fill: 'freeze',
|
|
42
|
+
from: `${length}px`,
|
|
43
|
+
to: '0px',
|
|
44
|
+
},
|
|
45
|
+
}, false)
|
|
46
|
+
|
|
47
|
+
// Clean up the dash attributes after it finishes
|
|
48
|
+
const total = delay + duration + (stagger ? stagger * (lineCtx.seriesIndex ?? 0) : 0)
|
|
49
|
+
|
|
50
|
+
globalThis.setTimeout(() => {
|
|
51
|
+
lineCtx.element.attr({
|
|
52
|
+
'stroke-dasharray': undefined,
|
|
53
|
+
'stroke-dashoffset': undefined,
|
|
54
|
+
})
|
|
55
|
+
}, total + 30)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Animation for area charts
|
|
59
|
+
if (animateArea && ctx.type === 'area') {
|
|
60
|
+
const areaCtx = ctx as AreaDrawEvent
|
|
61
|
+
|
|
62
|
+
areaCtx.element.animate({
|
|
63
|
+
|
|
64
|
+
d: {
|
|
65
|
+
begin,
|
|
66
|
+
dur: duration,
|
|
67
|
+
easing,
|
|
68
|
+
fill: 'freeze',
|
|
69
|
+
from: areaCtx.path
|
|
70
|
+
.clone()
|
|
71
|
+
.scale(1, 0)
|
|
72
|
+
.translate(0, areaCtx.chartRect.height())
|
|
73
|
+
.stringify(),
|
|
74
|
+
to: areaCtx.path.stringify(),
|
|
75
|
+
},
|
|
76
|
+
}, false)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Animation for bar charts
|
|
80
|
+
if (ctx.type === 'bar') {
|
|
81
|
+
const barCtx = ctx as BarDrawEvent
|
|
82
|
+
|
|
83
|
+
// Access private options property via type assertion
|
|
84
|
+
const chartOptions = (chart as unknown as {
|
|
85
|
+
options?: Options & Partial<BarChartOptions>
|
|
86
|
+
}).options
|
|
87
|
+
|
|
88
|
+
const horizontal = !!(chartOptions as BarChartOptions | undefined)?.horizontalBars
|
|
89
|
+
|
|
90
|
+
// For vertical bars, y1 is baseline, y2 is top. For horizontal, x1 is baseline, x2 is end.
|
|
91
|
+
const from = horizontal ? barCtx.x1 : barCtx.y1
|
|
92
|
+
const to = horizontal ? barCtx.x2 : barCtx.y2
|
|
93
|
+
|
|
94
|
+
// Start collapsed at baseline
|
|
95
|
+
if (horizontal) {
|
|
96
|
+
barCtx.element.attr({ x2: from })
|
|
97
|
+
|
|
98
|
+
barCtx.element.animate({
|
|
99
|
+
x2: {
|
|
100
|
+
begin,
|
|
101
|
+
dur: duration,
|
|
102
|
+
easing,
|
|
103
|
+
fill: 'freeze',
|
|
104
|
+
from: String(from),
|
|
105
|
+
to: String(to),
|
|
106
|
+
},
|
|
107
|
+
}, false)
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
barCtx.element.attr({ y2: from })
|
|
111
|
+
|
|
112
|
+
barCtx.element.animate({
|
|
113
|
+
y2: {
|
|
114
|
+
begin,
|
|
115
|
+
dur: duration,
|
|
116
|
+
easing,
|
|
117
|
+
fill: 'freeze',
|
|
118
|
+
from: String(from),
|
|
119
|
+
to: String(to),
|
|
120
|
+
},
|
|
121
|
+
}, false)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import numbroLib from 'numbro'
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
return {
|
|
5
|
-
normalizeText,
|
|
6
|
-
numbro,
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function normalizeText(text: string) {
|
|
3
|
+
export function normalizeText(text: string) {
|
|
11
4
|
return text
|
|
12
5
|
.toLowerCase()
|
|
13
6
|
.normalize('NFD')
|
|
14
7
|
.replaceAll(/[\u0300-\u036F]/g, '')
|
|
15
8
|
}
|
|
16
9
|
|
|
17
|
-
function numbro(number: '∞' | number | undefined, format?: string) {
|
|
10
|
+
export function numbro(number: '∞' | number | undefined, format?: string) {
|
|
18
11
|
if (!number && number !== 0) {
|
|
19
12
|
return ''
|
|
20
13
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Custom icons
|
|
9
9
|
// -> ./assets/icons
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
export const layerIcons = {
|
|
12
12
|
arrowDown: 'solar:alt-arrow-down-bold',
|
|
13
13
|
arrowRight: 'solar:alt-arrow-right-bold',
|
|
14
14
|
arrowUp: 'solar:alt-arrow-up-bold',
|
|
@@ -28,19 +28,12 @@ const icons = {
|
|
|
28
28
|
tags: 'hugeicons:tags',
|
|
29
29
|
} as const
|
|
30
30
|
|
|
31
|
-
export type LayerIconIcon = keyof typeof
|
|
31
|
+
export type LayerIconIcon = keyof typeof layerIcons
|
|
32
32
|
|
|
33
|
-
export type LayerIconValue = typeof
|
|
33
|
+
export type LayerIconValue = typeof layerIcons[LayerIconIcon]
|
|
34
34
|
|
|
35
|
-
export
|
|
36
|
-
const iconsNames = Object.keys(icons)
|
|
35
|
+
export const layerIconNames = Object.keys(layerIcons) as LayerIconIcon[]
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
getIcon,
|
|
44
|
-
iconsNames,
|
|
45
|
-
}
|
|
37
|
+
export function getLayerIcon(attribute: LayerIconIcon): LayerIconValue {
|
|
38
|
+
return layerIcons[attribute] || layerIcons.default
|
|
46
39
|
}
|
package/nuxt.config.ts
CHANGED
|
@@ -6,6 +6,7 @@ import uno from './uno.config.js'
|
|
|
6
6
|
|
|
7
7
|
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
8
8
|
const isPostHogEnabled = process.env.NODE_ENV === 'production' && Boolean(process.env.NUXT_PUBLIC_POSTHOG_KEY)
|
|
9
|
+
const isSentryEnabled = process.env.NODE_ENV === 'production'
|
|
9
10
|
|
|
10
11
|
export default defineNuxtConfig({
|
|
11
12
|
modules: [
|
|
@@ -23,9 +24,14 @@ export default defineNuxtConfig({
|
|
|
23
24
|
'@vueuse/nuxt',
|
|
24
25
|
'floating-vue/nuxt',
|
|
25
26
|
'motion-v/nuxt',
|
|
27
|
+
...(isSentryEnabled ? ['@sentry/nuxt/module'] : []),
|
|
26
28
|
],
|
|
27
29
|
|
|
28
30
|
components: [
|
|
31
|
+
{
|
|
32
|
+
path: 'components/auth',
|
|
33
|
+
pathPrefix: false,
|
|
34
|
+
},
|
|
29
35
|
{
|
|
30
36
|
path: 'components/bases',
|
|
31
37
|
pathPrefix: false,
|
|
@@ -114,6 +120,8 @@ export default defineNuxtConfig({
|
|
|
114
120
|
|
|
115
121
|
robots: { credits: false },
|
|
116
122
|
|
|
123
|
+
sourcemap: { client: 'hidden' },
|
|
124
|
+
|
|
117
125
|
sitemap: {
|
|
118
126
|
autoLastmod: true,
|
|
119
127
|
credits: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasmakers/ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable Nuxt UI components for SaaS Makers projects",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"uno.config.ts"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"@capacitor/core": "8.4.0",
|
|
24
25
|
"@capacitor/preferences": "8.0.1",
|
|
26
|
+
"@capgo/capacitor-social-login": "8.3.22",
|
|
25
27
|
"@nuxt/icon": "2.2.3",
|
|
26
28
|
"@nuxt/scripts": "0.13.2",
|
|
27
29
|
"@nuxtjs/color-mode": "3.5.2",
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
"@nuxtjs/sitemap": "8.2.0",
|
|
32
34
|
"@pinia/nuxt": "0.11.3",
|
|
33
35
|
"@posthog/nuxt": "1.7.76",
|
|
36
|
+
"@sentry/nuxt": "10.57.0",
|
|
34
37
|
"@unhead/vue": "2.0.19",
|
|
35
38
|
"@unocss/nuxt": "66.7.0",
|
|
36
39
|
"@unocss/reset": "66.7.0",
|
|
@@ -53,8 +56,8 @@
|
|
|
53
56
|
"devDependencies": {
|
|
54
57
|
"nuxt": "4.3.1",
|
|
55
58
|
"typescript": "5.9.3",
|
|
56
|
-
"@saasmakers/
|
|
57
|
-
"@saasmakers/
|
|
59
|
+
"@saasmakers/apps-helpers": "0.1.0",
|
|
60
|
+
"@saasmakers/shared": "0.2.8"
|
|
58
61
|
},
|
|
59
62
|
"peerDependencies": {
|
|
60
63
|
"@saasmakers/apps-helpers": "^0.1.0",
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="842" height="1e3" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill="#000000" d="M702 960c-54.2 52.6-114 44.4-171 19.6-60.6-25.3-116-26.9-180 0-79.7 34.4-122 24.4-170-19.6-271-279-231-704 77-720 74.7 4 127 41.3 171 44.4 65.4-13.3 128-51.4 198-46.4 84.1 6.8 147 40 189 99.7-173 104-132 332 26.9 396-31.8 83.5-72.6 166-141 227zM423 237C414.9 113 515.4 11 631 1c15.9 143-130 250-208 236z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="842" height="1e3" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill="#FFFFFF" d="M702 960c-54.2 52.6-114 44.4-171 19.6-60.6-25.3-116-26.9-180 0-79.7 34.4-122 24.4-170-19.6-271-279-231-704 77-720 74.7 4 127 41.3 171 44.4 65.4-13.3 128-51.4 198-46.4 84.1 6.8 147 40 189 99.7-173 104-132 332 26.9 396-31.8 83.5-72.6 166-141 227zM423 237C414.9 113 515.4 11 631 1c15.9 143-130 250-208 236z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg width="47px" height="48px" viewBox="0 0 47 48" version="1.1"
|
|
3
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
+
xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
5
|
+
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
|
|
6
|
+
<title>Google_color</title>
|
|
7
|
+
<desc>Created with Sketch.</desc>
|
|
8
|
+
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
9
|
+
<g id="Color-" transform="translate(-401.000000, -860.000000)">
|
|
10
|
+
<g id="Google" transform="translate(401.000000, 860.000000)">
|
|
11
|
+
<path d="M9.82727273,24 C9.82727273,22.4757333 10.0804318,21.0144 10.5322727,19.6437333 L2.62345455,13.6042667 C1.08206818,16.7338667 0.213636364,20.2602667 0.213636364,24 C0.213636364,27.7365333 1.081,31.2608 2.62025,34.3882667 L10.5247955,28.3370667 C10.0772273,26.9728 9.82727273,25.5168 9.82727273,24" id="Fill-1" fill="#FBBC05"></path>
|
|
12
|
+
<path d="M23.7136364,10.1333333 C27.025,10.1333333 30.0159091,11.3066667 32.3659091,13.2266667 L39.2022727,6.4 C35.0363636,2.77333333 29.6954545,0.533333333 23.7136364,0.533333333 C14.4268636,0.533333333 6.44540909,5.84426667 2.62345455,13.6042667 L10.5322727,19.6437333 C12.3545909,14.112 17.5491591,10.1333333 23.7136364,10.1333333" id="Fill-2" fill="#EA4335"></path>
|
|
13
|
+
<path d="M23.7136364,37.8666667 C17.5491591,37.8666667 12.3545909,33.888 10.5322727,28.3562667 L2.62345455,34.3946667 C6.44540909,42.1557333 14.4268636,47.4666667 23.7136364,47.4666667 C29.4455,47.4666667 34.9177955,45.4314667 39.0249545,41.6181333 L31.5177727,35.8144 C29.3995682,37.1488 26.7323182,37.8666667 23.7136364,37.8666667" id="Fill-3" fill="#34A853"></path>
|
|
14
|
+
<path d="M46.1454545,24 C46.1454545,22.6133333 45.9318182,21.12 45.6113636,19.7333333 L23.7136364,19.7333333 L23.7136364,28.8 L36.3181818,28.8 C35.6879545,31.8912 33.9724545,34.2677333 31.5177727,35.8144 L39.0249545,41.6181333 C43.3393409,37.6138667 46.1454545,31.6490667 46.1454545,24" id="Fill-4" fill="#4285F4"></path>
|
|
15
|
+
</g>
|
|
16
|
+
</g>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import type { AreaDrawEvent, BarChartOptions, BarDrawEvent, BaseChart, DrawEvent, LineDrawEvent, Options } from 'chartist'
|
|
2
|
-
import { easings } from 'chartist'
|
|
3
|
-
|
|
4
|
-
export default function useChartist() {
|
|
5
|
-
type EasingType = keyof typeof easings | number[]
|
|
6
|
-
|
|
7
|
-
const progressiveLinePlugin = (params: {
|
|
8
|
-
animateArea?: boolean
|
|
9
|
-
delay?: number
|
|
10
|
-
duration?: number
|
|
11
|
-
easing?: EasingType
|
|
12
|
-
stagger?: number
|
|
13
|
-
} = {}) => {
|
|
14
|
-
const animateArea = params.animateArea ?? true
|
|
15
|
-
const delay = params.delay ?? 0
|
|
16
|
-
const duration = params.duration ?? 500
|
|
17
|
-
const easing = params.easing ?? easings.easeOutQuart
|
|
18
|
-
const stagger = params.stagger ?? 0
|
|
19
|
-
|
|
20
|
-
return (chart: BaseChart) => {
|
|
21
|
-
chart.on('draw', (ctx: DrawEvent) => {
|
|
22
|
-
const begin = delay + (stagger ? stagger * (ctx.seriesIndex ?? 0) : 0)
|
|
23
|
-
|
|
24
|
-
// Animation for line charts
|
|
25
|
-
if (ctx.type === 'line') {
|
|
26
|
-
const lineCtx = ctx as LineDrawEvent
|
|
27
|
-
const node = lineCtx.element.getNode<SVGPathElement>()
|
|
28
|
-
const length = node.getTotalLength()
|
|
29
|
-
|
|
30
|
-
// Set the stroke dasharray and dashoffset
|
|
31
|
-
lineCtx.element.attr({
|
|
32
|
-
'stroke-dasharray': `${length}px ${length}px`,
|
|
33
|
-
'stroke-dashoffset': `${length}px`,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
// Animate the stroke dashoffset to 0
|
|
37
|
-
lineCtx.element.animate({
|
|
38
|
-
'stroke-dashoffset': {
|
|
39
|
-
begin,
|
|
40
|
-
dur: duration,
|
|
41
|
-
easing,
|
|
42
|
-
fill: 'freeze',
|
|
43
|
-
from: `${length}px`,
|
|
44
|
-
to: '0px',
|
|
45
|
-
},
|
|
46
|
-
}, false)
|
|
47
|
-
|
|
48
|
-
// Clean up the dash attributes after it finishes
|
|
49
|
-
const total = delay + duration + (stagger ? stagger * (lineCtx.seriesIndex ?? 0) : 0)
|
|
50
|
-
|
|
51
|
-
globalThis.setTimeout(() => {
|
|
52
|
-
lineCtx.element.attr({
|
|
53
|
-
'stroke-dasharray': undefined,
|
|
54
|
-
'stroke-dashoffset': undefined,
|
|
55
|
-
})
|
|
56
|
-
}, total + 30)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Animation for area charts
|
|
60
|
-
if (animateArea && ctx.type === 'area') {
|
|
61
|
-
const areaCtx = ctx as AreaDrawEvent
|
|
62
|
-
|
|
63
|
-
areaCtx.element.animate({
|
|
64
|
-
|
|
65
|
-
d: {
|
|
66
|
-
begin,
|
|
67
|
-
dur: duration,
|
|
68
|
-
easing,
|
|
69
|
-
fill: 'freeze',
|
|
70
|
-
from: areaCtx.path
|
|
71
|
-
.clone()
|
|
72
|
-
.scale(1, 0)
|
|
73
|
-
.translate(0, areaCtx.chartRect.height())
|
|
74
|
-
.stringify(),
|
|
75
|
-
to: areaCtx.path.stringify(),
|
|
76
|
-
},
|
|
77
|
-
}, false)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Animation for bar charts
|
|
81
|
-
if (ctx.type === 'bar') {
|
|
82
|
-
const barCtx = ctx as BarDrawEvent
|
|
83
|
-
|
|
84
|
-
// Access private options property via type assertion
|
|
85
|
-
const chartOptions = (chart as unknown as {
|
|
86
|
-
options?: Options & Partial<BarChartOptions>
|
|
87
|
-
}).options
|
|
88
|
-
|
|
89
|
-
const horizontal = !!(chartOptions as BarChartOptions | undefined)?.horizontalBars
|
|
90
|
-
|
|
91
|
-
// For vertical bars, y1 is baseline, y2 is top. For horizontal, x1 is baseline, x2 is end.
|
|
92
|
-
const from = horizontal ? barCtx.x1 : barCtx.y1
|
|
93
|
-
const to = horizontal ? barCtx.x2 : barCtx.y2
|
|
94
|
-
|
|
95
|
-
// Start collapsed at baseline
|
|
96
|
-
if (horizontal) {
|
|
97
|
-
barCtx.element.attr({ x2: from })
|
|
98
|
-
|
|
99
|
-
barCtx.element.animate({
|
|
100
|
-
x2: {
|
|
101
|
-
begin,
|
|
102
|
-
dur: duration,
|
|
103
|
-
easing,
|
|
104
|
-
fill: 'freeze',
|
|
105
|
-
from: String(from),
|
|
106
|
-
to: String(to),
|
|
107
|
-
},
|
|
108
|
-
}, false)
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
barCtx.element.attr({ y2: from })
|
|
112
|
-
|
|
113
|
-
barCtx.element.animate({
|
|
114
|
-
y2: {
|
|
115
|
-
begin,
|
|
116
|
-
dur: duration,
|
|
117
|
-
easing,
|
|
118
|
-
fill: 'freeze',
|
|
119
|
-
from: String(from),
|
|
120
|
-
to: String(to),
|
|
121
|
-
},
|
|
122
|
-
}, false)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { progressiveLinePlugin }
|
|
130
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export default function useMotion() {
|
|
2
|
-
const fadeIn = {
|
|
3
|
-
animate: {
|
|
4
|
-
opacity: 1,
|
|
5
|
-
transition: { duration: 0.25 },
|
|
6
|
-
},
|
|
7
|
-
initial: { opacity: 0 },
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const fadeInLeft = {
|
|
11
|
-
animate: {
|
|
12
|
-
opacity: 1,
|
|
13
|
-
transition: { duration: 0.25 },
|
|
14
|
-
x: 0,
|
|
15
|
-
},
|
|
16
|
-
initial: {
|
|
17
|
-
opacity: 0,
|
|
18
|
-
x: -25,
|
|
19
|
-
},
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const fadeInUp = {
|
|
23
|
-
animate: {
|
|
24
|
-
opacity: 1,
|
|
25
|
-
transition: { duration: 0.25 },
|
|
26
|
-
y: 0,
|
|
27
|
-
},
|
|
28
|
-
initial: {
|
|
29
|
-
opacity: 0,
|
|
30
|
-
y: 25,
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
fadeIn,
|
|
36
|
-
fadeInLeft,
|
|
37
|
-
fadeInUp,
|
|
38
|
-
}
|
|
39
|
-
}
|