@fy-/fws-vue 0.3.78 → 0.4.0
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/UserData.vue +53 -19
- package/components/fws/UserProfile.vue +246 -0
- package/components/fws/UserProfileStrict.vue +105 -0
- package/composables/seo.ts +3 -2
- package/index.ts +4 -0
- package/package.json +1 -1
- package/stores/user.ts +9 -0
|
@@ -3,18 +3,22 @@ import useVuelidate from "@vuelidate/core";
|
|
|
3
3
|
import DefaultInput from "../ui/DefaultInput.vue";
|
|
4
4
|
import { useUserStore } from "../../stores/user";
|
|
5
5
|
import { useRest } from "../../composables/rest";
|
|
6
|
+
import { useEventBus } from "../../composables/event-bus";
|
|
6
7
|
import { computed, reactive, watchEffect } from "vue";
|
|
7
|
-
import { required } from "@vuelidate/validators";
|
|
8
8
|
const rest = useRest();
|
|
9
9
|
const userStore = useUserStore();
|
|
10
10
|
const userData = computed(() => userStore.user);
|
|
11
|
+
const eventBus = useEventBus();
|
|
11
12
|
const state = reactive({
|
|
12
13
|
userData: {
|
|
13
14
|
Firstname: userData.value?.Firstname || "",
|
|
14
15
|
Lastname: userData.value?.Lastname || "",
|
|
15
16
|
Phone: userData.value?.Phone || "",
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
AcceptedTerms: userData.value?.AcceptedTerms || false,
|
|
18
|
+
EnabledNotifications: userData.value?.EnabledNotifications || false,
|
|
19
|
+
EnabledEmails: userData.value?.EnabledEmails || false,
|
|
20
|
+
EnabledTrainingFromMyData:
|
|
21
|
+
userData.value?.EnabledTrainingFromMyData || false,
|
|
18
22
|
},
|
|
19
23
|
});
|
|
20
24
|
watchEffect(() => {
|
|
@@ -22,45 +26,41 @@ watchEffect(() => {
|
|
|
22
26
|
Firstname: userData.value?.Firstname || "",
|
|
23
27
|
Lastname: userData.value?.Lastname || "",
|
|
24
28
|
Phone: userData.value?.Phone || "",
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
AcceptedTerms: userData.value?.AcceptedTerms || false,
|
|
30
|
+
EnabledNotifications: userData.value?.EnabledNotifications || false,
|
|
31
|
+
EnabledEmails: userData.value?.EnabledEmails || false,
|
|
32
|
+
EnabledTrainingFromMyData:
|
|
33
|
+
userData.value?.EnabledTrainingFromMyData || false,
|
|
27
34
|
};
|
|
28
35
|
});
|
|
29
36
|
const rules = {
|
|
30
37
|
userData: {
|
|
31
|
-
Username: {
|
|
32
|
-
required: required,
|
|
33
|
-
},
|
|
34
38
|
Firstname: {},
|
|
35
39
|
Lastname: {},
|
|
36
40
|
Phone: {},
|
|
37
41
|
Bio: {},
|
|
42
|
+
AcceptedTerms: {},
|
|
43
|
+
EnabledNotifications: {},
|
|
44
|
+
EnabledEmails: {},
|
|
45
|
+
EnabledTrainingFromMyData: {},
|
|
38
46
|
},
|
|
39
47
|
};
|
|
40
48
|
const v$ = useVuelidate(rules, state);
|
|
41
49
|
|
|
42
50
|
const patchUser = async () => {
|
|
51
|
+
eventBus.emit("main-loading", true);
|
|
43
52
|
if (await v$.value.userData.$validate()) {
|
|
44
53
|
const response = await rest("User", "PATCH", state.userData);
|
|
45
54
|
if (response && response.result == "success") {
|
|
46
|
-
|
|
55
|
+
eventBus.emit("user:refresh", true);
|
|
47
56
|
}
|
|
48
57
|
}
|
|
58
|
+
eventBus.emit("main-loading", false);
|
|
49
59
|
};
|
|
50
60
|
</script>
|
|
51
61
|
|
|
52
62
|
<template>
|
|
53
63
|
<form @submit.prevent="patchUser">
|
|
54
|
-
<DefaultInput
|
|
55
|
-
id="usernameFWS"
|
|
56
|
-
v-model="state.userData.Username"
|
|
57
|
-
class="mb-4"
|
|
58
|
-
type="text"
|
|
59
|
-
:label="$t('fws_username_label')"
|
|
60
|
-
:help="$t('fws_username_help')"
|
|
61
|
-
:error-vuelidate="v$.userData.Username.$errors"
|
|
62
|
-
:req="true"
|
|
63
|
-
/>
|
|
64
64
|
<DefaultInput
|
|
65
65
|
id="firstnameFWS"
|
|
66
66
|
v-model="state.userData.Firstname"
|
|
@@ -88,6 +88,40 @@ const patchUser = async () => {
|
|
|
88
88
|
:help="$t('fws_phone_help')"
|
|
89
89
|
:error-vuelidate="v$.userData.Phone.$errors"
|
|
90
90
|
/>
|
|
91
|
+
<DefaultInput
|
|
92
|
+
id="acceptedTermsFWS"
|
|
93
|
+
v-if="!userData?.AcceptedTerms"
|
|
94
|
+
v-model:checkbox-value="state.userData.AcceptedTerms"
|
|
95
|
+
type="toggle"
|
|
96
|
+
:label="$t('fws_accepted_terms_label')"
|
|
97
|
+
:help="$t('fws_accepted_terms_help')"
|
|
98
|
+
:error-vuelidate="v$.userData.AcceptedTerms.$errors"
|
|
99
|
+
/>
|
|
100
|
+
<DefaultInput
|
|
101
|
+
id="enabledNotificationsFWS"
|
|
102
|
+
v-model:checkbox-value="state.userData.EnabledNotifications"
|
|
103
|
+
type="toggle"
|
|
104
|
+
:label="$t('fws_enabled_notifications_label')"
|
|
105
|
+
:help="$t('fws_enabled_notifications_help')"
|
|
106
|
+
:error-vuelidate="v$.userData.EnabledNotifications.$errors"
|
|
107
|
+
/>
|
|
108
|
+
<DefaultInput
|
|
109
|
+
id="enabledEmailsFWS"
|
|
110
|
+
v-model:checkbox-value="state.userData.EnabledEmails"
|
|
111
|
+
type="toggle"
|
|
112
|
+
:label="$t('fws_enabled_emails_label')"
|
|
113
|
+
:help="$t('fws_enabled_emails_help')"
|
|
114
|
+
:error-vuelidate="v$.userData.EnabledEmails.$errors"
|
|
115
|
+
/>
|
|
116
|
+
<DefaultInput
|
|
117
|
+
id="enabledTrainingFromMyDataFWS"
|
|
118
|
+
v-model:checkbox-value="state.userData.EnabledTrainingFromMyData"
|
|
119
|
+
type="toggle"
|
|
120
|
+
:label="$t('fws_enabled_training_from_my_data_label')"
|
|
121
|
+
:help="$t('fws_enabled_training_from_my_data_help')"
|
|
122
|
+
:error-vuelidate="v$.userData.EnabledTrainingFromMyData.$errors"
|
|
123
|
+
/>
|
|
124
|
+
|
|
91
125
|
<div class="flex">
|
|
92
126
|
<button type="submit" class="btn defaults primary">
|
|
93
127
|
{{ $t("fws_save_user_cta") }}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import useVuelidate from "@vuelidate/core";
|
|
3
|
+
import DefaultInput from "../ui/DefaultInput.vue";
|
|
4
|
+
import DefaultModal from "../ui/DefaultModal.vue";
|
|
5
|
+
import { useUserStore } from "../../stores/user";
|
|
6
|
+
import { useRest } from "../../composables/rest";
|
|
7
|
+
import { useEventBus } from "../../composables/event-bus";
|
|
8
|
+
import { computed, reactive, watchEffect, ref } from "vue";
|
|
9
|
+
import { required } from "@vuelidate/validators";
|
|
10
|
+
import VuePictureCropper, { cropper } from "vue-picture-cropper";
|
|
11
|
+
import { Uploader } from "@fy-/fws-js";
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(
|
|
14
|
+
defineProps<{
|
|
15
|
+
imageDomain?: string;
|
|
16
|
+
}>(),
|
|
17
|
+
{
|
|
18
|
+
imageDomain: "https://s.nocachenocry.com",
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
const rest = useRest();
|
|
22
|
+
const userStore = useUserStore();
|
|
23
|
+
const userData = computed(() => userStore.user);
|
|
24
|
+
const eventBus = useEventBus();
|
|
25
|
+
const state = reactive({
|
|
26
|
+
userData: {
|
|
27
|
+
Username: userData.value?.UserProfile?.Username || "",
|
|
28
|
+
Gender: userData.value?.UserProfile?.Gender || "",
|
|
29
|
+
Bio: userData.value?.UserProfile?.Bio || "",
|
|
30
|
+
Birthdate: userData.value?.UserProfile?.Birthdate || "",
|
|
31
|
+
PublicGender: userData.value?.UserProfile?.PublicGender || false,
|
|
32
|
+
PublicBio: userData.value?.UserProfile?.PublicBio || false,
|
|
33
|
+
PublicBirthdate: userData.value?.UserProfile?.PublicBirthdate || false,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
watchEffect(() => {
|
|
37
|
+
state.userData = {
|
|
38
|
+
Username: userData.value?.UserProfile?.Username || "",
|
|
39
|
+
Gender: userData.value?.UserProfile?.Gender || "",
|
|
40
|
+
Bio: userData.value?.UserProfile?.Bio || "",
|
|
41
|
+
Birthdate: userData.value?.UserProfile?.Birthdate
|
|
42
|
+
? new Date(userData.value?.UserProfile?.Birthdate.unixms)
|
|
43
|
+
.toISOString()
|
|
44
|
+
.split("T")[0]
|
|
45
|
+
: "",
|
|
46
|
+
PublicGender: userData.value?.UserProfile?.PublicGender || false,
|
|
47
|
+
PublicBio: userData.value?.UserProfile?.PublicBio || false,
|
|
48
|
+
PublicBirthdate: userData.value?.UserProfile?.PublicBirthdate || false,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
const rules = {
|
|
52
|
+
userData: {
|
|
53
|
+
Username: {
|
|
54
|
+
required: required,
|
|
55
|
+
},
|
|
56
|
+
Gender: {},
|
|
57
|
+
Bio: {},
|
|
58
|
+
Birthdate: {},
|
|
59
|
+
PublicGender: {},
|
|
60
|
+
PublicBio: {},
|
|
61
|
+
PublicBirthdate: {},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const v$ = useVuelidate(rules, state);
|
|
65
|
+
|
|
66
|
+
const patchUser = async () => {
|
|
67
|
+
eventBus.emit("main-loading", true);
|
|
68
|
+
if (await v$.value.userData.$validate()) {
|
|
69
|
+
const data = { ...state.userData };
|
|
70
|
+
const birtdate = new Date(`${data.Birthdate}T00:00:00Z`);
|
|
71
|
+
const birtdateAtUnixms = birtdate.getTime();
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
data.Birthdate = birtdateAtUnixms;
|
|
74
|
+
const response = await rest("User/_Profile", "PATCH", data);
|
|
75
|
+
if (response && response.result == "success") {
|
|
76
|
+
eventBus.emit("user:refresh", true);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
eventBus.emit("main-loading", false);
|
|
80
|
+
};
|
|
81
|
+
const uploadInput = ref<HTMLInputElement | null>(null);
|
|
82
|
+
const pic = ref<string>("");
|
|
83
|
+
const cropResult = reactive({
|
|
84
|
+
dataURL: "",
|
|
85
|
+
blobURL: "",
|
|
86
|
+
});
|
|
87
|
+
const uploader = ref(new Uploader());
|
|
88
|
+
|
|
89
|
+
async function getCropResult() {
|
|
90
|
+
if (!cropper) return;
|
|
91
|
+
const base64 = cropper.getDataURL({});
|
|
92
|
+
const blob: Blob | null = await cropper.getBlob();
|
|
93
|
+
if (!blob) return;
|
|
94
|
+
eventBus.emit("main-loading", true);
|
|
95
|
+
|
|
96
|
+
const file = await cropper.getFile({
|
|
97
|
+
fileName: "avatar-" + userData.value?.UUID,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
cropResult.dataURL = base64;
|
|
101
|
+
cropResult.blobURL = URL.createObjectURL(blob);
|
|
102
|
+
if (file) {
|
|
103
|
+
uploader.value.addFile(file);
|
|
104
|
+
}
|
|
105
|
+
const fileUploadCallback = (response: any) => {
|
|
106
|
+
eventBus.emit("avCropModal", false);
|
|
107
|
+
eventBus.emit("main-loading", false);
|
|
108
|
+
eventBus.emit("user:refresh", true);
|
|
109
|
+
};
|
|
110
|
+
uploader.value.startUpload(`/_special/rest/User/_Avatar`, fileUploadCallback);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function selectFile(e: Event) {
|
|
114
|
+
pic.value = "";
|
|
115
|
+
cropResult.dataURL = "";
|
|
116
|
+
cropResult.blobURL = "";
|
|
117
|
+
|
|
118
|
+
const { files } = e.target as HTMLInputElement;
|
|
119
|
+
if (!files || !files.length) return;
|
|
120
|
+
|
|
121
|
+
const file = files[0];
|
|
122
|
+
const reader = new FileReader();
|
|
123
|
+
reader.readAsDataURL(file);
|
|
124
|
+
reader.onload = () => {
|
|
125
|
+
pic.value = String(reader.result);
|
|
126
|
+
eventBus.emit("avCropModal", true);
|
|
127
|
+
if (!uploadInput.value) return;
|
|
128
|
+
uploadInput.value.value = "";
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<template>
|
|
134
|
+
<form @submit.prevent="patchUser">
|
|
135
|
+
<DefaultInput
|
|
136
|
+
id="usernameFWS"
|
|
137
|
+
v-model="state.userData.Username"
|
|
138
|
+
class="mb-4"
|
|
139
|
+
type="text"
|
|
140
|
+
:label="$t('fws_username_label')"
|
|
141
|
+
:help="$t('fws_username_help')"
|
|
142
|
+
:error-vuelidate="v$.userData.Username.$errors"
|
|
143
|
+
:disabled="userData?.UserProfile?.HasUsernameAndSlug ? true : false"
|
|
144
|
+
/>
|
|
145
|
+
<div class="flex gap-2 items-center mb-4">
|
|
146
|
+
<img
|
|
147
|
+
:src="`${imageDomain}/${userData?.UserProfile?.AvatarUUID}?vars=format=png:resize=100x100`"
|
|
148
|
+
class="w-16 h-16 rounded-full flex-0 shrink-0 grow-0"
|
|
149
|
+
v-if="userData?.UserProfile?.AvatarUUID"
|
|
150
|
+
/>
|
|
151
|
+
<div class="flex-1">
|
|
152
|
+
<label
|
|
153
|
+
class="block text-sm font-medium mb-2 text-neutral-900 dark:text-white"
|
|
154
|
+
for="file_input"
|
|
155
|
+
>{{ $t("fws_upload_av_label") }}</label
|
|
156
|
+
>
|
|
157
|
+
<input
|
|
158
|
+
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"
|
|
159
|
+
ref="uploadInput"
|
|
160
|
+
type="file"
|
|
161
|
+
accept="image/jpg, image/jpeg, image/png, image/gif"
|
|
162
|
+
@change="selectFile"
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
<DefaultModal id="avCrop" :title="$t('fws_crop_av_title')">
|
|
167
|
+
<button @click="getCropResult" class="btn defaults primary">
|
|
168
|
+
{{ $t("fws_crop_av_cta") }}
|
|
169
|
+
</button>
|
|
170
|
+
<div class="max-h-[80vh]">
|
|
171
|
+
<VuePictureCropper
|
|
172
|
+
:boxStyle="{
|
|
173
|
+
width: 'auto',
|
|
174
|
+
height: 'auto',
|
|
175
|
+
backgroundColor: '#f8f8f8',
|
|
176
|
+
margin: 'auto',
|
|
177
|
+
}"
|
|
178
|
+
:img="pic"
|
|
179
|
+
:options="{
|
|
180
|
+
viewMode: 1,
|
|
181
|
+
dragMode: 'crop',
|
|
182
|
+
aspectRatio: 1 / 1,
|
|
183
|
+
}"
|
|
184
|
+
class="max-h-[70vh] w-full"
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
</DefaultModal>
|
|
188
|
+
<DefaultInput
|
|
189
|
+
id="genderFWS"
|
|
190
|
+
v-model="state.userData.Gender"
|
|
191
|
+
class="mb-4"
|
|
192
|
+
type="select"
|
|
193
|
+
:options="[
|
|
194
|
+
['female', $t('fws_persona_phys_appearance_opt_female')],
|
|
195
|
+
['male', $t('fws_persona_phys_appearance_opt_male')],
|
|
196
|
+
['non-binary', $t('fws_persona_phys_appearance_opt_non_binary')],
|
|
197
|
+
]"
|
|
198
|
+
:label="$t('fws_gender_label')"
|
|
199
|
+
:error-vuelidate="v$.userData.Gender.$errors"
|
|
200
|
+
/>
|
|
201
|
+
<!-- @vue-skip -->
|
|
202
|
+
<DefaultInput
|
|
203
|
+
id="birthdateFWS"
|
|
204
|
+
v-model="state.userData.Birthdate"
|
|
205
|
+
class="mb-4"
|
|
206
|
+
type="date"
|
|
207
|
+
:label="$t('fws_birthdate_label')"
|
|
208
|
+
:error-vuelidate="v$.userData.Birthdate.$errors"
|
|
209
|
+
/>
|
|
210
|
+
<DefaultInput
|
|
211
|
+
id="bioFWS"
|
|
212
|
+
v-model="state.userData.Bio"
|
|
213
|
+
class="mb-4"
|
|
214
|
+
type="text"
|
|
215
|
+
:label="$t('fws_bio_label')"
|
|
216
|
+
:error-vuelidate="v$.userData.Bio.$errors"
|
|
217
|
+
/>
|
|
218
|
+
<DefaultInput
|
|
219
|
+
id="publicGenderFWS"
|
|
220
|
+
v-model:checkbox-value="state.userData.PublicGender"
|
|
221
|
+
type="toggle"
|
|
222
|
+
:label="$t('fws_public_gender')"
|
|
223
|
+
:error-vuelidate="v$.userData.PublicGender.$errors"
|
|
224
|
+
/>
|
|
225
|
+
<DefaultInput
|
|
226
|
+
id="publicBioFWS"
|
|
227
|
+
v-model:checkbox-value="state.userData.PublicBio"
|
|
228
|
+
type="toggle"
|
|
229
|
+
:label="$t('fws_public_bio')"
|
|
230
|
+
:error-vuelidate="v$.userData.PublicBio.$errors"
|
|
231
|
+
/>
|
|
232
|
+
<DefaultInput
|
|
233
|
+
id="publicBirthdateFWS"
|
|
234
|
+
v-model:checkbox-value="state.userData.PublicBirthdate"
|
|
235
|
+
type="toggle"
|
|
236
|
+
:label="$t('fws_public_birthdate')"
|
|
237
|
+
:error-vuelidate="v$.userData.PublicBirthdate.$errors"
|
|
238
|
+
/>
|
|
239
|
+
|
|
240
|
+
<div class="flex">
|
|
241
|
+
<button type="submit" class="btn defaults primary">
|
|
242
|
+
{{ $t("fws_save_user_cta") }}
|
|
243
|
+
</button>
|
|
244
|
+
</div>
|
|
245
|
+
</form>
|
|
246
|
+
</template>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import useVuelidate from "@vuelidate/core";
|
|
3
|
+
import DefaultInput from "../ui/DefaultInput.vue";
|
|
4
|
+
import { useUserStore } from "../../stores/user";
|
|
5
|
+
import { useRest } from "../../composables/rest";
|
|
6
|
+
import { useEventBus } from "../../composables/event-bus";
|
|
7
|
+
import { computed, reactive, watchEffect, ref } from "vue";
|
|
8
|
+
import { required } from "@vuelidate/validators";
|
|
9
|
+
|
|
10
|
+
const rest = useRest();
|
|
11
|
+
const userStore = useUserStore();
|
|
12
|
+
const userData = computed(() => userStore.user);
|
|
13
|
+
const eventBus = useEventBus();
|
|
14
|
+
const state = reactive({
|
|
15
|
+
userData: {
|
|
16
|
+
Username: userData.value?.UserProfile?.Username || "",
|
|
17
|
+
Gender: userData.value?.UserProfile?.Gender || "",
|
|
18
|
+
Birthdate: userData.value?.UserProfile?.Birthdate || "",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
watchEffect(() => {
|
|
22
|
+
state.userData = {
|
|
23
|
+
Username: userData.value?.UserProfile?.Username || "",
|
|
24
|
+
Gender: userData.value?.UserProfile?.Gender || "",
|
|
25
|
+
Birthdate: userData.value?.UserProfile?.Birthdate
|
|
26
|
+
? new Date(userData.value?.UserProfile?.Birthdate.unixms)
|
|
27
|
+
.toISOString()
|
|
28
|
+
.split("T")[0]
|
|
29
|
+
: "",
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
const rules = {
|
|
33
|
+
userData: {
|
|
34
|
+
Username: {
|
|
35
|
+
required: required,
|
|
36
|
+
},
|
|
37
|
+
Gender: {
|
|
38
|
+
required: required,
|
|
39
|
+
},
|
|
40
|
+
Birthdate: {
|
|
41
|
+
required: required,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
const v$ = useVuelidate(rules, state);
|
|
46
|
+
|
|
47
|
+
const patchUser = async () => {
|
|
48
|
+
eventBus.emit("main-loading", true);
|
|
49
|
+
if (await v$.value.userData.$validate()) {
|
|
50
|
+
const data = { ...state.userData };
|
|
51
|
+
const birtdate = new Date(`${data.Birthdate}T00:00:00Z`);
|
|
52
|
+
const birtdateAtUnixms = birtdate.getTime();
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
data.Birthdate = birtdateAtUnixms;
|
|
55
|
+
const response = await rest("User/_Profile", "PATCH", data);
|
|
56
|
+
if (response && response.result == "success") {
|
|
57
|
+
eventBus.emit("user:refresh", true);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
eventBus.emit("main-loading", false);
|
|
61
|
+
};
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<form @submit.prevent="patchUser">
|
|
66
|
+
<DefaultInput
|
|
67
|
+
id="usernameFWS"
|
|
68
|
+
v-model="state.userData.Username"
|
|
69
|
+
class="mb-4"
|
|
70
|
+
type="text"
|
|
71
|
+
:label="$t('fws_username_label')"
|
|
72
|
+
:help="$t('fws_username_help')"
|
|
73
|
+
:error-vuelidate="v$.userData.Username.$errors"
|
|
74
|
+
:disabled="userData?.UserProfile?.HasUsernameAndSlug ? true : false"
|
|
75
|
+
/>
|
|
76
|
+
<DefaultInput
|
|
77
|
+
id="genderFWS"
|
|
78
|
+
v-model="state.userData.Gender"
|
|
79
|
+
class="mb-4"
|
|
80
|
+
type="select"
|
|
81
|
+
:options="[
|
|
82
|
+
['female', $t('fws_persona_phys_appearance_opt_female')],
|
|
83
|
+
['male', $t('fws_persona_phys_appearance_opt_male')],
|
|
84
|
+
['non-binary', $t('fws_persona_phys_appearance_opt_non_binary')],
|
|
85
|
+
]"
|
|
86
|
+
:label="$t('fws_gender_label')"
|
|
87
|
+
:error-vuelidate="v$.userData.Gender.$errors"
|
|
88
|
+
/>
|
|
89
|
+
<!-- @vue-skip -->
|
|
90
|
+
<DefaultInput
|
|
91
|
+
id="birthdateFWS"
|
|
92
|
+
v-model="state.userData.Birthdate"
|
|
93
|
+
class="mb-4"
|
|
94
|
+
type="date"
|
|
95
|
+
:label="$t('fws_birthdate_label')"
|
|
96
|
+
:error-vuelidate="v$.userData.Birthdate.$errors"
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<div class="flex">
|
|
100
|
+
<button type="submit" class="btn defaults primary">
|
|
101
|
+
{{ $t("fws_save_user_cta") }}
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
</form>
|
|
105
|
+
</template>
|
package/composables/seo.ts
CHANGED
|
@@ -30,15 +30,16 @@ export interface LazyHead {
|
|
|
30
30
|
export const useSeo = (seo: Ref<LazyHead>, initial: boolean = false) => {
|
|
31
31
|
const currentUrl = `${getURL().Scheme}://${getURL().Host}${getURL().Path}`;
|
|
32
32
|
const currentLocale = seo.value.locale || getLocale();
|
|
33
|
-
|
|
33
|
+
const actualCurrentURL = computed(() => seo.value.canonical || currentUrl);
|
|
34
34
|
useFyHead({
|
|
35
35
|
title: computed(() => seo.value.title),
|
|
36
|
+
scripts: seo.value.scripts,
|
|
36
37
|
links: computed(() => {
|
|
37
38
|
const links = [];
|
|
38
39
|
|
|
39
40
|
links.push({
|
|
40
41
|
rel: "canonical",
|
|
41
|
-
href:
|
|
42
|
+
href: actualCurrentURL.value,
|
|
42
43
|
key: "canonical",
|
|
43
44
|
});
|
|
44
45
|
|
package/index.ts
CHANGED
|
@@ -51,6 +51,8 @@ import CmsArticleBoxed from "./components/fws/CmsArticleBoxed.vue";
|
|
|
51
51
|
import CmsArticleSingle from "./components/fws/CmsArticleSingle.vue";
|
|
52
52
|
import UserOAuth2 from "./components/fws/UserOAuth2.vue";
|
|
53
53
|
import UserData from "./components/fws/UserData.vue";
|
|
54
|
+
import UserProfile from "./components/fws/UserProfile.vue";
|
|
55
|
+
import UserProfileStrict from "./components/fws/UserProfileStrict.vue";
|
|
54
56
|
// Css
|
|
55
57
|
import "./style.css";
|
|
56
58
|
|
|
@@ -142,4 +144,6 @@ export {
|
|
|
142
144
|
CmsArticleBoxed,
|
|
143
145
|
CmsArticleSingle,
|
|
144
146
|
UserData,
|
|
147
|
+
UserProfile,
|
|
148
|
+
UserProfileStrict,
|
|
145
149
|
};
|
package/package.json
CHANGED
package/stores/user.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { rest } from "@fy-/fws-js";
|
|
|
5
5
|
import { computed } from "vue";
|
|
6
6
|
import { RouteLocation, useRouter } from "vue-router";
|
|
7
7
|
import { useServerRouter } from "./serverRouter";
|
|
8
|
+
import { useEventBus } from "../composables/event-bus";
|
|
8
9
|
|
|
9
10
|
export type UserStore = {
|
|
10
11
|
user: User | null;
|
|
@@ -52,7 +53,11 @@ export async function useUserCheckAsync(path = "/login", redirectLink = false) {
|
|
|
52
53
|
await userStore.refreshUser();
|
|
53
54
|
const isAuth = computed(() => userStore.isAuth);
|
|
54
55
|
const router = useServerRouter();
|
|
56
|
+
const eventBus = useEventBus();
|
|
55
57
|
|
|
58
|
+
eventBus.on("user:refresh", async () => {
|
|
59
|
+
await userStore.refreshUser();
|
|
60
|
+
});
|
|
56
61
|
const checkUser = (route: RouteLocation) => {
|
|
57
62
|
if (route.meta.reqLogin) {
|
|
58
63
|
if (!isAuth.value) {
|
|
@@ -79,7 +84,11 @@ export function useUserCheck(path = "/login", redirectLink = false) {
|
|
|
79
84
|
const userStore = useUserStore();
|
|
80
85
|
const isAuth = computed(() => userStore.isAuth);
|
|
81
86
|
const router = useServerRouter();
|
|
87
|
+
const eventBus = useEventBus();
|
|
82
88
|
|
|
89
|
+
eventBus.on("user:refresh", async () => {
|
|
90
|
+
await userStore.refreshUser();
|
|
91
|
+
});
|
|
83
92
|
const checkUser = (route: RouteLocation) => {
|
|
84
93
|
if (route.meta.reqLogin) {
|
|
85
94
|
if (!isAuth.value) {
|