Account
Account gives every signed-in user a place to manage themselves: their name and avatar, email address, password, passkeys and API tokens. Other modules add their own pages to the same area, such as membership or tickets. Install it when users sign in to your app and should look after their own account. Staff manage other people's accounts in the admin area of Users instead.
The package is @frigear-nu/account. It is a settings page inside your public site, with its own section navigation.
It owns no tables and registers no server routes: its pages call the routes of Users and Auth.
What you get
- Pages (each opt-in): an overview at
/account,/account/profile,/account/security(password and passkeys),/account/documentsand/account/api-tokens. - Layout (on by default): the
accountlayout, with the site header and footer, a left section list and aPageSectionSwitcheron narrow screens. Pages other modules add under/accountget it too. - Composables and helpers:
useAccount()for the profile, email and password changes,useAccountNav()for the navigation, and theAccountHeader,AccountIdentityandAccountMenucomponents (on by default). - Account menu (on by default):
AccountMenu, the signed-in user's dropdown with the account links, sign-out and colour mode.
The screens follow the UX patterns and pages.
@frigear-nu/module-kit, @frigear-nu/auth,
@frigear-nu/users and @frigear-nu/ui, which brings @nuxt/ui.You add:@nuxthub/core.Optional:@frigear-nu/menu (the account navigation, the dropdown and the overview
page), @frigear-nu/widget (the account.overview widget area) and
@frigear-nu/authorization (closes the area to visitors).Quick setup
Install Account
pnpm add @frigear-nu/account @nuxthub/core
Add it to nuxt.config.ts
Turn on the pages you want. Each takes true for its default path, or a path of your own:
export default defineNuxtConfig({
modules: [
'@nuxthub/core',
'@frigear-nu/menu', // optional
'@frigear-nu/account',
],
account: {
settings: {
pages: {
profile: true, // or a custom path, e.g. '/me'
security: true,
index: true, // the overview, only registered with @frigear-nu/menu
documents: true,
apiTokens: true,
},
documents: [
{ id: 'handbook', label: { da: 'Håndbog', en: 'Handbook' }, path: '/assets/files/handbook.pdf' },
{ id: 'crew', label: 'Crew plan', path: '/assets/files/crew.pdf', permission: 'crew-area:view' },
],
},
},
})
/account/profile shows your name and email inside the site, with the account
sections in a list on the left.Options
Options go under the account key.
| Option | Type | Default | Purpose |
|---|---|---|---|
settings.layout | boolean | true | Renders the account area in the account layout. See Layout. |
settings.pages.index | string | boolean | off | The overview: summary, widgets and shortcuts. true means /account. |
settings.pages.profile | string | boolean | off | Name, avatar and email. true means /account/profile. |
settings.pages.security | string | boolean | off | Password, passkeys and, with OAuth Server, Signed-in apps. true means /account/security. |
settings.pages.documents | string | boolean | off | Lists settings.documents for download. true means /account/documents. |
settings.pages.apiTokens | string | boolean | off | Create and revoke API tokens. true means /account/api-tokens. |
settings.documents | AccountDocument[] | [] | Files offered on the documents page. |
settings.layout. An app/layouts/account.vue of your own (or a layer's) replaces the module's, and the route rule
still applies it by name. false registers no layout and sets no rule. The shipped pages then render their own
AccountHeader but bring no container.
settings.pages.index. It needs @frigear-nu/menu. Without Menu it is skipped with a warning.
settings.pages.apiTokens. It is skipped when Auth has apiTokens: false.
settings.pages.*. A page is on with true (its default path) or a custom path, and off when left out. A page
whose path already exists in your app is not registered. So your app replaces a shipped page by declaring its own at
that path.
type AccountDocument = {
id: string
/** A string, or a locale map (falls back to `en`, then the first value). */
label: string | Record<string, string>
/** Where the file is served from, e.g. `/assets/files/handbook.pdf`. */
path: string
icon?: string
/** The permission a user must hold to see it listed. Omit to list it for every signed-in user. Needs Authorization. */
permission?: string
}
permission only filters the list, and without Authorization a document with a permission is never listed. A file served from public/ can be fetched by anyone who knows its URL. Put sensitive
files behind a server route if that matters.Server
Account registers no server routes of its own. Its pages call the self-service routes of
Users through usersApi: PATCH /api/users/me (profile), PUT /api/users/me/email
and PUT /api/users/me/password. They need a cookie session and answer the caller's
profile.
App
The pages, the layout and the components your app can use or replace.
Pages
API tokens. The page lists the user's tokens in a table.
- Create API token, in the page heading's actions, opens a slideover with the name, scopes and expiry.
- The new token is shown once above the table, with a copy button.
- Revoking a token asks for confirmation.
- Tokens are created through Auth.
- When
users.settings.mcpis on, the page also shows a "Connect to MCP" card (MCP).
Profile and security. The profile page opens Change email in a slideover. The security page's Add passkey, in the passkeys card heading, opens a slideover too, with an optional name field.
Email changes. Confirmed email changes are a setting of Users (users.settings.verifyEmailChange). Point
users.settings.profilePath at the profile page, which then shows the result. See
Users.
Layout
Every page under /account and /<locale>/account renders in the account layout, plus any configured page path
outside it.
- The module sets this with an
appLayoutroute rule. So pages other modules add under/account(membership, tickets, applications) get the layout too, whenever they register. - A page that sets
layoutin its owndefinePageMetakeeps its choice.
The layout itself (site header and footer, a left section list, PageSectionSwitcher on narrow screens) is described
in Account area. The navigation is built from the account
menu, and the layout renders no heading.
Details:
- Without
@frigear-nu/menu, the navigation lists the pages that are on insettings.pages. - An app without a
defaultlayout still works: the area renders on its own. - The document title defaults to the active navigation entry. A page's heading overrides it.
app.vuemust render<NuxtLayout>for any layout to apply.
Heading
Every account page renders its own heading. AccountHeader is an opt-in
PageHeading for this. Its title is the page's title meta, else the label
of the account menu item the route belongs to (longest matching path), else "Account". It also sets the document
title.
Overview
The overview (pages.index) shows, in order:
- an
AccountHeader("Overview"), - a summary card:
UUserwith avatar, name and email, andAccountIdentity, - the
account.overviewwidget area, when@frigear-nu/widgetis installed, - a card with shortcuts to every other entry of the
accountmenu.
Widgets receive the session user as a prop. A module adds one from its setup():
// In another module's setup()
import { extendWidgets } from '@frigear-nu/widget/utils'
extendWidgets(nuxt, 'account.overview', {
id: 'next-ticket',
component: resolver.resolve('./runtime/app/components/NextTicket.vue'),
order: 10,
})
Use StatCard for numbers and ListCard for short lists, as on the admin
dashboard.
Composables
const { updateProfileDetails, changeEmailAddress, changePassword, pending, error } = useAccount()
await updateProfileDetails({ name: 'Ada Lovelace', avatar: 'https://example.com/ada.png' }) // avatar: null clears it
await changeEmailAddress({ email: 'ada@example.com' })
await changePassword({ currentPassword: 'old', newPassword: 'new-password', confirmNewPassword: 'new-password' })
Each call refreshes the user session afterwards. pending and error are refs.
const { items, links, current } = await useAccountNav()
The layout, the heading and the overview read useAccountNav():
itemsare the account navigation entries: theaccountmenu, or the pages that are on without the Menu module.linksare the same entries asUNavigationMenuitems, localised and withactive.currentis the entry the route belongs to.
The module also auto-imports the components AccountHeader, AccountIdentity and AccountMenu.
AccountMenu
The signed-in user's dropdown. It shows:
- the user, with a role badge for any role other than
user, - the
account.dropdownmenu: Account, Admin and sign-out, - Appearance: light, dark or system, which sets
useColorMode().preference.
Items with a permission are only listed for users who hold it (Authorization filters them through the menus:filter hook). The entry for the area you are in is
left out. Items with order 99 or more (sign-out) come last.
| Prop | Type | Purpose |
|---|---|---|
block | boolean | A full-width row (avatar, name, chevrons) for a sidebar footer, as in the admin shell. Without it, an avatar button for a site header. |
collapsed | boolean | With block: only the avatar, for a collapsed sidebar. |
<AccountMenu />
<AccountMenu block :collapsed="collapsed" />
The colour mode lives here rather than in the header. Set header.colorMode: false in the
base layer app config to drop the header toggle.
Overriding parts
The module's components have a lower priority than your app's, so a component with the same name in your app replaces
it. AccountIdentity is shown next to the user in the overview's summary card and nowhere else. By default it is a role
badge for anyone who is not a plain user, otherwise nothing. Override it to show something else, such as a member
number or a Frigear ID badge:
<!-- app/components/Account/AccountIdentity.vue -->
<template>
<MyIdentityDetails />
</template>
Menu contributions
With Menu installed, each page that is on adds an item. The account menu is the account
navigation and the overview's shortcuts. account.dropdown is AccountMenu. order sorts within the menu.
| Item | Menu | Order | Page |
|---|---|---|---|
account ("Account") | account.dropdown | -99 | pages.index |
account-overview ("Overview") | account | 0 | pages.index |
profile | account and account.dropdown | 10 | pages.profile |
security | account | 20 | pages.security |
api-tokens | account | 25 | pages.apiTokens |
documents | account | 30 | pages.documents |
Other modules add to the same menu: Membership (Subscriptions) at 15, Tickets
(Events) at 40 and Applications (OAuth Server) at 50. Pick
an order between these for a new page. The source is registered for the account menu.
Signed-in only
With @frigear-nu/authorization installed, the module adds an authenticated: true route rule for /account and
/account/**, and for each /<locale> prefix. So every page under it is closed to
visitors, including the pages other modules add (membership, tickets, applications).
- Set
authorization.redirectToto send visitors to the sign-in page instead of a401error. - Without the Authorization module, the pages are not guarded and the module logs a warning.
Routes
None. The module registers no server routes.
Translations
Override any string in your app's i18n/locales/<code>.json. The module registers da and en locale files under the
account.* namespace. See
overriding module strings.
- The key groups are
title,menu.*,profile.*,documents.*andsecurity.*. - The layout and the menu use
nav.*,appearance.*,overview.*andaccountMenu. - The full list is in
src/runtime/i18n/lang/en.json. - Without
@nuxtjs/i18n, the English defaults are used.
{ "account": { "profile": { "title": "My details" } } }
Next steps
- Widget – add a card of your own to the overview.
- Authorization – close the account area to visitors.
- Layouts and navigation – how the account area fits the other areas.