@fy-/fws-vue 2.1.6 → 2.1.8
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/components/fws/CmsArticleBoxed.vue +23 -20
- package/components/fws/CmsArticleSingle.vue +74 -68
- package/components/fws/DataTable.vue +132 -125
- package/components/fws/FilterData.vue +99 -101
- package/components/fws/UserData.vue +33 -32
- package/components/fws/UserFlow.vue +163 -155
- package/components/fws/UserOAuth2.vue +73 -72
- package/components/fws/UserProfile.vue +98 -101
- package/components/fws/UserProfileStrict.vue +65 -64
- package/components/ssr/ClientOnly.ts +7 -7
- package/components/ui/DefaultBreadcrumb.vue +13 -13
- package/components/ui/DefaultConfirm.vue +35 -34
- package/components/ui/DefaultDateSelection.vue +19 -17
- package/components/ui/DefaultDropdown.vue +25 -25
- package/components/ui/DefaultDropdownLink.vue +15 -14
- package/components/ui/DefaultGallery.vue +179 -168
- package/components/ui/DefaultInput.vue +121 -126
- package/components/ui/DefaultLoader.vue +17 -17
- package/components/ui/DefaultModal.vue +35 -33
- package/components/ui/DefaultNotif.vue +50 -52
- package/components/ui/DefaultPaging.vue +92 -95
- package/components/ui/DefaultSidebar.vue +29 -25
- package/components/ui/DefaultTagInput.vue +121 -119
- package/components/ui/transitions/CollapseTransition.vue +1 -1
- package/components/ui/transitions/ExpandTransition.vue +1 -1
- package/components/ui/transitions/FadeTransition.vue +1 -1
- package/components/ui/transitions/ScaleTransition.vue +1 -1
- package/components/ui/transitions/SlideTransition.vue +3 -3
- package/composables/event-bus.ts +10 -8
- package/composables/rest.ts +59 -56
- package/composables/seo.ts +111 -95
- package/composables/ssr.ts +64 -62
- package/composables/templating.ts +57 -57
- package/composables/translations.ts +13 -13
- package/env.d.ts +6 -4
- package/index.ts +101 -98
- package/package.json +7 -7
- package/stores/serverRouter.ts +25 -25
- package/stores/user.ts +79 -72
- package/types.d.ts +65 -65
|
@@ -1,81 +1,82 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { useEventBus } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
2
|
+
import { computed, onMounted, ref } from 'vue'
|
|
3
|
+
import { useEventBus } from '../../composables/event-bus'
|
|
4
|
+
import { useRest } from '../../composables/rest'
|
|
5
|
+
import { useTranslation } from '../../composables/translations'
|
|
6
|
+
import { useUserStore } from '../../stores/user'
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
8
|
+
import DefaultModal from '../ui/DefaultModal.vue'
|
|
9
|
+
|
|
10
|
+
const rest = useRest()
|
|
11
|
+
const eventBus = useEventBus()
|
|
12
|
+
const userStore = useUserStore()
|
|
13
|
+
const isAuth = computed(() => userStore.isAuth)
|
|
14
|
+
const data = ref()
|
|
15
|
+
const providersData = ref()
|
|
16
|
+
const usedProviders = ref<Record<string, boolean>>({})
|
|
16
17
|
const props = defineProps({
|
|
17
18
|
returnTo: {
|
|
18
19
|
type: String,
|
|
19
20
|
required: false,
|
|
20
|
-
default:
|
|
21
|
+
default: '/user/account?tab=user_settings',
|
|
21
22
|
},
|
|
22
|
-
})
|
|
23
|
-
const returnTo = ref(props.returnTo)
|
|
24
|
-
if (returnTo.value
|
|
25
|
-
returnTo.value =
|
|
23
|
+
})
|
|
24
|
+
const returnTo = ref(props.returnTo)
|
|
25
|
+
if (returnTo.value === '') {
|
|
26
|
+
returnTo.value = '/user/account?tab=user_settings'
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
eventBus.emit(
|
|
29
|
-
const d = await rest(
|
|
30
|
-
if (d && d.result
|
|
31
|
-
providersData.value = d.data
|
|
28
|
+
async function getOAuth2Providers() {
|
|
29
|
+
eventBus.emit('main-loading', true)
|
|
30
|
+
const d = await rest('User/OAuth2/Providers', 'GET')
|
|
31
|
+
if (d && d.result === 'success') {
|
|
32
|
+
providersData.value = d.data
|
|
32
33
|
}
|
|
33
|
-
eventBus.emit(
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
eventBus.emit(
|
|
37
|
-
const d = await rest(`User/OAuth2/Provider/${providerUUID}`,
|
|
34
|
+
eventBus.emit('main-loading', false)
|
|
35
|
+
}
|
|
36
|
+
async function getOAuth2Redirect(providerUUID: string) {
|
|
37
|
+
eventBus.emit('main-loading', true)
|
|
38
|
+
const d = await rest(`User/OAuth2/Provider/${providerUUID}`, 'POST', {
|
|
38
39
|
ReturnTo: returnTo.value,
|
|
39
|
-
})
|
|
40
|
-
if (d && d.result
|
|
41
|
-
window.location.href = d.data
|
|
40
|
+
})
|
|
41
|
+
if (d && d.result === 'success') {
|
|
42
|
+
window.location.href = d.data
|
|
42
43
|
}
|
|
43
|
-
eventBus.emit(
|
|
44
|
-
}
|
|
45
|
-
const translate = useTranslation()
|
|
46
|
-
|
|
47
|
-
eventBus.emit(
|
|
48
|
-
title: translate(
|
|
49
|
-
desc: translate(
|
|
44
|
+
eventBus.emit('main-loading', false)
|
|
45
|
+
}
|
|
46
|
+
const translate = useTranslation()
|
|
47
|
+
async function deleteOAuth2Connection(providerUUID: string) {
|
|
48
|
+
eventBus.emit('showConfirm', {
|
|
49
|
+
title: translate('remove_provider_confirm_title'),
|
|
50
|
+
desc: translate('remove_provider_confirm_desc_warning'),
|
|
50
51
|
onConfirm: async () => {
|
|
51
|
-
eventBus.emit(
|
|
52
|
-
const d = await rest(`User/OAuth2/Provider/${providerUUID}`,
|
|
53
|
-
if (d && d.result
|
|
54
|
-
getOAuth2User()
|
|
52
|
+
eventBus.emit('main-loading', true)
|
|
53
|
+
const d = await rest(`User/OAuth2/Provider/${providerUUID}`, 'DELETE')
|
|
54
|
+
if (d && d.result === 'success') {
|
|
55
|
+
getOAuth2User()
|
|
55
56
|
}
|
|
56
|
-
eventBus.emit(
|
|
57
|
+
eventBus.emit('main-loading', false)
|
|
57
58
|
},
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
eventBus.emit(
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
async function getOAuth2User() {
|
|
62
|
+
eventBus.emit('main-loading', true)
|
|
62
63
|
if (!isAuth.value) {
|
|
63
|
-
return
|
|
64
|
+
return
|
|
64
65
|
}
|
|
65
|
-
const d = await rest(
|
|
66
|
-
usedProviders.value = {}
|
|
67
|
-
if (d && d.result
|
|
68
|
-
data.value = d.data
|
|
66
|
+
const d = await rest('User/OAuth2', 'GET')
|
|
67
|
+
usedProviders.value = {}
|
|
68
|
+
if (d && d.result === 'success') {
|
|
69
|
+
data.value = d.data
|
|
69
70
|
data.value.forEach((p: any) => {
|
|
70
|
-
usedProviders.value[p.ProviderUUID] = true
|
|
71
|
-
})
|
|
71
|
+
usedProviders.value[p.ProviderUUID] = true
|
|
72
|
+
})
|
|
72
73
|
}
|
|
73
|
-
eventBus.emit(
|
|
74
|
-
}
|
|
74
|
+
eventBus.emit('main-loading', false)
|
|
75
|
+
}
|
|
75
76
|
onMounted(() => {
|
|
76
|
-
getOAuth2User()
|
|
77
|
-
getOAuth2Providers()
|
|
78
|
-
})
|
|
77
|
+
getOAuth2User()
|
|
78
|
+
getOAuth2Providers()
|
|
79
|
+
})
|
|
79
80
|
</script>
|
|
80
81
|
|
|
81
82
|
<template>
|
|
@@ -83,28 +84,28 @@ onMounted(() => {
|
|
|
83
84
|
<DefaultModal id="providers" :title="$t('providers_modal_title')">
|
|
84
85
|
<template v-for="provider in providersData" :key="provider.UUID">
|
|
85
86
|
<div
|
|
86
|
-
class="flex items-center gap-3"
|
|
87
87
|
v-if="!usedProviders[provider.UUID]"
|
|
88
|
+
class="flex items-center gap-3"
|
|
88
89
|
>
|
|
89
90
|
<button
|
|
90
|
-
@click="
|
|
91
|
-
() => {
|
|
92
|
-
getOAuth2Redirect(provider.UUID);
|
|
93
|
-
}
|
|
94
|
-
"
|
|
95
91
|
class="flex border border-fv-neutral-300 dark:border-fv-neutral-700 shadow items-center gap-2 justify-start btn neutral defaults w-full mx-auto !font-semibold"
|
|
96
92
|
:style="`background: ${
|
|
97
93
|
provider.Data.Button.button['background-color']
|
|
98
94
|
}; color: ${$getContrastingTextColor(
|
|
99
95
|
provider.Data.Button.button['background-color'],
|
|
100
96
|
)}`"
|
|
97
|
+
@click="
|
|
98
|
+
() => {
|
|
99
|
+
getOAuth2Redirect(provider.UUID);
|
|
100
|
+
}
|
|
101
|
+
"
|
|
101
102
|
>
|
|
102
103
|
<img
|
|
103
104
|
:key="`${provider.Data.Button.label}oauth`"
|
|
104
105
|
class="h-12 w-12 block p-2 mr-3"
|
|
105
106
|
:alt="provider.Data.Button.info.Name"
|
|
106
107
|
:src="provider.Data.Button.button.logo"
|
|
107
|
-
|
|
108
|
+
>
|
|
108
109
|
<div>
|
|
109
110
|
{{
|
|
110
111
|
$t("user_flow_signin_with", {
|
|
@@ -130,27 +131,27 @@ onMounted(() => {
|
|
|
130
131
|
</button>
|
|
131
132
|
</h2>
|
|
132
133
|
<p
|
|
133
|
-
class="text-red-900 dark:text-red-300 text-sm bg-red-200/[.2] dark:bg-red-900/[.2] p-2 rounded shadow"
|
|
134
134
|
v-if="
|
|
135
|
-
$route.query.error
|
|
136
|
-
|
|
135
|
+
$route.query.error
|
|
136
|
+
&& $route.query.error === 'user_oauth2_connection_exists'
|
|
137
137
|
"
|
|
138
|
+
class="text-red-900 dark:text-red-300 text-sm bg-red-200/[.2] dark:bg-red-900/[.2] p-2 rounded shadow"
|
|
138
139
|
>
|
|
139
140
|
{{ $t("oauth2_error_user_oauth2_connection_exists") }}
|
|
140
141
|
</p>
|
|
141
|
-
<div v-if="data && data.length
|
|
142
|
+
<div v-if="data && data.length === 0">
|
|
142
143
|
<p>{{ $t("providers_empty") }}</p>
|
|
143
144
|
</div>
|
|
144
145
|
<div
|
|
145
146
|
v-for="provider in data"
|
|
146
|
-
class="flex items-center gap-3"
|
|
147
147
|
:key="provider.ProviderUUID"
|
|
148
|
+
class="flex items-center gap-3"
|
|
148
149
|
>
|
|
149
150
|
<img
|
|
150
151
|
:src="provider.Provider.Button.button.logo"
|
|
151
152
|
class="w-14 h-14 p-1 rounded-full"
|
|
152
153
|
:style="`background-color: ${provider.Provider.Button.button['background-color']}`"
|
|
153
|
-
|
|
154
|
+
>
|
|
154
155
|
<div>
|
|
155
156
|
<h3 class="text-xl">
|
|
156
157
|
{{ provider.Provider.Button.name }}
|
|
@@ -1,73 +1,73 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { useEventBus } from
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import
|
|
2
|
+
import { Uploader } from '@fy-/fws-js'
|
|
3
|
+
import useVuelidate from '@vuelidate/core'
|
|
4
|
+
import { maxLength, required } from '@vuelidate/validators'
|
|
5
|
+
import { computed, reactive, ref, watchEffect } from 'vue'
|
|
6
|
+
import VuePictureCropper, { cropper } from 'vue-picture-cropper'
|
|
7
|
+
import { useEventBus } from '../../composables/event-bus'
|
|
8
|
+
import { useRest } from '../../composables/rest'
|
|
9
|
+
import { useUserStore } from '../../stores/user'
|
|
10
|
+
import DefaultInput from '../ui/DefaultInput.vue'
|
|
11
|
+
import DefaultModal from '../ui/DefaultModal.vue'
|
|
12
12
|
|
|
13
13
|
const props = withDefaults(
|
|
14
14
|
defineProps<{
|
|
15
|
-
imageDomain?: string
|
|
16
|
-
onCompleted?: (data: any) => void
|
|
17
|
-
hidePublic?: boolean
|
|
18
|
-
hideBirthdate?: boolean
|
|
19
|
-
hideGender?: boolean
|
|
15
|
+
imageDomain?: string
|
|
16
|
+
onCompleted?: (data: any) => void
|
|
17
|
+
hidePublic?: boolean
|
|
18
|
+
hideBirthdate?: boolean
|
|
19
|
+
hideGender?: boolean
|
|
20
20
|
}>(),
|
|
21
21
|
{
|
|
22
|
-
imageDomain:
|
|
22
|
+
imageDomain: 'https://s.nocachenocry.com',
|
|
23
23
|
onCompleted: () => {},
|
|
24
24
|
hidePublic: false,
|
|
25
25
|
hideBirthdate: false,
|
|
26
26
|
hideGender: false,
|
|
27
27
|
},
|
|
28
|
-
)
|
|
29
|
-
const rest = useRest()
|
|
30
|
-
const userStore = useUserStore()
|
|
31
|
-
const userData = computed(() => userStore.user)
|
|
32
|
-
const eventBus = useEventBus()
|
|
28
|
+
)
|
|
29
|
+
const rest = useRest()
|
|
30
|
+
const userStore = useUserStore()
|
|
31
|
+
const userData = computed(() => userStore.user)
|
|
32
|
+
const eventBus = useEventBus()
|
|
33
33
|
// Default date = 18y from now
|
|
34
|
-
const currentDate = new Date()
|
|
34
|
+
const currentDate = new Date()
|
|
35
35
|
const defaultDate = new Date(
|
|
36
36
|
currentDate.setFullYear(currentDate.getFullYear() - 18),
|
|
37
37
|
)
|
|
38
38
|
.toISOString()
|
|
39
|
-
.split(
|
|
39
|
+
.split('T')[0]
|
|
40
40
|
|
|
41
41
|
const state = reactive({
|
|
42
42
|
userData: {
|
|
43
|
-
Username: userData.value?.UserProfile?.Username ||
|
|
44
|
-
Gender: userData.value?.UserProfile?.Gender ||
|
|
45
|
-
Bio: userData.value?.UserProfile?.Bio ||
|
|
43
|
+
Username: userData.value?.UserProfile?.Username || '',
|
|
44
|
+
Gender: userData.value?.UserProfile?.Gender || '',
|
|
45
|
+
Bio: userData.value?.UserProfile?.Bio || '',
|
|
46
46
|
Birthdate: userData.value?.UserProfile?.Birthdate || defaultDate,
|
|
47
47
|
PublicGender: userData.value?.UserProfile?.PublicGender || false,
|
|
48
48
|
PublicBio: userData.value?.UserProfile?.PublicBio || false,
|
|
49
49
|
PublicBirthdate: userData.value?.UserProfile?.PublicBirthdate || false,
|
|
50
50
|
},
|
|
51
|
-
})
|
|
51
|
+
})
|
|
52
52
|
watchEffect(() => {
|
|
53
53
|
state.userData = {
|
|
54
|
-
Username: userData.value?.UserProfile?.Username ||
|
|
55
|
-
Gender: userData.value?.UserProfile?.Gender ||
|
|
56
|
-
Bio: userData.value?.UserProfile?.Bio ||
|
|
54
|
+
Username: userData.value?.UserProfile?.Username || '',
|
|
55
|
+
Gender: userData.value?.UserProfile?.Gender || '',
|
|
56
|
+
Bio: userData.value?.UserProfile?.Bio || '',
|
|
57
57
|
Birthdate: userData.value?.UserProfile?.Birthdate
|
|
58
58
|
? new Date(userData.value?.UserProfile?.Birthdate.unixms)
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
.toISOString()
|
|
60
|
+
.split('T')[0]
|
|
61
61
|
: defaultDate,
|
|
62
62
|
PublicGender: userData.value?.UserProfile?.PublicGender || false,
|
|
63
63
|
PublicBio: userData.value?.UserProfile?.PublicBio || false,
|
|
64
64
|
PublicBirthdate: userData.value?.UserProfile?.PublicBirthdate || false,
|
|
65
|
-
}
|
|
66
|
-
})
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
67
|
const rules = {
|
|
68
68
|
userData: {
|
|
69
69
|
Username: {
|
|
70
|
-
required
|
|
70
|
+
required,
|
|
71
71
|
},
|
|
72
72
|
Gender: {},
|
|
73
73
|
Bio: {
|
|
@@ -78,85 +78,84 @@ const rules = {
|
|
|
78
78
|
PublicBio: {},
|
|
79
79
|
PublicBirthdate: {},
|
|
80
80
|
},
|
|
81
|
-
}
|
|
82
|
-
const v$ = useVuelidate(rules, state)
|
|
81
|
+
}
|
|
82
|
+
const v$ = useVuelidate(rules, state)
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
eventBus.emit(
|
|
84
|
+
async function patchUser() {
|
|
85
|
+
eventBus.emit('main-loading', true)
|
|
86
86
|
if (await v$.value.userData.$validate()) {
|
|
87
|
-
const data = { ...state.userData }
|
|
88
|
-
const birtdate = new Date(`${data.Birthdate}T00:00:00Z`)
|
|
87
|
+
const data = { ...state.userData }
|
|
88
|
+
const birtdate = new Date(`${data.Birthdate}T00:00:00Z`)
|
|
89
89
|
try {
|
|
90
|
-
const birtdateAtUnixms = birtdate.getTime()
|
|
91
|
-
|
|
92
|
-
data.Birthdate = new Date(birtdateAtUnixms).toISOString().split("T")[0];
|
|
93
|
-
} catch (e) {
|
|
94
|
-
// @ts-ignore
|
|
95
|
-
data.Birthdate = data.Birthdate.toISOString().split("T")[0];
|
|
90
|
+
const birtdateAtUnixms = birtdate.getTime()
|
|
91
|
+
data.Birthdate = new Date(birtdateAtUnixms).toISOString().split('T')[0]
|
|
96
92
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
catch {
|
|
94
|
+
// @ts-expect-error: Birthdate is a string
|
|
95
|
+
data.Birthdate = data.Birthdate.toISOString().split('T')[0]
|
|
96
|
+
}
|
|
97
|
+
const response = await rest('User/_Profile', 'PATCH', data)
|
|
98
|
+
if (response && response.result === 'success') {
|
|
100
99
|
if (props.onCompleted) {
|
|
101
|
-
props.onCompleted(response)
|
|
100
|
+
props.onCompleted(response)
|
|
102
101
|
}
|
|
103
|
-
eventBus.emit(
|
|
102
|
+
eventBus.emit('user:refresh', true)
|
|
104
103
|
}
|
|
105
104
|
}
|
|
106
|
-
eventBus.emit(
|
|
107
|
-
}
|
|
108
|
-
const uploadInput = ref<HTMLInputElement | null>(null)
|
|
109
|
-
const pic = ref<string>(
|
|
105
|
+
eventBus.emit('main-loading', false)
|
|
106
|
+
}
|
|
107
|
+
const uploadInput = ref<HTMLInputElement | null>(null)
|
|
108
|
+
const pic = ref<string>('')
|
|
110
109
|
const cropResult = reactive({
|
|
111
|
-
dataURL:
|
|
112
|
-
blobURL:
|
|
113
|
-
})
|
|
114
|
-
const uploader = ref(new Uploader())
|
|
110
|
+
dataURL: '',
|
|
111
|
+
blobURL: '',
|
|
112
|
+
})
|
|
113
|
+
const uploader = ref(new Uploader())
|
|
115
114
|
|
|
116
115
|
async function getCropResult() {
|
|
117
|
-
if (!cropper) return
|
|
118
|
-
const base64 = cropper.getDataURL({})
|
|
119
|
-
const blob: Blob | null = await cropper.getBlob()
|
|
120
|
-
if (!blob) return
|
|
121
|
-
eventBus.emit(
|
|
116
|
+
if (!cropper) return
|
|
117
|
+
const base64 = cropper.getDataURL({})
|
|
118
|
+
const blob: Blob | null = await cropper.getBlob()
|
|
119
|
+
if (!blob) return
|
|
120
|
+
eventBus.emit('main-loading', true)
|
|
122
121
|
|
|
123
122
|
const file = await cropper.getFile({
|
|
124
|
-
fileName:
|
|
125
|
-
})
|
|
123
|
+
fileName: `avatar-${userData.value?.UUID}`,
|
|
124
|
+
})
|
|
126
125
|
|
|
127
|
-
cropResult.dataURL = base64
|
|
128
|
-
cropResult.blobURL = URL.createObjectURL(blob)
|
|
126
|
+
cropResult.dataURL = base64
|
|
127
|
+
cropResult.blobURL = URL.createObjectURL(blob)
|
|
129
128
|
if (file) {
|
|
130
|
-
uploader.value.addFile(file)
|
|
129
|
+
uploader.value.addFile(file)
|
|
131
130
|
}
|
|
132
131
|
const fileUploadCallback = (response: any) => {
|
|
133
132
|
if (props.onCompleted) {
|
|
134
|
-
props.onCompleted(response)
|
|
133
|
+
props.onCompleted(response)
|
|
135
134
|
}
|
|
136
|
-
eventBus.emit(
|
|
137
|
-
eventBus.emit(
|
|
138
|
-
eventBus.emit(
|
|
139
|
-
}
|
|
140
|
-
uploader.value.startUpload(`/_special/rest/User/_Avatar`, fileUploadCallback)
|
|
135
|
+
eventBus.emit('avCropModal', false)
|
|
136
|
+
eventBus.emit('main-loading', false)
|
|
137
|
+
eventBus.emit('user:refresh', true)
|
|
138
|
+
}
|
|
139
|
+
uploader.value.startUpload(`/_special/rest/User/_Avatar`, fileUploadCallback)
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
function selectFile(e: Event) {
|
|
144
|
-
pic.value =
|
|
145
|
-
cropResult.dataURL =
|
|
146
|
-
cropResult.blobURL =
|
|
143
|
+
pic.value = ''
|
|
144
|
+
cropResult.dataURL = ''
|
|
145
|
+
cropResult.blobURL = ''
|
|
147
146
|
|
|
148
|
-
const { files } = e.target as HTMLInputElement
|
|
149
|
-
if (!files || !files.length) return
|
|
147
|
+
const { files } = e.target as HTMLInputElement
|
|
148
|
+
if (!files || !files.length) return
|
|
150
149
|
|
|
151
|
-
const file = files[0]
|
|
152
|
-
const reader = new FileReader()
|
|
153
|
-
reader.readAsDataURL(file)
|
|
150
|
+
const file = files[0]
|
|
151
|
+
const reader = new FileReader()
|
|
152
|
+
reader.readAsDataURL(file)
|
|
154
153
|
reader.onload = () => {
|
|
155
|
-
pic.value = String(reader.result)
|
|
156
|
-
eventBus.emit(
|
|
157
|
-
if (!uploadInput.value) return
|
|
158
|
-
uploadInput.value.value =
|
|
159
|
-
}
|
|
154
|
+
pic.value = String(reader.result)
|
|
155
|
+
eventBus.emit('avCropModal', true)
|
|
156
|
+
if (!uploadInput.value) return
|
|
157
|
+
uploadInput.value.value = ''
|
|
158
|
+
}
|
|
160
159
|
}
|
|
161
160
|
</script>
|
|
162
161
|
|
|
@@ -174,32 +173,31 @@ function selectFile(e: Event) {
|
|
|
174
173
|
/>
|
|
175
174
|
<div class="flex gap-2 items-center mb-4">
|
|
176
175
|
<img
|
|
176
|
+
v-if="userData?.UserProfile?.AvatarUUID"
|
|
177
177
|
:src="`${imageDomain}/${userData?.UserProfile?.AvatarUUID}?vars=format=png:resize=100x100`"
|
|
178
178
|
class="w-16 h-16 rounded-full flex-0 shrink-0 grow-0"
|
|
179
|
-
|
|
180
|
-
/>
|
|
179
|
+
>
|
|
181
180
|
<div class="flex-1">
|
|
182
181
|
<label
|
|
183
182
|
class="block text-sm font-medium mb-2 text-neutral-900 dark:text-white"
|
|
184
183
|
for="file_input"
|
|
185
|
-
|
|
186
|
-
>
|
|
184
|
+
>{{ $t("fws_upload_av_label") }}</label>
|
|
187
185
|
<input
|
|
188
|
-
class="block text-sm w-full text-neutral-900 border border-neutral-300 rounded-lg cursor-pointer bg-neutral-50 dark:text-neutral-400 focus:outline-none dark:bg-neutral-700 dark:border-neutral-600 dark:placeholder-neutral-400"
|
|
189
186
|
ref="uploadInput"
|
|
187
|
+
class="block text-sm w-full text-neutral-900 border border-neutral-300 rounded-lg cursor-pointer bg-neutral-50 dark:text-neutral-400 focus:outline-none dark:bg-neutral-700 dark:border-neutral-600 dark:placeholder-neutral-400"
|
|
190
188
|
type="file"
|
|
191
189
|
accept="image/jpg, image/jpeg, image/png, image/gif"
|
|
192
190
|
@change="selectFile"
|
|
193
|
-
|
|
191
|
+
>
|
|
194
192
|
</div>
|
|
195
193
|
</div>
|
|
196
194
|
<DefaultModal id="avCrop" :title="$t('fws_crop_av_title')">
|
|
197
|
-
<button
|
|
195
|
+
<button class="btn defaults primary" @click="getCropResult">
|
|
198
196
|
{{ $t("fws_crop_av_cta") }}
|
|
199
197
|
</button>
|
|
200
198
|
<div class="max-h-[80vh]">
|
|
201
199
|
<VuePictureCropper
|
|
202
|
-
:
|
|
200
|
+
:box-style="{
|
|
203
201
|
width: 'auto',
|
|
204
202
|
height: 'auto',
|
|
205
203
|
backgroundColor: '#f8f8f8',
|
|
@@ -228,13 +226,12 @@ function selectFile(e: Event) {
|
|
|
228
226
|
:label="$t('fws_gender_label')"
|
|
229
227
|
:error-vuelidate="v$.userData.Gender.$errors"
|
|
230
228
|
/>
|
|
231
|
-
<!-- @vue-skip -->
|
|
232
229
|
<DefaultInput
|
|
233
230
|
id="birthdateFWS"
|
|
234
231
|
v-model="state.userData.Birthdate"
|
|
235
232
|
class="mb-4"
|
|
236
233
|
type="datepicker"
|
|
237
|
-
:
|
|
234
|
+
:disable-dates-under18="true"
|
|
238
235
|
:label="$t('fws_birthdate_label')"
|
|
239
236
|
:error-vuelidate="v$.userData.Birthdate.$errors"
|
|
240
237
|
/>
|
|
@@ -248,12 +245,12 @@ function selectFile(e: Event) {
|
|
|
248
245
|
/>
|
|
249
246
|
<template v-if="!hidePublic">
|
|
250
247
|
<DefaultInput
|
|
248
|
+
v-if="!hideGender"
|
|
251
249
|
id="publicGenderFWS"
|
|
252
250
|
v-model:checkbox-value="state.userData.PublicGender"
|
|
253
251
|
type="toggle"
|
|
254
252
|
:label="$t('fws_public_gender')"
|
|
255
253
|
:error-vuelidate="v$.userData.PublicGender.$errors"
|
|
256
|
-
v-if="!hideGender"
|
|
257
254
|
/>
|
|
258
255
|
<DefaultInput
|
|
259
256
|
id="publicBioFWS"
|
|
@@ -263,12 +260,12 @@ function selectFile(e: Event) {
|
|
|
263
260
|
:error-vuelidate="v$.userData.PublicBio.$errors"
|
|
264
261
|
/>
|
|
265
262
|
<DefaultInput
|
|
263
|
+
v-if="!hideBirthdate"
|
|
266
264
|
id="publicBirthdateFWS"
|
|
267
265
|
v-model:checkbox-value="state.userData.PublicBirthdate"
|
|
268
266
|
type="toggle"
|
|
269
267
|
:label="$t('fws_public_birthdate')"
|
|
270
268
|
:error-vuelidate="v$.userData.PublicBirthdate.$errors"
|
|
271
|
-
v-if="!hideBirthdate"
|
|
272
269
|
/>
|
|
273
270
|
</template>
|
|
274
271
|
<div class="flex">
|