@frigear-nu/account 0.0.6 → 0.0.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/README.md +20 -6
- package/dist/module.d.mts +31 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +41 -36
- package/dist/runtime/app/components/Account/AccountHeader.d.vue.ts +3 -0
- package/dist/runtime/app/components/Account/AccountHeader.vue +59 -0
- package/dist/runtime/app/components/Account/AccountHeader.vue.d.ts +3 -0
- package/dist/runtime/app/components/Account/AccountMenu.vue +7 -2
- package/dist/runtime/app/components/Account/IdentificationCode.d.vue.ts +3 -0
- package/dist/runtime/app/components/Account/IdentificationCode.vue +82 -0
- package/dist/runtime/app/components/Account/IdentificationCode.vue.d.ts +3 -0
- package/dist/runtime/app/components/Account/IdentificationCodeDialog.d.vue.ts +20 -0
- package/dist/runtime/app/components/Account/IdentificationCodeDialog.vue +16 -0
- package/dist/runtime/app/components/Account/IdentificationCodeDialog.vue.d.ts +20 -0
- package/dist/runtime/app/composables/useAccount.js +3 -3
- package/dist/runtime/app/pages/api-tokens.d.vue.ts +3 -0
- package/dist/runtime/app/pages/api-tokens.vue +308 -0
- package/dist/runtime/app/pages/api-tokens.vue.d.ts +3 -0
- package/dist/runtime/app/pages/documents.d.vue.ts +3 -0
- package/dist/runtime/app/pages/documents.vue +38 -0
- package/dist/runtime/app/pages/documents.vue.d.ts +3 -0
- package/dist/runtime/app/pages/id-code.d.vue.ts +3 -0
- package/dist/runtime/app/pages/id-code.vue +13 -0
- package/dist/runtime/app/pages/id-code.vue.d.ts +3 -0
- package/dist/runtime/app/pages/layout.d.vue.ts +3 -0
- package/dist/runtime/app/pages/layout.vue +10 -0
- package/dist/runtime/app/pages/layout.vue.d.ts +3 -0
- package/dist/runtime/app/pages/profile.vue +188 -46
- package/dist/runtime/app/pages/security.vue +180 -60
- package/dist/runtime/app/utils/documents.d.ts +5 -0
- package/dist/runtime/app/utils/documents.js +7 -0
- package/dist/runtime/i18n/lang/da.json +138 -14
- package/dist/runtime/i18n/lang/en.json +138 -14
- package/dist/runtime/menus.js +42 -0
- package/dist/runtime/server/routes/account/id-code.get.js +20 -0
- package/package.json +25 -11
- package/dist/runtime/server/db/schema.sqlite.d.ts +0 -24
- package/dist/runtime/server/db/schema.sqlite.js +0 -4
- package/dist/runtime/server/routes/account/change-email.post.js +0 -20
- package/dist/runtime/server/routes/account/change-password.post.d.ts +0 -2
- package/dist/runtime/server/routes/account/change-password.post.js +0 -26
- package/dist/runtime/server/routes/account/profile.post.d.ts +0 -2
- package/dist/runtime/server/routes/account/profile.post.js +0 -17
- /package/dist/runtime/server/routes/account/{change-email.post.d.ts → id-code.get.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @frigear-nu/account
|
|
2
2
|
|
|
3
|
-
A module for the account area (profile,
|
|
3
|
+
A module for the account area (profile, security, documents, API tokens pages) in the main frigear app. Migrated from
|
|
4
4
|
[`nuxt-frigear-nu`](https://github.com/Frigear-nu/nuxt-frigear-nu)'s `app/pages/account/**`.
|
|
5
5
|
|
|
6
6
|
## Features
|
|
@@ -8,13 +8,13 @@ A module for the account area (profile, email, password, security) in the main f
|
|
|
8
8
|
- 👤 Update your profile details (name, avatar).
|
|
9
9
|
- ✉️ Change your email address.
|
|
10
10
|
- 🔒 Change your password.
|
|
11
|
-
- 🔑 Manage passkeys (via `@frigear-nu/
|
|
11
|
+
- 🔑 Manage passkeys (via `@frigear-nu/auth`'s `usePasskeys`, when WebAuthn is enabled there).
|
|
12
12
|
- 🧩 Overridable composable (`useAccount`) and opt-in profile/security pages (built with [`@nuxt/ui`](https://ui.nuxt.com)).
|
|
13
13
|
- 🗂️ Contributes "Profile"/"Security" tiles to `@frigear-nu/menu` (when installed), and an opt-in dashboard page
|
|
14
14
|
listing them.
|
|
15
15
|
|
|
16
|
-
This module
|
|
17
|
-
|
|
16
|
+
This module is a UI shell: it owns no tables and no mutation routes. The forms call the self-service routes of
|
|
17
|
+
`@frigear-nu/users` (`/api/users/me/*`) and the token routes of `@frigear-nu/auth`.
|
|
18
18
|
|
|
19
19
|
## Quick Setup
|
|
20
20
|
|
|
@@ -24,8 +24,8 @@ Install the module to your Nuxt application with one command:
|
|
|
24
24
|
npx nuxt module add @frigear-nu/account
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
This module requires [`@frigear-nu/users`](../users) to be installed in the host app, since
|
|
28
|
-
|
|
27
|
+
This module requires [`@frigear-nu/users`](../users) to be installed in the host app, since its forms call the
|
|
28
|
+
self-service routes there.
|
|
29
29
|
|
|
30
30
|
## Configuration
|
|
31
31
|
|
|
@@ -69,3 +69,17 @@ await updateProfileDetails({ name: 'Ada Lovelace' })
|
|
|
69
69
|
await changeEmailAddress({ email: 'ada@example.com' })
|
|
70
70
|
await changePassword({ currentPassword: 'old', newPassword: 'new-password', confirmNewPassword: 'new-password' })
|
|
71
71
|
```
|
|
72
|
+
|
|
73
|
+
## Translations
|
|
74
|
+
|
|
75
|
+
Strings live under the `account.*` namespace in `da`/`en` locale files and can be overridden in your app's
|
|
76
|
+
`i18n/locales/<code>.json`. See the [documentation](https://docs.frigear.dev/modules/account#translations).
|
|
77
|
+
|
|
78
|
+
## Frigear ID (QR code)
|
|
79
|
+
|
|
80
|
+
`settings.pages.idCode` (`true` or a path, default `/account/id`) adds a page with a QR code (`urn:frigear:id:<jwt>`)
|
|
81
|
+
that refreshes every 60 seconds, plus an account menu tile. `GET /api/account/id-code` returns the RS256-signed JWT
|
|
82
|
+
(60s lifetime, `sub` = user id). It signs with `runtimeConfig.account.idCode.privateKey`
|
|
83
|
+
(`NUXT_ACCOUNT_ID_CODE_PRIVATE_KEY`) or, when unset, the `@frigear-nu/oauth-server` key (`runtimeConfig.oauthServer`),
|
|
84
|
+
so scanners can verify it against `/.well-known/jwks.json`. Without a key the endpoint responds `501`.
|
|
85
|
+
`AccountIdentificationCodeDialog` wraps the same content in a modal.
|
package/dist/module.d.mts
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
|
|
3
|
+
/** A downloadable file listed on the documents page. */
|
|
4
|
+
type AccountDocument = {
|
|
5
|
+
id: string;
|
|
6
|
+
/** A string, or a map of locale -> string (falls back to `en`, then the first value). */
|
|
7
|
+
label: string | Record<string, string>;
|
|
8
|
+
/** Where the file is served from, e.g. `/assets/files/handbook.pdf`. */
|
|
9
|
+
path: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Only shown to users with one of these roles (the `role` of the session user). Omit to show to every signed-in
|
|
13
|
+
* user. This only decides what is *listed*: a file in `public/` can still be fetched by anyone who knows its URL.
|
|
14
|
+
*/
|
|
15
|
+
roles?: string[];
|
|
16
|
+
};
|
|
3
17
|
type PublicModuleOptions = {
|
|
4
18
|
pages: {
|
|
5
19
|
index?: string;
|
|
6
20
|
profile?: string;
|
|
7
21
|
security?: string;
|
|
22
|
+
documents?: string;
|
|
23
|
+
idCode?: string;
|
|
24
|
+
apiTokens?: string;
|
|
8
25
|
};
|
|
26
|
+
documents: AccountDocument[];
|
|
9
27
|
};
|
|
10
28
|
type ModuleOptions = {
|
|
11
29
|
settings?: {
|
|
@@ -13,7 +31,11 @@ type ModuleOptions = {
|
|
|
13
31
|
index?: string | boolean;
|
|
14
32
|
profile?: string | boolean;
|
|
15
33
|
security?: string | boolean;
|
|
34
|
+
documents?: string | boolean;
|
|
35
|
+
idCode?: string | boolean;
|
|
36
|
+
apiTokens?: string | boolean;
|
|
16
37
|
};
|
|
38
|
+
documents?: AccountDocument[];
|
|
17
39
|
};
|
|
18
40
|
};
|
|
19
41
|
|
|
@@ -21,6 +43,15 @@ declare module 'nuxt/schema' {
|
|
|
21
43
|
interface NuxtConfig {
|
|
22
44
|
account?: ModuleOptions;
|
|
23
45
|
}
|
|
46
|
+
interface RuntimeConfig {
|
|
47
|
+
account?: {
|
|
48
|
+
idCode?: {
|
|
49
|
+
privateKey?: string;
|
|
50
|
+
keyId?: string;
|
|
51
|
+
issuer?: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
}
|
|
24
55
|
interface PublicRuntimeConfig {
|
|
25
56
|
account?: PublicModuleOptions;
|
|
26
57
|
}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, hasNuxtModule, addServerHandler, addImports, extendPages, addComponentsDir } from '@nuxt/kit';
|
|
2
|
-
import { registerModule, registerLocales,
|
|
2
|
+
import { registerModule, registerTailwindSource, registerLocales, resolvePagePath, addOptionalMenuSource } from '@frigear-nu/module-kit/utils';
|
|
3
3
|
|
|
4
|
-
function resolvePagePath(setting, defaultPath) {
|
|
5
|
-
if (typeof setting === "string") return setting;
|
|
6
|
-
if (setting === true) return defaultPath;
|
|
7
|
-
return void 0;
|
|
8
|
-
}
|
|
9
4
|
const module$1 = defineNuxtModule({
|
|
10
5
|
meta: { name: "@frigear-nu/account", configKey: "account" },
|
|
11
6
|
defaults: {
|
|
@@ -18,6 +13,8 @@ const module$1 = defineNuxtModule({
|
|
|
18
13
|
"@frigear-nu/module-kit": {
|
|
19
14
|
version: ">=0.0.1"
|
|
20
15
|
},
|
|
16
|
+
"@frigear-nu/auth": { version: ">=0.0.1" },
|
|
17
|
+
// The profile, email and password forms call its self-service routes.
|
|
21
18
|
"@frigear-nu/users": {
|
|
22
19
|
version: ">=0.0.1"
|
|
23
20
|
},
|
|
@@ -29,59 +26,67 @@ const module$1 = defineNuxtModule({
|
|
|
29
26
|
async setup(options, nuxt) {
|
|
30
27
|
registerModule("@frigear-nu/account", nuxt);
|
|
31
28
|
const resolver = createResolver(import.meta.url);
|
|
29
|
+
registerTailwindSource(nuxt, resolver.resolve("./runtime/app"));
|
|
32
30
|
registerLocales(nuxt, resolver.resolve("./runtime/i18n/lang"));
|
|
33
31
|
const settings = options.settings ?? {};
|
|
34
32
|
const withMenus = hasNuxtModule("@frigear-nu/menu");
|
|
35
33
|
addServerHandler({
|
|
36
|
-
route: "/api/account/
|
|
37
|
-
method: "
|
|
38
|
-
handler: resolver.resolve("./runtime/server/routes/account/
|
|
39
|
-
});
|
|
40
|
-
addServerHandler({
|
|
41
|
-
route: "/api/account/change-email",
|
|
42
|
-
method: "post",
|
|
43
|
-
handler: resolver.resolve("./runtime/server/routes/account/change-email.post")
|
|
44
|
-
});
|
|
45
|
-
addServerHandler({
|
|
46
|
-
route: "/api/account/change-password",
|
|
47
|
-
method: "post",
|
|
48
|
-
handler: resolver.resolve("./runtime/server/routes/account/change-password.post")
|
|
34
|
+
route: "/api/account/id-code",
|
|
35
|
+
method: "get",
|
|
36
|
+
handler: resolver.resolve("./runtime/server/routes/account/id-code.get")
|
|
49
37
|
});
|
|
38
|
+
const existingAccount = nuxt.options.runtimeConfig.account;
|
|
39
|
+
nuxt.options.runtimeConfig.account = {
|
|
40
|
+
idCode: { privateKey: "", keyId: "", issuer: "", ...existingAccount?.idCode }
|
|
41
|
+
};
|
|
50
42
|
addImports([{ name: "useAccount", from: resolver.resolve("./runtime/app/composables/useAccount") }]);
|
|
51
43
|
const profilePath = resolvePagePath(settings.pages?.profile, "/account/profile");
|
|
52
44
|
const securityPath = resolvePagePath(settings.pages?.security, "/account/security");
|
|
45
|
+
const documentsPath = resolvePagePath(settings.pages?.documents, "/account/documents");
|
|
46
|
+
const idCodePath = resolvePagePath(settings.pages?.idCode, "/account/id");
|
|
47
|
+
const apiTokensPath = resolvePagePath(settings.pages?.apiTokens, "/account/api-tokens");
|
|
53
48
|
const indexPath = withMenus ? resolvePagePath(settings.pages?.index, "/account") : void 0;
|
|
54
49
|
if (!withMenus && settings.pages?.index) {
|
|
55
50
|
console.warn("[@frigear-nu/account] `settings.pages.index` was set, but `@frigear-nu/menu` is not installed - skipping the dashboard page.");
|
|
56
51
|
}
|
|
57
52
|
nuxt.options.runtimeConfig.public.account = {
|
|
58
|
-
pages: { index: indexPath, profile: profilePath, security: securityPath }
|
|
53
|
+
pages: { index: indexPath, profile: profilePath, security: securityPath, documents: documentsPath, idCode: idCodePath, apiTokens: apiTokensPath },
|
|
54
|
+
documents: settings.documents ?? []
|
|
59
55
|
};
|
|
60
|
-
if (indexPath || profilePath || securityPath) {
|
|
56
|
+
if (indexPath || profilePath || securityPath || documentsPath || idCodePath || apiTokensPath) {
|
|
61
57
|
extendPages((pages) => {
|
|
62
58
|
const existingPaths = new Set(pages.map((page) => page.path));
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
const children = [];
|
|
60
|
+
const add = (path, name, file) => {
|
|
61
|
+
if (path && !existingPaths.has(path)) children.push({ name, path, file: resolver.resolve(file) });
|
|
62
|
+
};
|
|
63
|
+
add(indexPath, "account-index", "./runtime/app/pages/index.vue");
|
|
64
|
+
add(profilePath, "account-profile", "./runtime/app/pages/profile.vue");
|
|
65
|
+
add(documentsPath, "account-documents", "./runtime/app/pages/documents.vue");
|
|
66
|
+
add(securityPath, "account-security", "./runtime/app/pages/security.vue");
|
|
67
|
+
add(idCodePath, "account-id-code", "./runtime/app/pages/id-code.vue");
|
|
68
|
+
add(apiTokensPath, "account-api-tokens", "./runtime/app/pages/api-tokens.vue");
|
|
69
|
+
if (children.length) {
|
|
70
|
+
pages.push({ name: "account-layout", path: "/__account", file: resolver.resolve("./runtime/app/pages/layout.vue"), children });
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
if (
|
|
75
|
-
const
|
|
76
|
-
|
|
74
|
+
if (hasNuxtModule("@frigear-nu/authorization")) {
|
|
75
|
+
const prefixes = ["", ...(nuxt.options.i18n?.locales ?? []).map((locale) => typeof locale === "string" ? locale : locale.code).map((code) => `/${code}`)];
|
|
76
|
+
const rules = nuxt.options.routeRules ||= {};
|
|
77
|
+
const guarded = [indexPath, profilePath, securityPath, documentsPath, idCodePath, apiTokensPath].filter((path) => !!path && !path.startsWith("/account"));
|
|
78
|
+
for (const prefix of prefixes) {
|
|
79
|
+
for (const pattern of [`${prefix}/account`, `${prefix}/account/**`, ...guarded.map((path) => `${prefix}${path}`)]) {
|
|
80
|
+
rules[pattern] = { authenticated: true, ...rules[pattern] };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
console.warn("[@frigear-nu/account] @frigear-nu/authorization is not installed: the account pages are not guarded. Install it and guard `/account/**` yourself.");
|
|
77
85
|
}
|
|
86
|
+
addOptionalMenuSource(nuxt, { file: resolver.resolve("./runtime/menus"), menu: "dashboard.account" });
|
|
78
87
|
addComponentsDir({
|
|
79
88
|
path: resolver.resolve("./runtime/app/components")
|
|
80
89
|
});
|
|
81
|
-
extendDatabaseTable(nuxt, {
|
|
82
|
-
resolver,
|
|
83
|
-
file: (dialect) => `./runtime/server/db/schema.${dialect}`
|
|
84
|
-
});
|
|
85
90
|
}
|
|
86
91
|
});
|
|
87
92
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const t = useModuleT("account");
|
|
3
|
+
const route = useRoute();
|
|
4
|
+
const { user } = useUserSession();
|
|
5
|
+
const pages = useRuntimeConfig().public.account?.pages;
|
|
6
|
+
const nuxtApp = useNuxtApp();
|
|
7
|
+
const items = typeof useMenu === "function" ? useMenu("account").data : ref([]);
|
|
8
|
+
const trimSlash = (path) => path.length > 1 ? path.replace(/\/+$/, "") : path;
|
|
9
|
+
const stripLocale = (path) => {
|
|
10
|
+
const trimmed = trimSlash(path);
|
|
11
|
+
const first = trimmed.split("/")[1];
|
|
12
|
+
const codes = (nuxtApp.$i18n?.locales?.value ?? []).map((locale) => typeof locale === "string" ? locale : locale.code);
|
|
13
|
+
return first && codes.includes(first) ? trimmed.slice(first.length + 1) || "/" : trimmed;
|
|
14
|
+
};
|
|
15
|
+
const current = computed(() => {
|
|
16
|
+
const path = stripLocale(route.path);
|
|
17
|
+
return (items.value ?? []).find((item) => item.to && !item.external && stripLocale(item.to) === path);
|
|
18
|
+
});
|
|
19
|
+
const title = computed(() => current.value?.label ?? t("title", "Account"));
|
|
20
|
+
const description = computed(() => current.value?.description);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<UContainer>
|
|
25
|
+
<UPageHeader
|
|
26
|
+
:title="title"
|
|
27
|
+
:description="description"
|
|
28
|
+
:ui="{ wrapper: 'flex-row items-center justify-between gap-4', description: 'mt-2' }"
|
|
29
|
+
>
|
|
30
|
+
<UButton
|
|
31
|
+
v-if="current && pages?.index"
|
|
32
|
+
variant="link"
|
|
33
|
+
icon="i-lucide-arrow-left"
|
|
34
|
+
:to="pages.index"
|
|
35
|
+
:label="t('backTo', 'Back to account')"
|
|
36
|
+
class="-ml-3 mt-2"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<template #links>
|
|
40
|
+
<div
|
|
41
|
+
v-if="user"
|
|
42
|
+
class="flex flex-col"
|
|
43
|
+
>
|
|
44
|
+
<div class="text-sm text-muted">
|
|
45
|
+
{{ t("signedInAs", "Signed in as") }}
|
|
46
|
+
</div>
|
|
47
|
+
<div class="text-primary">
|
|
48
|
+
{{ user.email }}
|
|
49
|
+
</div>
|
|
50
|
+
<div class="mt-1 flex justify-end">
|
|
51
|
+
<UBadge variant="soft">
|
|
52
|
+
Frigear ID: <code>{{ user.id }}</code>
|
|
53
|
+
</UBadge>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
</UPageHeader>
|
|
58
|
+
</UContainer>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -19,13 +19,18 @@ const items = computedAsync(async () => {
|
|
|
19
19
|
}
|
|
20
20
|
]);
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
const role = user.value?.role;
|
|
23
|
+
const visible = (dropdownItems.value ?? []).filter((item) => {
|
|
24
|
+
const roles = item.meta?.roles;
|
|
25
|
+
return !roles?.length || !!role && roles.includes(role);
|
|
26
|
+
});
|
|
27
|
+
items2.push(visible.map((item) => ({
|
|
23
28
|
label: item.label,
|
|
24
29
|
to: item.to,
|
|
25
30
|
icon: item.icon,
|
|
26
31
|
external: item.external,
|
|
27
32
|
replace: item.external
|
|
28
|
-
}))
|
|
33
|
+
})));
|
|
29
34
|
return items2;
|
|
30
35
|
});
|
|
31
36
|
</script>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { renderSVG } from "uqr";
|
|
3
|
+
import { useIntervalFn } from "@vueuse/core";
|
|
4
|
+
const t = useModuleT("account.identificationCode");
|
|
5
|
+
const { user } = useUserSession();
|
|
6
|
+
const { data, refresh, status, error } = useFetch("/api/account/id-code", { server: false });
|
|
7
|
+
const remaining = ref(0);
|
|
8
|
+
watch(data, (value) => {
|
|
9
|
+
remaining.value = value?.expiresIn ?? 0;
|
|
10
|
+
}, { immediate: true });
|
|
11
|
+
const refreshing = computed(() => status.value === "pending");
|
|
12
|
+
useIntervalFn(() => {
|
|
13
|
+
if (!data.value || refreshing.value) return;
|
|
14
|
+
if (remaining.value <= 1) {
|
|
15
|
+
remaining.value = 0;
|
|
16
|
+
refresh();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
remaining.value -= 1;
|
|
20
|
+
}, 1e3);
|
|
21
|
+
const svg = computed(() => data.value ? renderSVG(data.value.payload, { ecc: "L", border: 1 }) : "");
|
|
22
|
+
const role = computed(() => user.value?.role);
|
|
23
|
+
const membership = computed(() => user.value?.membership);
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div class="flex flex-col items-center gap-3">
|
|
28
|
+
<UAlert
|
|
29
|
+
v-if="error"
|
|
30
|
+
color="error"
|
|
31
|
+
variant="subtle"
|
|
32
|
+
icon="i-lucide-triangle-alert"
|
|
33
|
+
:title="t('error', 'Could not create your Frigear ID.')"
|
|
34
|
+
/>
|
|
35
|
+
<template v-else-if="svg">
|
|
36
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
37
|
+
<div
|
|
38
|
+
class="size-60 rounded-md bg-white p-2 [&>svg]:size-full"
|
|
39
|
+
v-html="svg"
|
|
40
|
+
/>
|
|
41
|
+
<UBadge
|
|
42
|
+
color="warning"
|
|
43
|
+
class="text-xl font-bold"
|
|
44
|
+
>
|
|
45
|
+
{{ t("expiresIn", "Refreshes in {time}s").replace("{time}", String(remaining)) }}
|
|
46
|
+
</UBadge>
|
|
47
|
+
<div class="flex flex-wrap justify-center gap-2">
|
|
48
|
+
<UBadge
|
|
49
|
+
v-if="user"
|
|
50
|
+
size="lg"
|
|
51
|
+
>
|
|
52
|
+
Frigear ID: {{ user.id }}
|
|
53
|
+
</UBadge>
|
|
54
|
+
<UBadge
|
|
55
|
+
v-if="membership"
|
|
56
|
+
size="lg"
|
|
57
|
+
>
|
|
58
|
+
{{ membership }}
|
|
59
|
+
</UBadge>
|
|
60
|
+
<UBadge
|
|
61
|
+
v-if="role"
|
|
62
|
+
size="lg"
|
|
63
|
+
color="success"
|
|
64
|
+
>
|
|
65
|
+
{{ t("role", "Role") }}: {{ role }}
|
|
66
|
+
</UBadge>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
<UIcon
|
|
70
|
+
v-else
|
|
71
|
+
name="i-lucide-refresh-cw"
|
|
72
|
+
class="size-6 animate-spin"
|
|
73
|
+
/>
|
|
74
|
+
<UButton
|
|
75
|
+
icon="i-lucide-refresh-cw"
|
|
76
|
+
variant="subtle"
|
|
77
|
+
:loading="refreshing"
|
|
78
|
+
:label="t('refresh', 'Refresh')"
|
|
79
|
+
@click="refresh()"
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type __VLS_ModelProps = {
|
|
2
|
+
'open'?: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare var __VLS_8: {};
|
|
5
|
+
type __VLS_Slots = {} & {
|
|
6
|
+
default?: (props: typeof __VLS_8) => any;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:open": (value: boolean) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
11
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
14
|
+
declare const _default: typeof __VLS_export;
|
|
15
|
+
export default _default;
|
|
16
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
17
|
+
new (): {
|
|
18
|
+
$slots: S;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const t = useModuleT("account.identificationCode");
|
|
3
|
+
const open = defineModel("open", { type: Boolean, ...{ default: false } });
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<UModal
|
|
8
|
+
v-model:open="open"
|
|
9
|
+
:title="t('title', 'Frigear ID')"
|
|
10
|
+
>
|
|
11
|
+
<slot />
|
|
12
|
+
<template #body>
|
|
13
|
+
<AccountIdentificationCode v-if="open" />
|
|
14
|
+
</template>
|
|
15
|
+
</UModal>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type __VLS_ModelProps = {
|
|
2
|
+
'open'?: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare var __VLS_8: {};
|
|
5
|
+
type __VLS_Slots = {} & {
|
|
6
|
+
default?: (props: typeof __VLS_8) => any;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:open": (value: boolean) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
11
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
14
|
+
declare const _default: typeof __VLS_export;
|
|
15
|
+
export default _default;
|
|
16
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
17
|
+
new (): {
|
|
18
|
+
$slots: S;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -17,19 +17,19 @@ export const useAccount = () => {
|
|
|
17
17
|
}
|
|
18
18
|
function updateProfileDetails(payload) {
|
|
19
19
|
return withRequest(
|
|
20
|
-
() => $fetch("/api/
|
|
20
|
+
() => $fetch("/api/users/me/profile", { method: "POST", body: payload }),
|
|
21
21
|
"Failed to update your profile."
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
24
|
function changeEmailAddress(payload) {
|
|
25
25
|
return withRequest(
|
|
26
|
-
() => $fetch("/api/
|
|
26
|
+
() => $fetch("/api/users/me/change-email", { method: "POST", body: payload }),
|
|
27
27
|
"Failed to change your email address."
|
|
28
28
|
);
|
|
29
29
|
}
|
|
30
30
|
function changePassword(payload) {
|
|
31
31
|
return withRequest(
|
|
32
|
-
() => $fetch("/api/
|
|
32
|
+
() => $fetch("/api/users/me/change-password", { method: "POST", body: payload }),
|
|
33
33
|
"Failed to change your password."
|
|
34
34
|
);
|
|
35
35
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|