@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
|
@@ -3,14 +3,13 @@ import { z } from "zod";
|
|
|
3
3
|
defineOptions({ name: "AccountSecurityPage" });
|
|
4
4
|
const t = useModuleT("account.security");
|
|
5
5
|
const toast = useToast();
|
|
6
|
+
const showError = useErrorToast();
|
|
7
|
+
const confirm = useConfirmDialog();
|
|
8
|
+
const format = useFormat();
|
|
6
9
|
const { user } = useUserSession();
|
|
7
|
-
const { changePassword
|
|
10
|
+
const { changePassword } = useAccount();
|
|
8
11
|
const webAuthnEnabled = useRuntimeConfig().public.authentication?.webAuthn === true;
|
|
9
12
|
const passkeys = webAuthnEnabled ? usePasskeys() : void 0;
|
|
10
|
-
function errorMessage(e, fallback) {
|
|
11
|
-
const err = e;
|
|
12
|
-
return err?.data?.statusMessage ?? err?.statusMessage ?? fallback;
|
|
13
|
-
}
|
|
14
13
|
const passwordSchema = z.object({
|
|
15
14
|
currentPassword: z.string().min(1, t("changePassword.current.required", "Enter your current password.")),
|
|
16
15
|
newPassword: z.string().min(8, t("changePassword.new.min", "Use at least 8 characters.")),
|
|
@@ -20,29 +19,31 @@ const passwordSchema = z.object({
|
|
|
20
19
|
path: ["confirmNewPassword"]
|
|
21
20
|
});
|
|
22
21
|
const passwordState = reactive({ currentPassword: "", newPassword: "", confirmNewPassword: "" });
|
|
22
|
+
const passwordAction = useAction();
|
|
23
23
|
async function onSubmitPassword(event) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
const changed = await passwordAction.run(
|
|
25
|
+
async () => {
|
|
26
|
+
await changePassword(event.data);
|
|
27
|
+
return true;
|
|
28
|
+
},
|
|
29
|
+
{ success: t("changePassword.success", "Your password was changed."), error: t("changePassword.error", "Could not change your password.") }
|
|
30
|
+
);
|
|
31
|
+
if (!changed) return;
|
|
32
|
+
passwordState.currentPassword = "";
|
|
33
|
+
passwordState.newPassword = "";
|
|
34
|
+
passwordState.confirmNewPassword = "";
|
|
33
35
|
}
|
|
34
36
|
const passkeyList = ref([]);
|
|
35
37
|
const passkeyName = ref("");
|
|
36
38
|
const passkeyPending = ref(false);
|
|
37
39
|
const passkeysLoading = ref(false);
|
|
38
|
-
const removingId = ref(null);
|
|
39
40
|
async function loadPasskeys() {
|
|
40
41
|
if (!passkeys) return;
|
|
41
42
|
passkeysLoading.value = true;
|
|
42
43
|
try {
|
|
43
44
|
passkeyList.value = await passkeys.listPasskeys();
|
|
44
45
|
} catch (e) {
|
|
45
|
-
|
|
46
|
+
showError(e, t("passkeys.loadError", "Could not load your passkeys."));
|
|
46
47
|
} finally {
|
|
47
48
|
passkeysLoading.value = false;
|
|
48
49
|
}
|
|
@@ -52,160 +53,164 @@ async function onRegisterPasskey() {
|
|
|
52
53
|
try {
|
|
53
54
|
await passkeys?.registerPasskey(user.value?.email ?? "", passkeyName.value);
|
|
54
55
|
passkeyName.value = "";
|
|
55
|
-
toast.add({ title: t("passkeys.registered", "Passkey added."), color: "success" });
|
|
56
|
+
toast.add({ title: t("passkeys.registered", "Passkey added."), color: "success", icon: "i-lucide-check" });
|
|
56
57
|
await loadPasskeys();
|
|
57
58
|
} catch (e) {
|
|
58
59
|
if (e?.name !== "NotAllowedError") {
|
|
59
|
-
|
|
60
|
+
showError(e, t("passkeys.registerError", "Could not register the passkey."));
|
|
60
61
|
}
|
|
61
62
|
} finally {
|
|
62
63
|
passkeyPending.value = false;
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
const passkeyLabel = (passkey) => passkey.name || t("passkeys.unnamed", "Passkey");
|
|
67
|
+
async function onRemovePasskey(passkey) {
|
|
68
|
+
const confirmed = await confirm({
|
|
69
|
+
title: t("passkeys.removeConfirm.title", "Remove {name}?", { name: passkeyLabel(passkey) }),
|
|
70
|
+
description: t("passkeys.removeConfirm.description", "You can no longer sign in with this passkey. It stays on your device until you delete it there."),
|
|
71
|
+
confirmText: t("passkeys.remove", "Remove passkey"),
|
|
72
|
+
destructive: true
|
|
73
|
+
});
|
|
74
|
+
if (!confirmed) return;
|
|
67
75
|
try {
|
|
68
|
-
await passkeys?.removePasskey(id);
|
|
69
|
-
toast.add({ title: t("passkeys.removed", "Passkey removed."), color: "success" });
|
|
76
|
+
await passkeys?.removePasskey(passkey.id);
|
|
77
|
+
toast.add({ title: t("passkeys.removed", "Passkey removed."), description: passkeyLabel(passkey), color: "success", icon: "i-lucide-check" });
|
|
70
78
|
await loadPasskeys();
|
|
71
79
|
} catch (e) {
|
|
72
|
-
|
|
73
|
-
} finally {
|
|
74
|
-
removingId.value = null;
|
|
80
|
+
showError(e, t("passkeys.removeError", "Could not remove the passkey."));
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return Number.isNaN(date.getTime()) ? "" : dateFormatter.format(date);
|
|
81
|
-
}
|
|
83
|
+
const passkeyMenu = (passkey) => [[
|
|
84
|
+
{ label: t("passkeys.remove", "Remove passkey"), icon: "i-lucide-trash-2", color: "error", onSelect: () => onRemovePasskey(passkey) }
|
|
85
|
+
]];
|
|
82
86
|
onMounted(loadPasskeys);
|
|
83
87
|
</script>
|
|
84
88
|
|
|
85
89
|
<template>
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
<div class="flex flex-col gap-4">
|
|
91
|
+
<SectionCard
|
|
92
|
+
:title="t('password.title', 'Password')"
|
|
93
|
+
:description="t('password.description', 'Change your password.')"
|
|
94
|
+
>
|
|
95
|
+
<UForm
|
|
96
|
+
id="account-change-password"
|
|
97
|
+
:schema="passwordSchema"
|
|
98
|
+
:state="passwordState"
|
|
99
|
+
class="flex flex-col gap-4"
|
|
100
|
+
@submit="onSubmitPassword"
|
|
101
|
+
>
|
|
102
|
+
<UFormField
|
|
103
|
+
:label="t('changePassword.current.label', 'Current password')"
|
|
104
|
+
name="currentPassword"
|
|
105
|
+
required
|
|
93
106
|
>
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<UInput
|
|
118
|
-
v-model="passwordState.newPassword"
|
|
119
|
-
type="password"
|
|
120
|
-
autocomplete="new-password"
|
|
121
|
-
class="w-full"
|
|
122
|
-
/>
|
|
123
|
-
</UFormField>
|
|
124
|
-
<UFormField
|
|
125
|
-
:label="t('changePassword.confirm.label', 'Confirm new password')"
|
|
126
|
-
name="confirmNewPassword"
|
|
127
|
-
required
|
|
128
|
-
>
|
|
129
|
-
<UInput
|
|
130
|
-
v-model="passwordState.confirmNewPassword"
|
|
131
|
-
type="password"
|
|
132
|
-
autocomplete="new-password"
|
|
133
|
-
class="w-full"
|
|
134
|
-
/>
|
|
135
|
-
</UFormField>
|
|
136
|
-
<UButton
|
|
137
|
-
type="submit"
|
|
138
|
-
:loading="pending"
|
|
139
|
-
:label="t('changePassword.submit', 'Change password')"
|
|
140
|
-
/>
|
|
141
|
-
</UForm>
|
|
142
|
-
</UPageCard>
|
|
143
|
-
|
|
144
|
-
<UPageCard
|
|
145
|
-
v-if="passkeys?.isEnabled.value"
|
|
146
|
-
:title="t('passkeys.title', 'Passkeys')"
|
|
147
|
-
:description="t('passkeys.description', 'Sign in with your fingerprint, face or device PIN instead of a password.')"
|
|
148
|
-
variant="subtle"
|
|
107
|
+
<UInput
|
|
108
|
+
v-model="passwordState.currentPassword"
|
|
109
|
+
type="password"
|
|
110
|
+
autocomplete="current-password"
|
|
111
|
+
class="w-full sm:max-w-sm"
|
|
112
|
+
/>
|
|
113
|
+
</UFormField>
|
|
114
|
+
<UFormField
|
|
115
|
+
:label="t('changePassword.new.label', 'New password')"
|
|
116
|
+
name="newPassword"
|
|
117
|
+
required
|
|
118
|
+
>
|
|
119
|
+
<UInput
|
|
120
|
+
v-model="passwordState.newPassword"
|
|
121
|
+
type="password"
|
|
122
|
+
autocomplete="new-password"
|
|
123
|
+
class="w-full sm:max-w-sm"
|
|
124
|
+
/>
|
|
125
|
+
</UFormField>
|
|
126
|
+
<UFormField
|
|
127
|
+
:label="t('changePassword.confirm.label', 'Confirm new password')"
|
|
128
|
+
name="confirmNewPassword"
|
|
129
|
+
required
|
|
149
130
|
>
|
|
150
|
-
<
|
|
151
|
-
v-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
{{ t("passkeys.created", "Added {date}", { date: formatDate(passkey.createdAt) }) }}
|
|
170
|
-
</p>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
<UButton
|
|
174
|
-
color="error"
|
|
175
|
-
variant="ghost"
|
|
176
|
-
icon="i-lucide-trash-2"
|
|
177
|
-
:loading="removingId === passkey.id"
|
|
178
|
-
:aria-label="t('passkeys.remove', 'Remove passkey')"
|
|
179
|
-
@click="onRemovePasskey(passkey.id)"
|
|
180
|
-
/>
|
|
181
|
-
</li>
|
|
182
|
-
</ul>
|
|
183
|
-
<p
|
|
184
|
-
v-else-if="!passkeysLoading"
|
|
185
|
-
class="text-sm text-muted"
|
|
186
|
-
>
|
|
187
|
-
{{ t("passkeys.empty", "You have not added any passkeys yet.") }}
|
|
188
|
-
</p>
|
|
131
|
+
<UInput
|
|
132
|
+
v-model="passwordState.confirmNewPassword"
|
|
133
|
+
type="password"
|
|
134
|
+
autocomplete="new-password"
|
|
135
|
+
class="w-full sm:max-w-sm"
|
|
136
|
+
/>
|
|
137
|
+
</UFormField>
|
|
138
|
+
</UForm>
|
|
139
|
+
<template #footer>
|
|
140
|
+
<div class="flex justify-end">
|
|
141
|
+
<UButton
|
|
142
|
+
type="submit"
|
|
143
|
+
form="account-change-password"
|
|
144
|
+
:loading="passwordAction.busy.value"
|
|
145
|
+
:label="t('changePassword.submit', 'Change password')"
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
</template>
|
|
149
|
+
</SectionCard>
|
|
189
150
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
151
|
+
<SectionCard
|
|
152
|
+
v-if="passkeys?.isEnabled.value"
|
|
153
|
+
:title="t('passkeys.title', 'Passkeys')"
|
|
154
|
+
:description="t('passkeys.description', 'Sign in with your fingerprint, face or device PIN instead of a password.')"
|
|
155
|
+
:loading="passkeysLoading && !passkeyList.length"
|
|
156
|
+
flush
|
|
157
|
+
>
|
|
158
|
+
<ul
|
|
159
|
+
v-if="passkeyList.length"
|
|
160
|
+
class="divide-y divide-default"
|
|
161
|
+
>
|
|
162
|
+
<li
|
|
163
|
+
v-for="passkey in passkeyList"
|
|
164
|
+
:key="passkey.id"
|
|
165
|
+
class="flex items-center justify-between gap-3 px-4 py-3"
|
|
166
|
+
>
|
|
167
|
+
<div class="flex min-w-0 items-center gap-3">
|
|
168
|
+
<UIcon
|
|
169
|
+
name="i-lucide-key-round"
|
|
170
|
+
class="size-5 shrink-0 text-muted"
|
|
205
171
|
/>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
172
|
+
<div class="min-w-0">
|
|
173
|
+
<p class="truncate text-sm font-medium text-highlighted">
|
|
174
|
+
{{ passkeyLabel(passkey) }}
|
|
175
|
+
</p>
|
|
176
|
+
<p class="text-sm text-muted">
|
|
177
|
+
{{ t("passkeys.created", "Added {date}", { date: format.date(passkey.createdAt) }) }}
|
|
178
|
+
</p>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
<RowMenu
|
|
182
|
+
:items="passkeyMenu(passkey)"
|
|
183
|
+
:subject="passkeyLabel(passkey)"
|
|
184
|
+
/>
|
|
185
|
+
</li>
|
|
186
|
+
</ul>
|
|
187
|
+
<UEmpty
|
|
188
|
+
v-else
|
|
189
|
+
variant="naked"
|
|
190
|
+
icon="i-lucide-key-round"
|
|
191
|
+
:title="t('passkeys.empty', 'You have not added any passkeys yet.')"
|
|
192
|
+
/>
|
|
193
|
+
|
|
194
|
+
<template #footer>
|
|
195
|
+
<form
|
|
196
|
+
class="flex flex-col gap-2 sm:flex-row sm:justify-end"
|
|
197
|
+
@submit.prevent="onRegisterPasskey"
|
|
198
|
+
>
|
|
199
|
+
<UInput
|
|
200
|
+
v-model="passkeyName"
|
|
201
|
+
:placeholder="t('passkeys.name.placeholder', 'Name, e.g. MacBook')"
|
|
202
|
+
:aria-label="t('passkeys.name.label', 'Passkey name')"
|
|
203
|
+
maxlength="100"
|
|
204
|
+
class="w-full sm:max-w-xs"
|
|
205
|
+
/>
|
|
206
|
+
<UButton
|
|
207
|
+
type="submit"
|
|
208
|
+
icon="i-lucide-plus"
|
|
209
|
+
:loading="passkeyPending"
|
|
210
|
+
:label="t('passkeys.register', 'Register a passkey')"
|
|
211
|
+
/>
|
|
212
|
+
</form>
|
|
213
|
+
</template>
|
|
214
|
+
</SectionCard>
|
|
215
|
+
</div>
|
|
211
216
|
</template>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ApiToken {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
prefix: string;
|
|
5
|
+
last4: string;
|
|
6
|
+
scopes: string[];
|
|
7
|
+
expiresAt: string | null;
|
|
8
|
+
lastUsedAt: string | null;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
status: 'ok' | 'revoked' | 'expired';
|
|
11
|
+
}
|
|
12
|
+
export interface ApiTokenScope {
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
/** The response to creating a token: the only time its plaintext `token` exists. */
|
|
17
|
+
export type CreatedApiToken = ApiToken & {
|
|
18
|
+
token: string;
|
|
19
|
+
};
|
|
File without changes
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
"label": "Konto",
|
|
8
8
|
"description": "Din konto."
|
|
9
9
|
},
|
|
10
|
+
"overview": {
|
|
11
|
+
"label": "Overblik",
|
|
12
|
+
"description": "Et overblik over din konto."
|
|
13
|
+
},
|
|
10
14
|
"profile": {
|
|
11
15
|
"label": "Profil",
|
|
12
16
|
"description": "Administrer dine profiloplysninger og e-mailadresse."
|
|
@@ -24,6 +28,22 @@
|
|
|
24
28
|
"description": "Opret nøgler til scripts og værktøjer."
|
|
25
29
|
}
|
|
26
30
|
},
|
|
31
|
+
"nav": {
|
|
32
|
+
"label": "Kontonavigation"
|
|
33
|
+
},
|
|
34
|
+
"accountMenu": "Kontomenu",
|
|
35
|
+
"appearance": {
|
|
36
|
+
"label": "Udseende",
|
|
37
|
+
"light": "Lyst",
|
|
38
|
+
"dark": "Mørkt",
|
|
39
|
+
"system": "System"
|
|
40
|
+
},
|
|
41
|
+
"overview": {
|
|
42
|
+
"title": "Konto",
|
|
43
|
+
"description": "Dine oplysninger og alt, der hører til din konto.",
|
|
44
|
+
"shortcuts": "Din konto",
|
|
45
|
+
"empty": "Her er ikke noget endnu."
|
|
46
|
+
},
|
|
27
47
|
"profile": {
|
|
28
48
|
"title": "Profil",
|
|
29
49
|
"description": "Skift dine brugeroplysninger.",
|
|
@@ -97,6 +117,10 @@
|
|
|
97
117
|
"unnamed": "Passkey",
|
|
98
118
|
"created": "Tilføjet {date}",
|
|
99
119
|
"remove": "Fjern passkey",
|
|
120
|
+
"removeConfirm": {
|
|
121
|
+
"title": "Fjern {name}?",
|
|
122
|
+
"description": "Du kan ikke længere logge ind med denne passkey. Den ligger stadig på din enhed, indtil du sletter den dér."
|
|
123
|
+
},
|
|
100
124
|
"empty": "Du har ikke tilføjet nogen passkeys endnu.",
|
|
101
125
|
"name": {
|
|
102
126
|
"label": "Navn på passkey",
|
|
@@ -109,29 +133,31 @@
|
|
|
109
133
|
"loadError": "Kunne ikke hente dine passkeys."
|
|
110
134
|
}
|
|
111
135
|
},
|
|
112
|
-
"backTo": "Tilbage til konto",
|
|
113
136
|
"signedInAs": "Logget ind som",
|
|
114
137
|
"apiTokens": {
|
|
115
138
|
"title": "API-nøgler",
|
|
116
139
|
"description": "Opret nøgler til scripts og værktøjer.",
|
|
117
140
|
"allScopes": "Fuld adgang",
|
|
118
|
-
"copy": "Kopiér",
|
|
141
|
+
"copy": "Kopiér nøgle",
|
|
119
142
|
"copied": "Nøglen er kopieret.",
|
|
120
143
|
"copiedSnippet": "Kopieret.",
|
|
121
|
-
"
|
|
144
|
+
"copySnippet": "Kopiér kommando",
|
|
122
145
|
"create": {
|
|
123
|
-
"title": "Opret
|
|
146
|
+
"title": "Opret API-nøgle",
|
|
124
147
|
"description": "Nøgler lader scripts og værktøjer som MCP-klienter handle på dine vegne. Giv hver nøgle kun de rettigheder, den har brug for.",
|
|
125
|
-
"submit": "Opret nøgle"
|
|
148
|
+
"submit": "Opret API-nøgle"
|
|
126
149
|
},
|
|
127
150
|
"createError": "Kunne ikke oprette nøglen.",
|
|
151
|
+
"created": "Nøglen er oprettet",
|
|
128
152
|
"name": {
|
|
129
153
|
"label": "Navn",
|
|
154
|
+
"description": "Så du kan genkende nøglen senere.",
|
|
130
155
|
"placeholder": "fx Min MCP-klient",
|
|
131
156
|
"required": "Giv nøglen et navn."
|
|
132
157
|
},
|
|
133
158
|
"scopes": {
|
|
134
159
|
"label": "Rettigheder",
|
|
160
|
+
"description": "Hvad nøglen må.",
|
|
135
161
|
"placeholder": "Vælg hvad nøglen må",
|
|
136
162
|
"required": "Vælg mindst én rettighed."
|
|
137
163
|
},
|
|
@@ -161,11 +187,13 @@
|
|
|
161
187
|
},
|
|
162
188
|
"list": {
|
|
163
189
|
"title": "Dine nøgler",
|
|
164
|
-
"description": "Træk en nøgle tilbage for at stoppe den med det samme.",
|
|
190
|
+
"description": "Nøgler lader scripts og værktøjer som MCP-klienter handle på dine vegne. Træk en nøgle tilbage for at stoppe den med det samme.",
|
|
165
191
|
"empty": "Du har ikke oprettet nogen nøgler endnu.",
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
192
|
+
"name": "Navn",
|
|
193
|
+
"createdColumn": "Oprettet",
|
|
194
|
+
"lastUsedColumn": "Sidst brugt",
|
|
195
|
+
"expiresColumn": "Udløber",
|
|
196
|
+
"neverUsed": "Aldrig"
|
|
169
197
|
},
|
|
170
198
|
"status": {
|
|
171
199
|
"ok": "Aktiv",
|
|
@@ -173,6 +201,10 @@
|
|
|
173
201
|
"expired": "Udløbet"
|
|
174
202
|
},
|
|
175
203
|
"revoke": "Træk nøgle tilbage",
|
|
204
|
+
"revokeConfirm": {
|
|
205
|
+
"title": "Træk {name} tilbage?",
|
|
206
|
+
"description": "Scripts og værktøjer, der bruger nøglen, holder op med at virke med det samme. Det kan ikke fortrydes."
|
|
207
|
+
},
|
|
176
208
|
"revoked": "Nøglen er trukket tilbage.",
|
|
177
209
|
"revokeError": "Kunne ikke trække nøglen tilbage."
|
|
178
210
|
}
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
"label": "Account",
|
|
8
8
|
"description": "Your account."
|
|
9
9
|
},
|
|
10
|
+
"overview": {
|
|
11
|
+
"label": "Overview",
|
|
12
|
+
"description": "A summary of your account."
|
|
13
|
+
},
|
|
10
14
|
"profile": {
|
|
11
15
|
"label": "Profile",
|
|
12
16
|
"description": "Manage your profile details and email address."
|
|
@@ -24,6 +28,22 @@
|
|
|
24
28
|
"description": "Create tokens for scripts and tools."
|
|
25
29
|
}
|
|
26
30
|
},
|
|
31
|
+
"nav": {
|
|
32
|
+
"label": "Account navigation"
|
|
33
|
+
},
|
|
34
|
+
"accountMenu": "Account menu",
|
|
35
|
+
"appearance": {
|
|
36
|
+
"label": "Appearance",
|
|
37
|
+
"light": "Light",
|
|
38
|
+
"dark": "Dark",
|
|
39
|
+
"system": "System"
|
|
40
|
+
},
|
|
41
|
+
"overview": {
|
|
42
|
+
"title": "Account",
|
|
43
|
+
"description": "Your details and everything that belongs to your account.",
|
|
44
|
+
"shortcuts": "Your account",
|
|
45
|
+
"empty": "Nothing here yet."
|
|
46
|
+
},
|
|
27
47
|
"profile": {
|
|
28
48
|
"title": "Profile",
|
|
29
49
|
"description": "Change your information.",
|
|
@@ -97,6 +117,10 @@
|
|
|
97
117
|
"unnamed": "Passkey",
|
|
98
118
|
"created": "Added {date}",
|
|
99
119
|
"remove": "Remove passkey",
|
|
120
|
+
"removeConfirm": {
|
|
121
|
+
"title": "Remove {name}?",
|
|
122
|
+
"description": "You can no longer sign in with this passkey. It stays on your device until you delete it there."
|
|
123
|
+
},
|
|
100
124
|
"empty": "You have not added any passkeys yet.",
|
|
101
125
|
"name": {
|
|
102
126
|
"label": "Passkey name",
|
|
@@ -109,29 +133,31 @@
|
|
|
109
133
|
"loadError": "Could not load your passkeys."
|
|
110
134
|
}
|
|
111
135
|
},
|
|
112
|
-
"backTo": "Back to account",
|
|
113
136
|
"signedInAs": "Signed in as",
|
|
114
137
|
"apiTokens": {
|
|
115
138
|
"title": "API tokens",
|
|
116
139
|
"description": "Create tokens for scripts and tools.",
|
|
117
140
|
"allScopes": "Full access",
|
|
118
|
-
"copy": "Copy",
|
|
141
|
+
"copy": "Copy token",
|
|
119
142
|
"copied": "Token copied.",
|
|
120
143
|
"copiedSnippet": "Copied.",
|
|
121
|
-
"
|
|
144
|
+
"copySnippet": "Copy command",
|
|
122
145
|
"create": {
|
|
123
|
-
"title": "Create
|
|
146
|
+
"title": "Create API token",
|
|
124
147
|
"description": "Tokens let scripts and tools such as MCP clients act as you. Give each one only the scopes it needs.",
|
|
125
|
-
"submit": "Create token"
|
|
148
|
+
"submit": "Create API token"
|
|
126
149
|
},
|
|
127
150
|
"createError": "Could not create the token.",
|
|
151
|
+
"created": "Token created",
|
|
128
152
|
"name": {
|
|
129
153
|
"label": "Name",
|
|
154
|
+
"description": "So you recognise the token later.",
|
|
130
155
|
"placeholder": "e.g. My MCP client",
|
|
131
156
|
"required": "Give the token a name."
|
|
132
157
|
},
|
|
133
158
|
"scopes": {
|
|
134
159
|
"label": "Scopes",
|
|
160
|
+
"description": "What the token may do.",
|
|
135
161
|
"placeholder": "Choose what the token may do",
|
|
136
162
|
"required": "Pick at least one scope."
|
|
137
163
|
},
|
|
@@ -161,11 +187,13 @@
|
|
|
161
187
|
},
|
|
162
188
|
"list": {
|
|
163
189
|
"title": "Your tokens",
|
|
164
|
-
"description": "Revoke
|
|
190
|
+
"description": "Tokens let scripts and tools such as MCP clients act as you. Revoke one to stop it working immediately.",
|
|
165
191
|
"empty": "You have not created any tokens yet.",
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
192
|
+
"name": "Name",
|
|
193
|
+
"createdColumn": "Created",
|
|
194
|
+
"lastUsedColumn": "Last used",
|
|
195
|
+
"expiresColumn": "Expires",
|
|
196
|
+
"neverUsed": "Never"
|
|
169
197
|
},
|
|
170
198
|
"status": {
|
|
171
199
|
"ok": "Active",
|
|
@@ -173,6 +201,10 @@
|
|
|
173
201
|
"expired": "Expired"
|
|
174
202
|
},
|
|
175
203
|
"revoke": "Revoke token",
|
|
204
|
+
"revokeConfirm": {
|
|
205
|
+
"title": "Revoke {name}?",
|
|
206
|
+
"description": "Scripts and tools using this token stop working immediately. This cannot be undone."
|
|
207
|
+
},
|
|
176
208
|
"revoked": "Token revoked.",
|
|
177
209
|
"revokeError": "Could not revoke the token."
|
|
178
210
|
}
|
package/dist/runtime/menus.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("@frigear-nu/menu
|
|
1
|
+
declare const _default: import("@frigear-nu/menu").MenuSource;
|
|
2
2
|
export default _default;
|
package/dist/runtime/menus.js
CHANGED
|
@@ -12,6 +12,16 @@ export default defineMenuSource(() => {
|
|
|
12
12
|
to: config.pages.index,
|
|
13
13
|
order: -99
|
|
14
14
|
});
|
|
15
|
+
extendMenuItem("account", {
|
|
16
|
+
id: "account-overview",
|
|
17
|
+
label: "Overview",
|
|
18
|
+
labelKey: "account.menu.overview.label",
|
|
19
|
+
description: "A summary of your account.",
|
|
20
|
+
descriptionKey: "account.menu.overview.description",
|
|
21
|
+
icon: "i-lucide-layout-dashboard",
|
|
22
|
+
to: config.pages.index,
|
|
23
|
+
order: 0
|
|
24
|
+
});
|
|
15
25
|
}
|
|
16
26
|
if (config?.pages?.profile) {
|
|
17
27
|
const profileItem = defineMenuItem({
|