@bitbaum/accountkit 0.0.0-stage → 0.8.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/LICENSE +21 -0
- package/README.md +214 -2
- package/dist/AccountMenu.d.ts +76 -0
- package/dist/AccountMenu.d.ts.map +1 -0
- package/dist/AccountMenu.js +156 -0
- package/dist/AccountMenu.js.map +1 -0
- package/dist/SignInError.d.ts +50 -0
- package/dist/SignInError.d.ts.map +1 -0
- package/dist/SignInError.js +34 -0
- package/dist/SignInError.js.map +1 -0
- package/dist/auth/client.d.ts +19 -0
- package/dist/auth/client.d.ts.map +1 -0
- package/dist/auth/client.js +17 -0
- package/dist/auth/client.js.map +1 -0
- package/dist/auth/factor.d.ts +9 -0
- package/dist/auth/factor.d.ts.map +1 -0
- package/dist/auth/factor.js +107 -0
- package/dist/auth/factor.js.map +1 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/labels.d.ts +126 -0
- package/dist/auth/labels.d.ts.map +1 -0
- package/dist/auth/labels.js +143 -0
- package/dist/auth/labels.js.map +1 -0
- package/dist/auth/limits.d.ts +28 -0
- package/dist/auth/limits.d.ts.map +1 -0
- package/dist/auth/limits.js +90 -0
- package/dist/auth/limits.js.map +1 -0
- package/dist/auth/mail.d.ts +10 -0
- package/dist/auth/mail.d.ts.map +1 -0
- package/dist/auth/mail.js +45 -0
- package/dist/auth/mail.js.map +1 -0
- package/dist/auth/methods.d.ts +71 -0
- package/dist/auth/methods.d.ts.map +1 -0
- package/dist/auth/methods.js +92 -0
- package/dist/auth/methods.js.map +1 -0
- package/dist/auth/next.d.ts +73 -0
- package/dist/auth/next.d.ts.map +1 -0
- package/dist/auth/next.js +131 -0
- package/dist/auth/next.js.map +1 -0
- package/dist/auth/react.d.ts +178 -0
- package/dist/auth/react.d.ts.map +1 -0
- package/dist/auth/react.js +405 -0
- package/dist/auth/react.js.map +1 -0
- package/dist/auth/recovery.d.ts +17 -0
- package/dist/auth/recovery.d.ts.map +1 -0
- package/dist/auth/recovery.js +85 -0
- package/dist/auth/recovery.js.map +1 -0
- package/dist/auth/roles.d.ts +19 -0
- package/dist/auth/roles.d.ts.map +1 -0
- package/dist/auth/roles.js +29 -0
- package/dist/auth/roles.js.map +1 -0
- package/dist/auth/schema.d.ts +1923 -0
- package/dist/auth/schema.d.ts.map +1 -0
- package/dist/auth/schema.js +120 -0
- package/dist/auth/schema.js.map +1 -0
- package/dist/auth/server.d.ts +56 -0
- package/dist/auth/server.d.ts.map +1 -0
- package/dist/auth/server.js +230 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/auth/translations.d.ts +9 -0
- package/dist/auth/translations.d.ts.map +1 -0
- package/dist/auth/translations.js +629 -0
- package/dist/auth/translations.js.map +1 -0
- package/dist/identity.d.ts +50 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +76 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/orangecat.d.ts +333 -0
- package/dist/orangecat.d.ts.map +1 -0
- package/dist/orangecat.js +508 -0
- package/dist/orangecat.js.map +1 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +11 -0
- package/dist/paths.js.map +1 -0
- package/package.json +145 -4
- package/styles.css +408 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bitbaum
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,215 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bitbaum/accountkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One account for every bitbaum product: the account menu (who is signed in, where to go, how to sign out), and **Sign in with OrangeCat** (`@bitbaum/accountkit/orangecat`).
|
|
4
|
+
|
|
5
|
+
Each product had written its own — Loki's `AccountMenu`, OrangeCat's `UserProfileDropdown` — and each had learned half of what the other had. Loki took initials only from the name; OrangeCat never rebuilt a name from the email address; OrangeCat had arrow keys and a 44px target, Loki had neither; Loki's public pages had no menu at all, so a signed-in person could not find sign-out. This is the one menu, with both halves.
|
|
6
|
+
|
|
7
|
+
It knows nothing about how you sign in. NextAuth, Supabase, better-auth: you pass the person, the places, and a sign-out function.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Use the current verified installation command on the
|
|
12
|
+
[AccountKit package page](https://bitbaum.orangecat.ch/packages/accountkit/).
|
|
13
|
+
Fleet's [package registry](https://github.com/bitbaum/fleet/blob/main/registers/packages.json)
|
|
14
|
+
is the source for the verified artifact and install transport; npm may lag a
|
|
15
|
+
verified Git release. [Release history](CHANGELOG.md) distinguishes the
|
|
16
|
+
registration designs shipped in different versions.
|
|
17
|
+
|
|
18
|
+
`dist/` is committed, so the `github:` install needs no build step and no `allowBuilds` entry.
|
|
19
|
+
|
|
20
|
+
## Password recovery
|
|
21
|
+
|
|
22
|
+
`SignInForm` includes password recovery when `signInMethods(env, mail.configured)`
|
|
23
|
+
reports `passwordReset`. It uses the product's Better Auth client and sends the
|
|
24
|
+
reset link back to its existing `signInPath`, preserving the local `next` path.
|
|
25
|
+
That page must remain reachable when signed out and must render `SignInForm`
|
|
26
|
+
when the reset callback carries `token` or `error=INVALID_TOKEN`. The form removes
|
|
27
|
+
the token from browser history after reading it. Products with a separate reset
|
|
28
|
+
route can render `PasswordRecoveryForm` from `@bitbaum/accountkit/auth/react`.
|
|
29
|
+
|
|
30
|
+
The standard mail adapter is `createAuthMailer` from `@bitbaum/accountkit/auth/mail`:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createAuthMailer } from "@bitbaum/accountkit/auth/mail";
|
|
34
|
+
const mail = createAuthMailer({ site: { title: "Example", url: "https://example.ch" } });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Install its optional peer `@bitbaum/mail-kit`. The adapter reads deployment
|
|
38
|
+
configuration lazily, uses the fleet sender and the product's reply address,
|
|
39
|
+
and supports subject overrides. It rejects malformed or foreign-origin links
|
|
40
|
+
and never logs a token, address or credential. Configuration still needs a real
|
|
41
|
+
Resend key and working sender; this adapter does not claim delivery readiness.
|
|
42
|
+
|
|
43
|
+
The sender must actually deliver account mail: `createAuthRuntime({ mail })`
|
|
44
|
+
calls `mail.send("reset", to, url)`. A deployment with `mail.configured=false`
|
|
45
|
+
does not offer recovery and disables its server request handler. A no-op sender
|
|
46
|
+
does not implement recovery. `authLabelsFor(locale)` includes recovery words;
|
|
47
|
+
older product label overrides remain compatible through the English fallback.
|
|
48
|
+
|
|
49
|
+
The package checks the complete token/callback/reset journey against Postgres,
|
|
50
|
+
including account-existence parity, expiry, one-time use, session revocation and
|
|
51
|
+
external-redirect rejection. Its browser suite checks the forms at 320px, 390px
|
|
52
|
+
and desktop width. Consumers must also verify their own page wiring and delivery.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import "@bitbaum/accountkit/styles.css";
|
|
56
|
+
import { AccountMenu } from "@bitbaum/accountkit";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Use
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<AccountMenu
|
|
63
|
+
user={{ name: session.user.name, email: session.user.email, image: session.user.image }}
|
|
64
|
+
groups={[{ id: "nav", items: [{ id: "settings", label: "Settings", href: "/settings" }] }]}
|
|
65
|
+
extra={<ThemeToggle />}
|
|
66
|
+
onSignOut={() => signOut({ callbackUrl: "/sign-in" })}
|
|
67
|
+
renderLink={(props) => <Link {...props} />}
|
|
68
|
+
/>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- **`onSignOut`** may return a promise. While it runs the row says so; if it rejects, the menu stays open and says sign-out did not complete. A sign-out that fails silently is the one thing this menu must never do.
|
|
72
|
+
- **`renderLink`** keeps your router (Next `<Link>`); the default is a plain `<a>`.
|
|
73
|
+
- **`extra`** is for rows that are not places, such as a theme switch.
|
|
74
|
+
- **`labels`** translates every string: `labels={{ trigger: "Konto: {name}", signOut: "Abmelden" }}`.
|
|
75
|
+
|
|
76
|
+
## What is shared, and not configurable
|
|
77
|
+
|
|
78
|
+
- **The name is never built from the email.** No name: `@handle`. No handle: "Account".
|
|
79
|
+
- **Initials come from the name only.** No name and no photo: a person glyph.
|
|
80
|
+
- **A photo that fails to load** falls back to initials, then the glyph.
|
|
81
|
+
- **44px** trigger and rows. The panel never runs off a 320–390px screen.
|
|
82
|
+
- **Escape** closes and returns focus to the trigger, wherever focus was. **Arrows**, **Home** and **End** move between items. A click outside closes.
|
|
83
|
+
|
|
84
|
+
## Theming
|
|
85
|
+
|
|
86
|
+
Every colour, radius and font is an `--acct-*` variable. The defaults read `@bitbaum/design-tokens` at zero specificity, so apps on those tokens match with no work and an app's own `:root` always wins. An app whose tokens are full colours rather than HSL triplets (Loki's are OKLCH) maps them once:
|
|
87
|
+
|
|
88
|
+
```css
|
|
89
|
+
:root {
|
|
90
|
+
--acct-panel: var(--surface-overlay);
|
|
91
|
+
--acct-fg: var(--text-primary);
|
|
92
|
+
/* … */
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Accounts (`@bitbaum/accountkit/auth`)
|
|
97
|
+
|
|
98
|
+
Every product owns its accounts, and they are the same account system everywhere: email and password, a magic link, passkeys, two-factor codes, the shared bitbaum sign-in and Sign in with OrangeCat (each shown only when configured). Built on [better-auth](https://www.better-auth.com) 1.x, so a product writes none of it.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// lib/db/schema.ts — one identical migration in every product
|
|
102
|
+
export * from "@bitbaum/accountkit/auth/schema";
|
|
103
|
+
|
|
104
|
+
// lib/auth.ts
|
|
105
|
+
import { createAuthRuntime } from "@bitbaum/accountkit/auth/next";
|
|
106
|
+
export const { GET, POST, getSession, requireUser, authStatus, getAuth } = createAuthRuntime({
|
|
107
|
+
site: { url: "https://app.example.ch", title: "Example", slug: "example" },
|
|
108
|
+
mail: { configured, send }, // your mail-kit sender; never log the link
|
|
109
|
+
isDatabaseConfigured: (env) => Boolean(env.DATABASE_URL),
|
|
110
|
+
getDatabase: () => db, // Drizzle, built on first use
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// app/api/auth/[...all]/route.ts
|
|
114
|
+
export { GET, POST } from "@/lib/auth";
|
|
115
|
+
|
|
116
|
+
// lib/auth-client.ts
|
|
117
|
+
import { createAuthClient } from "better-auth/react";
|
|
118
|
+
import { authClientPlugins } from "@bitbaum/accountkit/auth/client";
|
|
119
|
+
export const authClient = createAuthClient({ plugins: authClientPlugins() });
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The screens are shared too (`@bitbaum/accountkit/auth/react`): `SignInForm`, `SignUpForm` and `AccountPanel`. They hold the logic and take the product's words (`labels`: `authLabelsFor(locale)` from `@bitbaum/accountkit/auth` has English, German, French, Italian, Romansh and Russian, so a product keeps no copy of its own; override `account.roles` if its roles have other names) and look (`classes`; without them the `acct-auth-*` classes in `styles.css` apply, on `--acct-*` variables):
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<SignInForm client={authClient} methods={signInMethods(process.env, mailConfigured)}
|
|
126
|
+
labels={myLabels} classes={{ button: "btn-primary", input: "field" }}
|
|
127
|
+
next="/de/matters" signInPath="/de/sign-in" signUpLink={<Link href="/sign-up">…</Link>} />
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A product whose rows were keyed on an OrangeCat actor id before it had accounts passes `legacyIdentityProvider: "orangecat"`: a person whose first way in was OrangeCat keeps that id as `user.actorId`, so nothing has to be migrated; everyone else's `actorId` is their user id.
|
|
131
|
+
|
|
132
|
+
What is the same everywhere, and not configurable per product: passwords of at least 8 characters and no other rule; a sign-up asks for an email and a password, nothing else; sessions of 14 days; no IP addresses stored; an address is never an identity (a new method joins an account only when the signed-in person links it); provider tokens are never kept; a sign-up cannot choose its role; one rate limiter keyed on the proxy's hop (limitkit), with a stricter bucket for anything that sends mail. `signInMethods(env, mailConfigured)` says which buttons to show, as booleans.
|
|
133
|
+
|
|
134
|
+
Needs `BETTER_AUTH_SECRET` (32+ characters; a product moving off next-auth may keep its `AUTH_SECRET`) and a database; without them every auth route answers 503 `accounts_unavailable` instead of crashing. Peer dependencies: `better-auth`, `@better-auth/passkey`, `drizzle-orm`, and `next` for `/auth/next`.
|
|
135
|
+
|
|
136
|
+
## Verify
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
pnpm run verify # format, lint, types, build, unit + render tests, real-browser checks
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The browser checks run the demo at 390px in light and dark: 44px targets, no sideways scroll with the menu open, Escape after a mouse open, arrow keys, outside click, a broken photo, an anonymous account, and a failing sign-out.
|
|
143
|
+
|
|
144
|
+
## Sign in with OrangeCat — `@bitbaum/accountkit/orangecat`
|
|
145
|
+
|
|
146
|
+
Nine apps wrote this themselves and each learned something the others had not. This is all of it, once. Server-only, no next-auth import (the types are structural), so it serves Auth.js v5 and next-auth v4.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { orangecatClient, orangecatProvider, syncOcSession } from "@bitbaum/accountkit/orangecat";
|
|
150
|
+
|
|
151
|
+
const orangecat = orangecatClient(); // ORANGECAT_OAUTH_CLIENT_ID / _SECRET / _ISSUER, or null
|
|
152
|
+
|
|
153
|
+
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
154
|
+
session: { strategy: "jwt" },
|
|
155
|
+
pages: { error: "/auth/error" },
|
|
156
|
+
providers: orangecat ? [orangecatProvider(orangecat)] : [],
|
|
157
|
+
callbacks: {
|
|
158
|
+
jwt: ({ token, account, profile }) => syncOcSession({ token, account, profile }, orangecat),
|
|
159
|
+
session: ({ session, token }) => ({ ...session, actorId: token.actorId }),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
What is not configurable, and why:
|
|
165
|
+
|
|
166
|
+
- **`client_secret_post` and PKCE.** OrangeCat's token endpoint accepts only `client_secret_post`; the Auth.js default is answered with a 400 that reads "client_id is required". PKCE is required even with a secret.
|
|
167
|
+
- **`profile()` has no `email`.** OrangeCat auto-confirms addresses, so `email_verified` proves nothing. With an `email`, Auth.js looks a new account up by it and either links (takeover) or refuses. The address rides along as `contactEmail`, contact data only. The identity is `sub` (`token.actorId`), never the email.
|
|
168
|
+
- **A revoked grant ends the session.** `syncOcSession` refreshes once the access token expires; `invalid_grant` drops `actorId` (read that, never `token.sub`, as "signed in"); any other failure keeps the session and retries in 60 s. Safe inside `jwt()` since orangecat#1212 (confidential clients keep their refresh token).
|
|
169
|
+
|
|
170
|
+
Entry: `signIn("orangecat", { redirectTo: returnPath(from) }, authorizationParams({ mode, email, provider }))` → `prompt=create`, `login_hint`, `idp_hint` (google, github — the providers OrangeCat has switched on). `returnPath` accepts only same-origin paths.
|
|
171
|
+
|
|
172
|
+
Apps with their own user table:
|
|
173
|
+
|
|
174
|
+
- `withOrangecatIdentity(adapter, store, { keepTokens })` — an OrangeCat sign-in resolves by `sub` alone; a new sub always creates a new user, stored with its OrangeCat address when free and `orangecat-<sub>@users.invalid` when taken. Linking an existing account is Auth.js's own path (sign in, then sign in with OrangeCat). `keepTokens: true` also writes the accounts row (for an app that calls OrangeCat on the person's behalf).
|
|
175
|
+
- `resolveOrangecatUser(store, identity)` — the same, for a callback (next-auth v4, no adapter). Survives the insert race.
|
|
176
|
+
- `decideOrangecatSignIn(input, policy)` — the pure rule, with `whenEmailTaken` / `whenNoEmail: "refuse"` for an app that prefers to send the person to connect from their profile.
|
|
177
|
+
- `mintLinkToken` / `readLinkToken` — evig's "Connect OrangeCat": a 10-minute signed cookie set from inside the session, so the identity that comes back attaches to that account and no other.
|
|
178
|
+
- `emailEarnsPromotion(provider)` — never ADMIN_EMAILS promotion for an OrangeCat login.
|
|
179
|
+
- `mayReceivePasswordReset({ email, hasPassword, orangecatLinked })` — no reset link for an account OrangeCat created, or for a placeholder.
|
|
180
|
+
|
|
181
|
+
### The error screen
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
import { SignInError } from "@bitbaum/accountkit";
|
|
185
|
+
|
|
186
|
+
// app/auth/error/page.tsx — Auth.js `pages.error`
|
|
187
|
+
<SignInError error={searchParams.error} retry={restartSignIn} retryFields={{ from }} home="/" />
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Import `@bitbaum/accountkit/styles.css`** (once, in the root layout or globals) and map the `--acct-*` variables onto the app's tokens, or the screen renders as bare text — solon shipped exactly that once, and no lint or typecheck can see it. Look at it at 390px before merging.
|
|
191
|
+
|
|
192
|
+
**Try again restarts sign-in** (a server action calling `signIn("orangecat", …)`, or a GET route that does). It never goes home, and it never prints the provider's error code. No hooks: renders in a server component.
|
|
193
|
+
|
|
194
|
+
MIT.
|
|
195
|
+
|
|
196
|
+
### Request-dependent sessions
|
|
197
|
+
|
|
198
|
+
`getSession()` is a server-component request API. It reads Next headers before
|
|
199
|
+
checking deployment readiness and lets framework rendering signals propagate.
|
|
200
|
+
A build without account secrets must not cache a signed-out page or redirect for
|
|
201
|
+
all future sessions. Calls outside a request are errors, not signed-out users.
|
|
202
|
+
|
|
203
|
+
### Second-factor policy
|
|
204
|
+
|
|
205
|
+
An enabled second factor applies to password, email-link, passkey and OIDC sign-in. The server reuses BetterAuth's native signed challenge, trusted-device validation, attempt limits and expiry. A primary credential alone does not leave an authenticated session while the challenge is pending.
|
|
206
|
+
|
|
207
|
+
Shared forms complete both JSON `twoFactorRedirect` responses and app-local `?twoFactor=1` redirects. Custom clients must handle those states and supply their local sign-in screen as `errorCallbackURL` for email/OIDC callbacks. `SignUpForm` accepts an optional `signInPath` for nonstandard route names; standard `/sign-up` routes infer the sibling `/sign-in`. Challenge URLs contain no bearer credential.
|
|
208
|
+
|
|
209
|
+
## Password entry
|
|
210
|
+
|
|
211
|
+
The shared registration form asks for password confirmation, preserves paste
|
|
212
|
+
and password-manager autofill, and offers a localized show/hide control for
|
|
213
|
+
both fields. A mismatch stays on the form and never calls registration. It
|
|
214
|
+
still asks for no real name. Passwordless methods keep their existing flow.
|
|
215
|
+
Products consume this behavior through the package rather than duplicating it.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type AccountUser } from "./identity.js";
|
|
3
|
+
/** A place to go. Rendered through `renderLink` so a Next app keeps <Link>. */
|
|
4
|
+
export type AccountLinkItem = {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
href: string;
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
/** Something to do. Closes the menu, then runs. */
|
|
11
|
+
export type AccountActionItem = {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
onSelect: () => void | Promise<unknown>;
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
};
|
|
17
|
+
export type AccountItem = AccountLinkItem | AccountActionItem;
|
|
18
|
+
/** Items are grouped; a divider separates groups. */
|
|
19
|
+
export type AccountGroup = {
|
|
20
|
+
id: string;
|
|
21
|
+
items: AccountItem[];
|
|
22
|
+
};
|
|
23
|
+
export type AccountMenuLabels = {
|
|
24
|
+
/** Accessible name of the trigger; "{name}" is replaced. */
|
|
25
|
+
trigger: string;
|
|
26
|
+
menu: string;
|
|
27
|
+
signOut: string;
|
|
28
|
+
signingOut: string;
|
|
29
|
+
signOutFailed: string;
|
|
30
|
+
};
|
|
31
|
+
export type RenderLinkProps = {
|
|
32
|
+
href: string;
|
|
33
|
+
className: string;
|
|
34
|
+
role: "menuitem";
|
|
35
|
+
tabIndex: -1;
|
|
36
|
+
onClick: () => void;
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
};
|
|
39
|
+
export type AccountMenuProps = {
|
|
40
|
+
user: AccountUser;
|
|
41
|
+
/**
|
|
42
|
+
* Called when the person signs out. May return a promise; while it is
|
|
43
|
+
* pending the row says so, and if it rejects the menu stays open and says
|
|
44
|
+
* the sign-out did not complete — a dead sign-out is the one failure this
|
|
45
|
+
* menu must never hide.
|
|
46
|
+
*/
|
|
47
|
+
onSignOut: () => void | Promise<unknown>;
|
|
48
|
+
groups?: AccountGroup[];
|
|
49
|
+
/**
|
|
50
|
+
* Rows that are not menu items — a theme switch, a status line. Rendered
|
|
51
|
+
* between the groups and Sign out, not navigable by arrow keys.
|
|
52
|
+
*/
|
|
53
|
+
extra?: ReactNode;
|
|
54
|
+
/** Defaults to a plain <a>. Pass `(p) => <Link {...p} />` in a Next app. */
|
|
55
|
+
renderLink?: (props: RenderLinkProps) => ReactNode;
|
|
56
|
+
labels?: Partial<AccountMenuLabels>;
|
|
57
|
+
/** Which edge of the trigger the panel aligns to. */
|
|
58
|
+
align?: "end" | "start";
|
|
59
|
+
className?: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The account menu: who is signed in, where to go, how to sign out.
|
|
63
|
+
*
|
|
64
|
+
* Identity, not navigation: the app's sidebar is for places in the product;
|
|
65
|
+
* this is for the person. Nothing should appear in both.
|
|
66
|
+
*
|
|
67
|
+
* Behaviour that is shared and NOT configurable, because each item shipped
|
|
68
|
+
* broken in at least one app:
|
|
69
|
+
* - a 44px trigger (Loki's was 36px, under the touch-target floor);
|
|
70
|
+
* - a photo that fails to load falls back to initials, then a glyph;
|
|
71
|
+
* - Escape closes and returns focus to the trigger; arrows, Home and End
|
|
72
|
+
* move between items; a click outside closes;
|
|
73
|
+
* - sign-out shows that it is happening, and says so when it fails.
|
|
74
|
+
*/
|
|
75
|
+
export declare function AccountMenu({ user, onSignOut, groups, extra, renderLink, labels: labelOverrides, align, className, }: AccountMenuProps): import("react").JSX.Element;
|
|
76
|
+
//# sourceMappingURL=AccountMenu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccountMenu.d.ts","sourceRoot":"","sources":["../src/AccountMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AAEvB,+EAA+E;AAC/E,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF,mDAAmD;AACnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE9D,qDAAqD;AACrD,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC;AAEhE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAUF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,CAAC,CAAC,CAAC;IACb,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACpC,qDAAqD;IACrD,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA+BF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,SAAS,EACT,MAAW,EACX,KAAK,EACL,UAAwB,EACxB,MAAM,EAAE,cAAc,EACtB,KAAa,EACb,SAAS,GACV,EAAE,gBAAgB,+BA8OlB"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment, useCallback, useEffect, useId, useRef, useState, } from "react";
|
|
4
|
+
import { displayNameFor, initialsFor, nextMenuIndex, secondaryLineFor, } from "./identity.js";
|
|
5
|
+
const DEFAULT_LABELS = {
|
|
6
|
+
trigger: "Account: {name}",
|
|
7
|
+
menu: "Account",
|
|
8
|
+
signOut: "Sign out",
|
|
9
|
+
signingOut: "Signing out…",
|
|
10
|
+
signOutFailed: "Sign-out did not complete. Try again.",
|
|
11
|
+
};
|
|
12
|
+
function defaultLink(props) {
|
|
13
|
+
return _jsx("a", { ...props });
|
|
14
|
+
}
|
|
15
|
+
function isLink(item) {
|
|
16
|
+
return "href" in item;
|
|
17
|
+
}
|
|
18
|
+
/** A person outline — drawn when there is neither a photo nor a name. */
|
|
19
|
+
function PersonGlyph() {
|
|
20
|
+
return (_jsxs("svg", { className: "acct-glyph", viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "12", cy: "8", r: "4" }), _jsx("path", { d: "M4 21a8 8 0 0 1 16 0" })] }));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The account menu: who is signed in, where to go, how to sign out.
|
|
24
|
+
*
|
|
25
|
+
* Identity, not navigation: the app's sidebar is for places in the product;
|
|
26
|
+
* this is for the person. Nothing should appear in both.
|
|
27
|
+
*
|
|
28
|
+
* Behaviour that is shared and NOT configurable, because each item shipped
|
|
29
|
+
* broken in at least one app:
|
|
30
|
+
* - a 44px trigger (Loki's was 36px, under the touch-target floor);
|
|
31
|
+
* - a photo that fails to load falls back to initials, then a glyph;
|
|
32
|
+
* - Escape closes and returns focus to the trigger; arrows, Home and End
|
|
33
|
+
* move between items; a click outside closes;
|
|
34
|
+
* - sign-out shows that it is happening, and says so when it fails.
|
|
35
|
+
*/
|
|
36
|
+
export function AccountMenu({ user, onSignOut, groups = [], extra, renderLink = defaultLink, labels: labelOverrides, align = "end", className, }) {
|
|
37
|
+
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
38
|
+
const [open, setOpen] = useState(false);
|
|
39
|
+
const [imageFailed, setImageFailed] = useState(false);
|
|
40
|
+
const [signingOut, setSigningOut] = useState(false);
|
|
41
|
+
const [signOutError, setSignOutError] = useState(false);
|
|
42
|
+
const rootRef = useRef(null);
|
|
43
|
+
const triggerRef = useRef(null);
|
|
44
|
+
const panelRef = useRef(null);
|
|
45
|
+
const panelId = useId();
|
|
46
|
+
const name = displayNameFor(user);
|
|
47
|
+
const initials = initialsFor(user);
|
|
48
|
+
const secondary = secondaryLineFor(user);
|
|
49
|
+
const showImage = Boolean(user.image) && !imageFailed;
|
|
50
|
+
// A new image URL deserves a fresh attempt.
|
|
51
|
+
useEffect(() => setImageFailed(false), [user.image]);
|
|
52
|
+
const items = useCallback(() => Array.from(panelRef.current?.querySelectorAll('[role="menuitem"]:not([disabled])') ?? []), []);
|
|
53
|
+
const close = useCallback((restoreFocus) => {
|
|
54
|
+
setOpen(false);
|
|
55
|
+
if (restoreFocus)
|
|
56
|
+
triggerRef.current?.focus();
|
|
57
|
+
}, []);
|
|
58
|
+
// A click anywhere outside closes — a listener, not a full-page scrim, so
|
|
59
|
+
// the click still reaches whatever it was aimed at.
|
|
60
|
+
//
|
|
61
|
+
// Escape is listened for on the DOCUMENT, not the panel. Opened with the
|
|
62
|
+
// mouse, focus stays on the trigger, so a panel-only handler never sees the
|
|
63
|
+
// key: the browser check caught Escape doing nothing after a click.
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!open)
|
|
66
|
+
return;
|
|
67
|
+
const onPointerDown = (event) => {
|
|
68
|
+
if (!rootRef.current?.contains(event.target))
|
|
69
|
+
close(false);
|
|
70
|
+
};
|
|
71
|
+
const onKeyDown = (event) => {
|
|
72
|
+
if (event.key === "Escape") {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
close(true);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
78
|
+
document.addEventListener("keydown", onKeyDown);
|
|
79
|
+
return () => {
|
|
80
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
81
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
82
|
+
};
|
|
83
|
+
}, [open, close]);
|
|
84
|
+
function focusAt(index) {
|
|
85
|
+
const list = items();
|
|
86
|
+
list[index]?.focus();
|
|
87
|
+
}
|
|
88
|
+
function openWithFocus(index) {
|
|
89
|
+
setOpen(true);
|
|
90
|
+
setSignOutError(false);
|
|
91
|
+
// After render: the panel does not exist yet.
|
|
92
|
+
requestAnimationFrame(() => {
|
|
93
|
+
const list = items();
|
|
94
|
+
focusAt(index === "first" ? 0 : list.length - 1);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function onTriggerKeyDown(event) {
|
|
98
|
+
if (event.key === "ArrowDown") {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
openWithFocus("first");
|
|
101
|
+
}
|
|
102
|
+
else if (event.key === "ArrowUp") {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
openWithFocus("last");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function onPanelKeyDown(event) {
|
|
108
|
+
// Escape is handled by the document listener above, wherever focus is.
|
|
109
|
+
if (event.key === "Tab") {
|
|
110
|
+
close(false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (event.key === "ArrowDown" ||
|
|
114
|
+
event.key === "ArrowUp" ||
|
|
115
|
+
event.key === "Home" ||
|
|
116
|
+
event.key === "End") {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
const list = items();
|
|
119
|
+
const current = list.indexOf(document.activeElement);
|
|
120
|
+
focusAt(nextMenuIndex(current, event.key, list.length));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function signOut() {
|
|
124
|
+
setSigningOut(true);
|
|
125
|
+
setSignOutError(false);
|
|
126
|
+
try {
|
|
127
|
+
await onSignOut();
|
|
128
|
+
// On success the app usually navigates away; closing covers the case
|
|
129
|
+
// where it does not.
|
|
130
|
+
setOpen(false);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
setSignOutError(true);
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
setSigningOut(false);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return (_jsxs("div", { ref: rootRef, className: ["acct", className].filter(Boolean).join(" "), "data-align": align, children: [_jsx("button", { ref: triggerRef, type: "button", className: "acct-trigger", "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": open ? panelId : undefined, "aria-label": labels.trigger.replace("{name}", name), title: name, onClick: () => {
|
|
140
|
+
setOpen((v) => !v);
|
|
141
|
+
setSignOutError(false);
|
|
142
|
+
}, onKeyDown: onTriggerKeyDown, children: showImage ? (_jsx("img", { className: "acct-avatar", src: user.image ?? undefined, alt: "", onError: () => setImageFailed(true) })) : initials ? (_jsx("span", { className: "acct-initials", "aria-hidden": "true", children: initials })) : (_jsx(PersonGlyph, {})) }), open && (_jsxs("div", { ref: panelRef, id: panelId, className: "acct-panel", role: "menu", "aria-label": labels.menu, onKeyDown: onPanelKeyDown, children: [_jsxs("div", { className: "acct-identity", children: [_jsx("p", { className: "acct-name", children: name }), secondary && _jsx("p", { className: "acct-secondary", children: secondary })] }), groups
|
|
143
|
+
.filter((g) => g.items.length > 0)
|
|
144
|
+
.map((group) => (_jsx("div", { className: "acct-group", role: "group", children: group.items.map((item) => isLink(item) ? (_jsx(Fragment, { children: renderLink({
|
|
145
|
+
href: item.href,
|
|
146
|
+
className: "acct-row",
|
|
147
|
+
role: "menuitem",
|
|
148
|
+
tabIndex: -1,
|
|
149
|
+
onClick: () => close(false),
|
|
150
|
+
children: (_jsxs(_Fragment, { children: [item.icon && (_jsx("span", { className: "acct-icon", children: item.icon })), _jsx("span", { children: item.label })] })),
|
|
151
|
+
}) }, item.id)) : (_jsxs("button", { type: "button", className: "acct-row", role: "menuitem", tabIndex: -1, onClick: () => {
|
|
152
|
+
close(false);
|
|
153
|
+
void item.onSelect();
|
|
154
|
+
}, children: [item.icon && (_jsx("span", { className: "acct-icon", children: item.icon })), _jsx("span", { children: item.label })] }, item.id))) }, group.id))), extra && _jsx("div", { className: "acct-group acct-extra", children: extra }), _jsxs("div", { className: "acct-group", children: [_jsx("button", { type: "button", className: "acct-row acct-signout", role: "menuitem", tabIndex: -1, disabled: signingOut, "aria-busy": signingOut, onClick: () => void signOut(), children: _jsx("span", { children: signingOut ? labels.signingOut : labels.signOut }) }), signOutError && (_jsx("p", { className: "acct-error", role: "alert", children: labels.signOutFailed }))] })] }))] }));
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=AccountMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccountMenu.js","sourceRoot":"","sources":["../src/AccountMenu.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Where Auth.js sends a sign-in that did not finish (`pages.error`).
|
|
4
|
+
*
|
|
5
|
+
* Every app had a different one: loki, skif and substrata showed Auth.js's
|
|
6
|
+
* bare "Error" page; heidi's "Try again" went to the home page, which is not
|
|
7
|
+
* trying again. Solon's was right, and this is it: "Try again" STARTS the
|
|
8
|
+
* sign-in again, directly, without another screen in between.
|
|
9
|
+
*
|
|
10
|
+
* It never prints the provider's error code. The codes mean nothing to a
|
|
11
|
+
* visitor and are a gift to anyone probing the endpoint; the operator's copy
|
|
12
|
+
* belongs in the server log.
|
|
13
|
+
*
|
|
14
|
+
* No hooks, no "use client": it renders in a server component, and `retry`
|
|
15
|
+
* may be a server action.
|
|
16
|
+
*/
|
|
17
|
+
export type SignInErrorKind = "failed" | "denied" | "configuration";
|
|
18
|
+
/** Auth.js's `?error=` code → what the person is told. Anything unknown is "failed". */
|
|
19
|
+
export declare function signInErrorKind(error: string | null | undefined): SignInErrorKind;
|
|
20
|
+
export type SignInErrorLabels = {
|
|
21
|
+
kicker: string;
|
|
22
|
+
failedTitle: string;
|
|
23
|
+
failedBody: string;
|
|
24
|
+
deniedTitle: string;
|
|
25
|
+
deniedBody: string;
|
|
26
|
+
configurationTitle: string;
|
|
27
|
+
configurationBody: string;
|
|
28
|
+
tryAgain: string;
|
|
29
|
+
home: string;
|
|
30
|
+
};
|
|
31
|
+
export type SignInErrorProps = {
|
|
32
|
+
/** Auth.js's `?error=` search param, as received. Never shown. */
|
|
33
|
+
error?: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* How "Try again" restarts sign-in: a server action that calls
|
|
36
|
+
* `signIn("orangecat", …)`, or the URL of a GET route that does.
|
|
37
|
+
* Never the home page.
|
|
38
|
+
*/
|
|
39
|
+
retry: string | ((formData: FormData) => void | Promise<void>);
|
|
40
|
+
/** Hidden fields posted with a server-action retry (`from`, `locale`). */
|
|
41
|
+
retryFields?: Record<string, string>;
|
|
42
|
+
/** Where "Back to the start" goes. */
|
|
43
|
+
home?: string;
|
|
44
|
+
/** More ways on, shown first — e.g. "Add an email on OrangeCat" for a denial. */
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
labels?: Partial<SignInErrorLabels>;
|
|
47
|
+
className?: string;
|
|
48
|
+
};
|
|
49
|
+
export declare function SignInError({ error, retry, retryFields, home, children, labels: overrides, className, }: SignInErrorProps): import("react").JSX.Element;
|
|
50
|
+
//# sourceMappingURL=SignInError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignInError.d.ts","sourceRoot":"","sources":["../src/SignInError.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,GAAG,eAAe,CAAC;AAEpE,wFAAwF;AACxF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,eAAe,CAIjB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAiBF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/D,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,WAAW,EACX,IAAU,EACV,QAAQ,EACR,MAAM,EAAE,SAAS,EACjB,SAAS,GACV,EAAE,gBAAgB,+BA6ClB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Auth.js's `?error=` code → what the person is told. Anything unknown is "failed". */
|
|
3
|
+
export function signInErrorKind(error) {
|
|
4
|
+
if (error === "AccessDenied")
|
|
5
|
+
return "denied";
|
|
6
|
+
if (error === "Configuration")
|
|
7
|
+
return "configuration";
|
|
8
|
+
return "failed";
|
|
9
|
+
}
|
|
10
|
+
const DEFAULT_LABELS = {
|
|
11
|
+
kicker: "Sign in",
|
|
12
|
+
failedTitle: "Sign-in did not finish",
|
|
13
|
+
failedBody: "Nothing was changed. Start again — it usually works the second time. If it keeps failing, OrangeCat may be briefly unavailable.",
|
|
14
|
+
deniedTitle: "This account cannot sign in here",
|
|
15
|
+
deniedBody: "OrangeCat signed you in, but this app could not accept the account.",
|
|
16
|
+
configurationTitle: "Sign-in is not available right now",
|
|
17
|
+
configurationBody: "Sign-in is not set up on this server yet. Nothing you did caused this.",
|
|
18
|
+
tryAgain: "Try again",
|
|
19
|
+
home: "Back to the start",
|
|
20
|
+
};
|
|
21
|
+
export function SignInError({ error, retry, retryFields, home = "/", children, labels: overrides, className, }) {
|
|
22
|
+
const labels = { ...DEFAULT_LABELS, ...overrides };
|
|
23
|
+
const kind = signInErrorKind(error);
|
|
24
|
+
const title = labels[`${kind}Title`];
|
|
25
|
+
const body = labels[`${kind}Body`];
|
|
26
|
+
// Retrying a server with no client configured fails the same way again.
|
|
27
|
+
const canRetry = kind !== "configuration";
|
|
28
|
+
// A denial is answered elsewhere first (the app's children), so the retry
|
|
29
|
+
// steps back to a secondary button there.
|
|
30
|
+
const retryClass = `acct-signin-button ${kind === "denied" && children ? "is-secondary" : ""}`;
|
|
31
|
+
return (_jsxs("section", { className: `acct-signin-error ${className ?? ""}`.trim(), "aria-labelledby": "acct-signin-error-title", "data-kind": kind, children: [_jsx("p", { className: "acct-signin-kicker", children: labels.kicker }), _jsx("h1", { id: "acct-signin-error-title", className: "acct-signin-title", children: title }), _jsx("p", { className: "acct-signin-body", children: body }), _jsxs("div", { className: "acct-signin-actions", children: [children, canRetry &&
|
|
32
|
+
(typeof retry === "string" ? (_jsx("a", { className: retryClass, href: retry, children: labels.tryAgain })) : (_jsxs("form", { action: retry, children: [Object.entries(retryFields ?? {}).map(([name, value]) => (_jsx("input", { type: "hidden", name: name, value: value }, name))), _jsx("button", { type: "submit", className: retryClass, children: labels.tryAgain })] }))), _jsx("a", { className: "acct-signin-home", href: home, children: labels.home })] })] }));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=SignInError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignInError.js","sourceRoot":"","sources":["../src/SignInError.tsx"],"names":[],"mappings":";AAoBA,wFAAwF;AACxF,MAAM,UAAU,eAAe,CAC7B,KAAgC;IAEhC,IAAI,KAAK,KAAK,cAAc;QAAE,OAAO,QAAQ,CAAC;IAC9C,IAAI,KAAK,KAAK,eAAe;QAAE,OAAO,eAAe,CAAC;IACtD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAcD,MAAM,cAAc,GAAsB;IACxC,MAAM,EAAE,SAAS;IACjB,WAAW,EAAE,wBAAwB;IACrC,UAAU,EACR,iIAAiI;IACnI,WAAW,EAAE,kCAAkC;IAC/C,UAAU,EACR,qEAAqE;IACvE,kBAAkB,EAAE,oCAAoC;IACxD,iBAAiB,EACf,wEAAwE;IAC1E,QAAQ,EAAE,WAAW;IACrB,IAAI,EAAE,mBAAmB;CAC1B,CAAC;AAqBF,MAAM,UAAU,WAAW,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,WAAW,EACX,IAAI,GAAG,GAAG,EACV,QAAQ,EACR,MAAM,EAAE,SAAS,EACjB,SAAS,GACQ;IACjB,MAAM,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,SAAS,EAAE,CAAC;IACnD,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC;IACnC,wEAAwE;IACxE,MAAM,QAAQ,GAAG,IAAI,KAAK,eAAe,CAAC;IAC1C,0EAA0E;IAC1E,0CAA0C;IAC1C,MAAM,UAAU,GAAG,sBAAsB,IAAI,KAAK,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAE/F,OAAO,CACL,mBACE,SAAS,EAAE,qBAAqB,SAAS,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,qBACxC,yBAAyB,eAC9B,IAAI,aAEf,YAAG,SAAS,EAAC,oBAAoB,YAAE,MAAM,CAAC,MAAM,GAAK,EACrD,aAAI,EAAE,EAAC,yBAAyB,EAAC,SAAS,EAAC,mBAAmB,YAC3D,KAAK,GACH,EACL,YAAG,SAAS,EAAC,kBAAkB,YAAE,IAAI,GAAK,EAC1C,eAAK,SAAS,EAAC,qBAAqB,aACjC,QAAQ,EACR,QAAQ;wBACP,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAC3B,YAAG,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,YAClC,MAAM,CAAC,QAAQ,GACd,CACL,CAAC,CAAC,CAAC,CACF,gBAAM,MAAM,EAAE,KAAK,aAChB,MAAM,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CACxD,gBAAkB,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,IAA5C,IAAI,CAA4C,CAC7D,CAAC,EACF,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,UAAU,YACxC,MAAM,CAAC,QAAQ,GACT,IACJ,CACR,CAAC,EACJ,YAAG,SAAS,EAAC,kBAAkB,EAAC,IAAI,EAAE,IAAI,YACvC,MAAM,CAAC,IAAI,GACV,IACA,IACE,CACX,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { passkeyClient } from "@better-auth/passkey/client";
|
|
2
|
+
import { magicLinkClient, twoFactorClient } from "better-auth/client/plugins";
|
|
3
|
+
/**
|
|
4
|
+
* The plugins the browser client needs, the same in every product:
|
|
5
|
+
*
|
|
6
|
+
* import { createAuthClient } from "better-auth/react";
|
|
7
|
+
* import { authClientPlugins } from "@bitbaum/accountkit/auth/client";
|
|
8
|
+
* export const authClient = createAuthClient({ plugins: authClientPlugins() });
|
|
9
|
+
*
|
|
10
|
+
* The client itself is created by the product, because its fully inferred type
|
|
11
|
+
* cannot be written into a published declaration file. It talks only to this
|
|
12
|
+
* origin (/api/auth), which is all a strict CSP's `connect-src 'self'` allows.
|
|
13
|
+
*/
|
|
14
|
+
export declare function authClientPlugins(): [
|
|
15
|
+
ReturnType<typeof twoFactorClient>,
|
|
16
|
+
ReturnType<typeof passkeyClient>,
|
|
17
|
+
ReturnType<typeof magicLinkClient>
|
|
18
|
+
];
|
|
19
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/auth/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE9E;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,IAAI;IACnC,UAAU,CAAC,OAAO,eAAe,CAAC;IAClC,UAAU,CAAC,OAAO,aAAa,CAAC;IAChC,UAAU,CAAC,OAAO,eAAe,CAAC;CACnC,CAEA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { passkeyClient } from "@better-auth/passkey/client";
|
|
2
|
+
import { magicLinkClient, twoFactorClient } from "better-auth/client/plugins";
|
|
3
|
+
/**
|
|
4
|
+
* The plugins the browser client needs, the same in every product:
|
|
5
|
+
*
|
|
6
|
+
* import { createAuthClient } from "better-auth/react";
|
|
7
|
+
* import { authClientPlugins } from "@bitbaum/accountkit/auth/client";
|
|
8
|
+
* export const authClient = createAuthClient({ plugins: authClientPlugins() });
|
|
9
|
+
*
|
|
10
|
+
* The client itself is created by the product, because its fully inferred type
|
|
11
|
+
* cannot be written into a published declaration file. It talks only to this
|
|
12
|
+
* origin (/api/auth), which is all a strict CSP's `connect-src 'self'` allows.
|
|
13
|
+
*/
|
|
14
|
+
export function authClientPlugins() {
|
|
15
|
+
return [twoFactorClient(), passkeyClient(), magicLinkClient()];
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/auth/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB;IAK/B,OAAO,CAAC,eAAe,EAAE,EAAE,aAAa,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC;AACjE,CAAC"}
|