@frigear-nu/account 0.0.10 → 0.1.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/README.md +7 -4
- package/dist/module.d.mts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +32 -26
- package/dist/runtime/app/components/Account/AccountApiTokenSlideover.d.vue.ts +11 -0
- package/dist/runtime/app/components/Account/AccountApiTokenSlideover.vue +95 -0
- package/dist/runtime/app/components/Account/AccountApiTokenSlideover.vue.d.ts +11 -0
- package/dist/runtime/app/components/Account/AccountHeader.vue +14 -34
- package/dist/runtime/app/components/Account/AccountMenu.d.vue.ts +10 -1
- package/dist/runtime/app/components/Account/AccountMenu.vue +100 -51
- package/dist/runtime/app/components/Account/AccountMenu.vue.d.ts +10 -1
- package/dist/runtime/app/composables/index.d.ts +2 -0
- package/dist/runtime/app/composables/index.js +1 -0
- package/dist/runtime/app/composables/useAccountNav.d.ts +20 -0
- package/dist/runtime/app/composables/useAccountNav.js +18 -0
- package/dist/runtime/app/layouts/account.d.vue.ts +13 -0
- package/dist/runtime/app/layouts/account.vue +54 -0
- package/dist/runtime/app/layouts/account.vue.d.ts +13 -0
- package/dist/runtime/app/pages/api-tokens.vue +193 -261
- package/dist/runtime/app/pages/documents.vue +25 -26
- package/dist/runtime/app/pages/index.vue +73 -17
- package/dist/runtime/app/pages/profile.vue +152 -169
- package/dist/runtime/app/pages/security.vue +156 -151
- package/dist/runtime/app/utils/api-tokens.d.ts +19 -0
- package/dist/runtime/app/utils/api-tokens.js +0 -0
- package/dist/runtime/i18n/lang/da.json +41 -9
- package/dist/runtime/i18n/lang/en.json +41 -9
- package/dist/runtime/menus.d.ts +1 -1
- package/dist/runtime/menus.js +10 -0
- package/package.json +21 -15
- package/dist/runtime/app/pages/layout.d.vue.ts +0 -3
- package/dist/runtime/app/pages/layout.vue +0 -10
- package/dist/runtime/app/pages/layout.vue.d.ts +0 -3
|
@@ -1,24 +1,80 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
defineOptions({ name: "AccountIndexPage" });
|
|
3
|
-
|
|
3
|
+
definePageMeta({ heading: false });
|
|
4
|
+
const t = useModuleT("account");
|
|
5
|
+
const { user } = useUserSession();
|
|
6
|
+
const { localize } = useLocaleLink();
|
|
7
|
+
const { items } = await useAccountNav();
|
|
8
|
+
const index = useRuntimeConfig().public.account?.pages?.index;
|
|
9
|
+
const WidgetArea = resolveComponent("WidgetArea");
|
|
10
|
+
const widgetArea = typeof WidgetArea === "string" ? void 0 : WidgetArea;
|
|
11
|
+
const shortcuts = computed(() => items.value.filter((item) => item.to !== index));
|
|
12
|
+
const profile = computed(() => {
|
|
13
|
+
const value = user.value;
|
|
14
|
+
const name = value?.name || value?.email || t("title", "Account");
|
|
15
|
+
return { name, email: value?.email ?? void 0, avatar: { src: value?.avatar || void 0, alt: name } };
|
|
16
|
+
});
|
|
4
17
|
</script>
|
|
5
18
|
|
|
6
19
|
<template>
|
|
7
|
-
<
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
<div class="flex flex-col gap-4">
|
|
21
|
+
<PageHeading
|
|
22
|
+
:title="t('overview.title', 'Account')"
|
|
23
|
+
:description="t('overview.description', 'Your details and everything that belongs to your account.')"
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
<SectionCard>
|
|
27
|
+
<div class="flex flex-wrap items-center justify-between gap-4">
|
|
28
|
+
<UUser
|
|
29
|
+
:name="profile.name"
|
|
30
|
+
:description="profile.email"
|
|
31
|
+
:avatar="profile.avatar"
|
|
32
|
+
size="lg"
|
|
33
|
+
:ui="{ name: 'truncate', description: 'truncate' }"
|
|
34
|
+
/>
|
|
35
|
+
<AccountHeaderRight />
|
|
36
|
+
</div>
|
|
37
|
+
</SectionCard>
|
|
38
|
+
|
|
39
|
+
<component
|
|
40
|
+
:is="widgetArea"
|
|
41
|
+
v-if="widgetArea"
|
|
42
|
+
name="account.overview"
|
|
43
|
+
:context="{ user }"
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<ListCard
|
|
47
|
+
:title="t('overview.shortcuts', 'Your account')"
|
|
48
|
+
:is-empty="!shortcuts.length"
|
|
49
|
+
:empty="t('overview.empty', 'Nothing here yet.')"
|
|
50
|
+
>
|
|
51
|
+
<li
|
|
52
|
+
v-for="item in shortcuts"
|
|
53
|
+
:key="item.id"
|
|
54
|
+
>
|
|
55
|
+
<NuxtLink
|
|
56
|
+
:to="item.external ? item.to : localize(item.to)"
|
|
57
|
+
:external="item.external"
|
|
58
|
+
class="group flex items-center gap-3"
|
|
59
|
+
>
|
|
60
|
+
<UIcon
|
|
61
|
+
v-if="item.icon"
|
|
62
|
+
:name="item.icon"
|
|
63
|
+
class="size-5 shrink-0 text-muted"
|
|
64
|
+
/>
|
|
65
|
+
<span class="min-w-0 flex-1">
|
|
66
|
+
<span class="block font-medium text-highlighted group-hover:text-primary">{{ item.label }}</span>
|
|
67
|
+
<span
|
|
68
|
+
v-if="item.description"
|
|
69
|
+
class="block truncate text-muted"
|
|
70
|
+
>{{ item.description }}</span>
|
|
71
|
+
</span>
|
|
72
|
+
<UIcon
|
|
73
|
+
name="i-lucide-chevron-right"
|
|
74
|
+
class="size-4 shrink-0 text-dimmed"
|
|
19
75
|
/>
|
|
20
|
-
</
|
|
21
|
-
</
|
|
22
|
-
</
|
|
23
|
-
</
|
|
76
|
+
</NuxtLink>
|
|
77
|
+
</li>
|
|
78
|
+
</ListCard>
|
|
79
|
+
</div>
|
|
24
80
|
</template>
|
|
@@ -5,7 +5,7 @@ const t = useModuleT("account.profile");
|
|
|
5
5
|
const toast = useToast();
|
|
6
6
|
const route = useRoute();
|
|
7
7
|
const { user } = useUserSession();
|
|
8
|
-
const { updateProfileDetails, changeEmailAddress
|
|
8
|
+
const { updateProfileDetails, changeEmailAddress } = useAccount();
|
|
9
9
|
const schema = z.object({
|
|
10
10
|
name: z.string().trim().min(1, t("name.required", "Please enter your name.")),
|
|
11
11
|
avatar: z.union([z.literal(""), z.url(t("avatar.invalid", "Enter a valid URL."))]).optional()
|
|
@@ -25,194 +25,177 @@ watch(saved, (next) => {
|
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
const dirty = computed(() => state.name !== saved.value.name || state.avatar !== saved.value.avatar);
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} catch (e) {
|
|
35
|
-
toast.add({ title: e.statusMessage ?? t("saveFailed", "Could not update your profile."), color: "error" });
|
|
36
|
-
} finally {
|
|
37
|
-
saving.value = false;
|
|
38
|
-
}
|
|
28
|
+
const profileAction = useAction();
|
|
29
|
+
function onSubmitProfile(event) {
|
|
30
|
+
return profileAction.run(
|
|
31
|
+
() => updateProfileDetails({ name: event.data.name, avatar: event.data.avatar || void 0 }),
|
|
32
|
+
{ success: t("saved", "Profile updated"), error: t("saveFailed", "Could not update your profile.") }
|
|
33
|
+
);
|
|
39
34
|
}
|
|
40
35
|
const emailModalOpen = ref(false);
|
|
41
36
|
const newEmail = ref("");
|
|
42
|
-
const emailPending = ref(false);
|
|
43
37
|
const emailVerificationSent = ref(false);
|
|
44
38
|
const emailChanged = computed(() => route.query["email-changed"] === "1");
|
|
45
39
|
const emailChangeFailed = computed(() => route.query.error === "email-change");
|
|
46
40
|
const emailSchema = z.object({ email: z.email(t("email.invalid", "Enter a valid email address.")) });
|
|
41
|
+
const emailAction = useAction();
|
|
47
42
|
async function onSubmitEmail() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
} finally {
|
|
58
|
-
emailPending.value = false;
|
|
59
|
-
}
|
|
43
|
+
const result = await emailAction.run(
|
|
44
|
+
() => changeEmailAddress({ email: newEmail.value }),
|
|
45
|
+
{ error: t("email.change.error", "Could not change your email address.") }
|
|
46
|
+
);
|
|
47
|
+
if (!result) return;
|
|
48
|
+
emailVerificationSent.value = result.verificationRequired === true;
|
|
49
|
+
emailModalOpen.value = false;
|
|
50
|
+
newEmail.value = "";
|
|
51
|
+
toast.add({ title: result.verificationRequired ? t("email.change.sent.title", "Check your inbox") : t("email.change.done", "Your email address was changed."), color: "success" });
|
|
60
52
|
}
|
|
61
53
|
</script>
|
|
62
54
|
|
|
63
55
|
<template>
|
|
64
|
-
<
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
:description="t('email.change.sent.description', 'We sent a link to your new address. It takes effect once you confirm it.')"
|
|
84
|
-
/>
|
|
56
|
+
<div class="flex flex-col gap-4">
|
|
57
|
+
<UAlert
|
|
58
|
+
v-if="emailChanged"
|
|
59
|
+
color="success"
|
|
60
|
+
variant="subtle"
|
|
61
|
+
:title="t('email.change.done', 'Your email address was changed.')"
|
|
62
|
+
/>
|
|
63
|
+
<UAlert
|
|
64
|
+
v-if="emailChangeFailed"
|
|
65
|
+
color="error"
|
|
66
|
+
variant="subtle"
|
|
67
|
+
:title="t('email.change.failed', 'This confirmation link is invalid or has expired.')"
|
|
68
|
+
/>
|
|
69
|
+
<UAlert
|
|
70
|
+
v-if="emailVerificationSent"
|
|
71
|
+
variant="subtle"
|
|
72
|
+
:title="t('email.change.sent.title', 'Check your inbox')"
|
|
73
|
+
:description="t('email.change.sent.description', 'We sent a link to your new address. It takes effect once you confirm it.')"
|
|
74
|
+
/>
|
|
85
75
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
76
|
+
<SectionCard
|
|
77
|
+
:title="t('title', 'Profile')"
|
|
78
|
+
:description="t('description', 'Change your information.')"
|
|
79
|
+
>
|
|
80
|
+
<UForm
|
|
81
|
+
id="account-profile"
|
|
82
|
+
:schema="schema"
|
|
83
|
+
:state="state"
|
|
84
|
+
class="flex flex-col gap-4"
|
|
85
|
+
@submit="onSubmitProfile"
|
|
86
|
+
>
|
|
87
|
+
<UFormField
|
|
88
|
+
name="name"
|
|
89
|
+
:label="t('name.label', 'Name')"
|
|
90
|
+
:description="t('name.description', 'Your full name.')"
|
|
91
|
+
required
|
|
92
|
+
class="flex items-start justify-between gap-4 max-sm:flex-col"
|
|
91
93
|
>
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
>
|
|
104
|
-
<UInput
|
|
105
|
-
v-model="state.name"
|
|
106
|
-
autocomplete="name"
|
|
107
|
-
/>
|
|
108
|
-
</UFormField>
|
|
109
|
-
<USeparator />
|
|
110
|
-
<UFormField
|
|
111
|
-
name="avatar"
|
|
112
|
-
:label="t('avatar.label', 'Avatar')"
|
|
113
|
-
:description="t('avatar.description', 'Link to a square image.')"
|
|
114
|
-
class="flex items-start justify-between gap-4 max-sm:flex-col"
|
|
115
|
-
>
|
|
116
|
-
<div class="flex items-center gap-3">
|
|
117
|
-
<UAvatar
|
|
118
|
-
:src="state.avatar || void 0"
|
|
119
|
-
:alt="state.name"
|
|
120
|
-
size="xl"
|
|
121
|
-
/>
|
|
122
|
-
<UInput
|
|
123
|
-
v-model="state.avatar"
|
|
124
|
-
type="url"
|
|
125
|
-
placeholder="https://"
|
|
126
|
-
:aria-label="t('avatar.url', 'Avatar URL')"
|
|
127
|
-
/>
|
|
128
|
-
</div>
|
|
129
|
-
</UFormField>
|
|
130
|
-
<USeparator />
|
|
131
|
-
<div class="flex justify-end">
|
|
132
|
-
<UButton
|
|
133
|
-
type="submit"
|
|
134
|
-
:loading="saving"
|
|
135
|
-
:disabled="!dirty"
|
|
136
|
-
:label="t('save', 'Save profile')"
|
|
137
|
-
/>
|
|
138
|
-
</div>
|
|
139
|
-
</UPageCard>
|
|
140
|
-
</UForm>
|
|
141
|
-
|
|
142
|
-
<UPageCard
|
|
143
|
-
variant="subtle"
|
|
144
|
-
:title="t('email.label', 'Email')"
|
|
145
|
-
:description="t('email.description', 'Your email address.')"
|
|
94
|
+
<UInput
|
|
95
|
+
v-model="state.name"
|
|
96
|
+
autocomplete="name"
|
|
97
|
+
/>
|
|
98
|
+
</UFormField>
|
|
99
|
+
<USeparator />
|
|
100
|
+
<UFormField
|
|
101
|
+
name="avatar"
|
|
102
|
+
:label="t('avatar.label', 'Avatar')"
|
|
103
|
+
:description="t('avatar.description', 'Link to a square image.')"
|
|
104
|
+
class="flex items-start justify-between gap-4 max-sm:flex-col"
|
|
146
105
|
>
|
|
147
|
-
<div class="flex items-center
|
|
148
|
-
<
|
|
149
|
-
:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
disabled
|
|
153
|
-
class="w-full sm:max-w-xs"
|
|
154
|
-
:aria-label="t('email.label', 'Email')"
|
|
106
|
+
<div class="flex items-center gap-3">
|
|
107
|
+
<UAvatar
|
|
108
|
+
:src="state.avatar || void 0"
|
|
109
|
+
:alt="state.name"
|
|
110
|
+
size="xl"
|
|
155
111
|
/>
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
112
|
+
<UInput
|
|
113
|
+
v-model="state.avatar"
|
|
114
|
+
type="url"
|
|
115
|
+
placeholder="https://"
|
|
116
|
+
:aria-label="t('avatar.url', 'Avatar URL')"
|
|
161
117
|
/>
|
|
162
118
|
</div>
|
|
163
|
-
</
|
|
119
|
+
</UFormField>
|
|
120
|
+
</UForm>
|
|
121
|
+
<template #footer>
|
|
122
|
+
<div class="flex justify-end">
|
|
123
|
+
<UButton
|
|
124
|
+
type="submit"
|
|
125
|
+
form="account-profile"
|
|
126
|
+
:loading="profileAction.busy.value"
|
|
127
|
+
:disabled="!dirty"
|
|
128
|
+
:label="t('save', 'Save profile')"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
</template>
|
|
132
|
+
</SectionCard>
|
|
164
133
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
134
|
+
<SectionCard
|
|
135
|
+
:title="t('email.label', 'Email')"
|
|
136
|
+
:description="t('email.description', 'Your email address.')"
|
|
137
|
+
>
|
|
138
|
+
<template #actions>
|
|
139
|
+
<UButton
|
|
140
|
+
color="neutral"
|
|
141
|
+
variant="outline"
|
|
142
|
+
icon="i-lucide-pencil"
|
|
143
|
+
:label="t('email.change.title', 'Change email')"
|
|
144
|
+
@click="emailModalOpen = true"
|
|
170
145
|
/>
|
|
146
|
+
</template>
|
|
147
|
+
<UInput
|
|
148
|
+
:model-value="user?.email ?? ''"
|
|
149
|
+
type="email"
|
|
150
|
+
readonly
|
|
151
|
+
disabled
|
|
152
|
+
class="w-full sm:max-w-xs"
|
|
153
|
+
:aria-label="t('email.label', 'Email')"
|
|
154
|
+
/>
|
|
155
|
+
</SectionCard>
|
|
171
156
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
157
|
+
<UModal
|
|
158
|
+
v-model:open="emailModalOpen"
|
|
159
|
+
:title="t('email.change.title', 'Change email')"
|
|
160
|
+
>
|
|
161
|
+
<template #body>
|
|
162
|
+
<UForm
|
|
163
|
+
id="account-change-email"
|
|
164
|
+
:schema="emailSchema"
|
|
165
|
+
:state="{ email: newEmail }"
|
|
166
|
+
class="flex flex-col gap-4"
|
|
167
|
+
@submit="onSubmitEmail"
|
|
175
168
|
>
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
form="change-email"
|
|
209
|
-
:loading="emailPending"
|
|
210
|
-
:label="t('email.change.title', 'Change email')"
|
|
211
|
-
/>
|
|
212
|
-
</div>
|
|
213
|
-
</template>
|
|
214
|
-
</UModal>
|
|
215
|
-
</UPageBody>
|
|
216
|
-
</UPage>
|
|
217
|
-
</UContainer>
|
|
169
|
+
<UFormField
|
|
170
|
+
name="email"
|
|
171
|
+
:label="t('email.change.label', 'New e-mail')"
|
|
172
|
+
required
|
|
173
|
+
>
|
|
174
|
+
<UInput
|
|
175
|
+
v-model="newEmail"
|
|
176
|
+
type="email"
|
|
177
|
+
autocomplete="email"
|
|
178
|
+
class="w-full"
|
|
179
|
+
/>
|
|
180
|
+
</UFormField>
|
|
181
|
+
</UForm>
|
|
182
|
+
</template>
|
|
183
|
+
<template #footer>
|
|
184
|
+
<div class="flex w-full justify-end gap-2">
|
|
185
|
+
<UButton
|
|
186
|
+
color="neutral"
|
|
187
|
+
variant="outline"
|
|
188
|
+
:label="t('cancel', 'Cancel')"
|
|
189
|
+
@click="emailModalOpen = false"
|
|
190
|
+
/>
|
|
191
|
+
<UButton
|
|
192
|
+
type="submit"
|
|
193
|
+
form="account-change-email"
|
|
194
|
+
:loading="emailAction.busy.value"
|
|
195
|
+
:label="t('email.change.title', 'Change email')"
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
</template>
|
|
199
|
+
</UModal>
|
|
200
|
+
</div>
|
|
218
201
|
</template>
|