Modules

Account

The account area - a layout with its own navigation, an overview and the profile, security, documents and API tokens pages - built on Users and Auth.

The account area as a Nuxt module: an account layout with a left navigation, and opt-in pages built with @nuxt/ui and the @frigear-nu/ui page patterns over the self-service routes of Users and the token routes of Auth. It owns no tables and registers no server routes of its own. The screens follow the UX patterns.

Requires@nuxthub/core, @frigear-nu/module-kit, @frigear-nu/auth, @frigear-nu/users and @frigear-nu/ui (which brings @nuxt/ui). Optional:@frigear-nu/menu (the account navigation, the dropdown and the overview page) and @frigear-nu/widget (the account.overview widget area).

Quick setup

npx nuxt module add @frigear-nu/account
export default defineNuxtConfig({
  modules: [
    '@nuxthub/core',
    '@frigear-nu/users',
    '@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', roles: ['admin', 'crew'] },
      ],
    },
  },
})

Options

All options live under account.settings.

OptionTypeDefaultPurpose
layoutbooleantrueRenders the account area in the account layout (see Layout). false leaves the layout to the app.
pages.indexstring | booleanoffThe overview (summary, widgets, shortcuts). Needs @frigear-nu/menu; skipped with a warning without it. true = /account.
pages.profilestring | booleanoffProfile page (name, avatar, email). true = /account/profile.
pages.securitystring | booleanoffPassword and passkeys. true = /account/security.
pages.documentsstring | booleanoffLists documents for download. true = /account/documents.
pages.apiTokensstring | booleanoffCreate and revoke personal API tokens. true = /account/api-tokens.
documentsAccountDocument[][]Files offered on the documents page.

A page is enabled with true (default path), a custom path string, or left off. A page whose path already exists in the app is not registered, so an 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
  /** Only listed for users whose session `role` is one of these. Omit to list for every signed-in user. */
  roles?: string[]
}
roles only filters the list. A file served from public/ can be fetched by anyone who knows its URL; put sensitive files behind a server route if that matters.

API tokens and email changes

The API tokens page lists the user's tokens in a table; Create API token opens a slideover with the name, scopes and expiry, and 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.mcp is on, the page also shows a "Connect to MCP" card (MCP).

Verified email changes are a setting of Users (users.settings.verifyEmailChange); point users.settings.profilePath at the profile page, which shows the result. See Users.

Layout

Every page under /account (and /<locale>/account), plus any configured page path outside it, renders in the account layout. The module sets this with an appLayout route rule, so pages other modules add under /account (membership, tickets, applications) get it too, whenever they register. A page that sets layout in its own definePageMeta keeps its choice.

The layout renders the app's default layout (the site header and footer) and puts the account area inside it:

  • on wide screens a left column (UPageAside) with the signed-in user and a vertical navigation built from the account menu (useMenu('account'), through useNavLinks); the overview is only active on its own path;
  • on narrow screens the same entries as a PageTabs strip above the content;
  • AccountHeader, then the page.

Without @frigear-nu/menu the navigation lists the enabled pages from settings.pages instead. An app without a default layout still works: the account area then renders on its own. The app's app.vue must render <NuxtLayout> for any layout to apply.

settings.layout: false turns this off: no layout is registered and the pages render in whatever layout the app picks. The shipped pages bring no container or heading of their own, so wrap them in a layout that renders <AccountHeader />.

Heading

AccountHeader is a PageHeading. Its title is the page's title meta, else the label of the account menu item the route belongs to (the longest matching path, so /account/membership/success still reads "Membership"), else "Account"; the description comes from the same menu item. It also sets the document title. AccountHeaderRight fills its actions on every page. A page hides the heading with:

definePageMeta({ heading: false })

A page another module adds needs nothing but its route and an account menu item to get the navigation and heading. It should not draw its own container or account header; its root is a plain <div class="flex flex-col gap-4">.

Overview

The overview (pages.index) shows a summary card (avatar, name, email and AccountHeaderRight), then the account.overview widget area when @frigear-nu/widget is installed, then a card with shortcuts to every other entry of the account menu. Widgets receive the session user as a prop:

// 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.

Server

The account routes call the self-service routes of Users (/api/users/me/*), which require a cookie session.

App

const { updateProfileDetails, changeEmailAddress, changePassword, pending, error } = useAccount()

await updateProfileDetails({ name: 'Ada Lovelace', avatar: 'https://example.com/ada.png' })
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()

useAccountNav() is what the layout, the heading and the overview read: items are the account navigation entries (the account menu, or the enabled pages without the menu module), links the same as UNavigationMenu items (localised, with active), and current the entry the route belongs to.

The module also auto-imports the components AccountHeader, AccountHeaderRight and AccountMenu.

AccountMenu

The signed-in user's dropdown: the user (with a role badge for anything but user), the account.dropdown menu (Account, Admin, sign out), and Appearance (light, dark or system, setting useColorMode().preference). Items with meta.roles are only listed for users holding one of those roles, the entry for the area you are in is left out, and items with order 99 or more (sign out) come last.

PropTypePurpose
blockbooleanA full-width row (avatar, name, chevrons) for a sidebar footer, as in the admin shell. Without it, an avatar button for a site header.
collapsedbooleanWith 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 the app's, so a component with the same name in your app replaces it. AccountHeaderRight is the block in the actions of the account heading and in the overview's summary card (signed-in email by default); override it to show anything else, such as a member or Frigear ID badge:

<!-- app/components/Account/AccountHeaderRight.vue -->
<template>
  <MyHeaderDetails />
</template>

With Menu installed, each enabled page adds an item. The account menu is the account navigation (and the overview's shortcuts); account.dropdown is AccountMenu. order sorts within the menu.

ItemMenuOrderPage
account ("Account")account.dropdown-99pages.index
account-overview ("Overview")account0pages.index
profileaccount and account.dropdown10pages.profile
securityaccount20pages.security
api-tokensaccount25pages.apiTokens
documentsaccount30pages.documents

Other modules slot into 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.

Page guard

With @frigear-nu/authorization installed, the module adds an authenticated: true route rule for /account and /account/** (and each /<locale> prefix), so every page under it, including those other modules add (membership, tickets, applications), is closed to guests. Set authorization.redirectTo to send guests to the login page instead of a 401 error. Without the authorization module the pages are not guarded and the module logs a warning.

Routes

None. The module registers no server routes.

Translations

The module registers da and en locale files under the account.* namespace (key groups title, menu.*, profile.*, documents.*, security.*; the full list is src/runtime/i18n/lang/en.json; the layout and menu use nav.*, appearance.*, overview.* and accountMenu). Without @nuxtjs/i18n the English defaults are used. Override strings in your app's i18n/locales/<code>.json, see overriding module strings:

{ "account": { "profile": { "title": "My details" } } }
Copyright © 2026