@adonis-agora/authkit-react 0.14.0 → 0.15.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/build/index.d.ts CHANGED
@@ -29,6 +29,10 @@ export { jsonRequest, useResource } from "./src/hooks/use_resource.js";
29
29
  export type { ResourceState } from "./src/hooks/use_resource.js";
30
30
  export { usePasskeyAutofill } from "./src/hooks/use_passkey_autofill.js";
31
31
  export type { UsePasskeyAutofillOptions } from "./src/hooks/use_passkey_autofill.js";
32
+ export { usePasskeyLogin } from "./src/hooks/use_passkey_login.js";
33
+ export type { UsePasskeyLoginOptions, UsePasskeyLoginResult, } from "./src/hooks/use_passkey_login.js";
34
+ export { authenticatePasskey, submitPasskeyVerification, loadStartAuthentication, } from "./src/passkey/authenticate.js";
35
+ export type { AuthenticatePasskeyOptions, SubmitPasskeyVerificationOptions, PasskeyCeremonyDeps, StartAuthenticationFn, } from "./src/passkey/authenticate.js";
32
36
  export { usePasswordStrength, heuristicScorer, } from "./src/hooks/use_password_strength.js";
33
37
  export type { PasswordStrengthScore, PasswordStrengthResult, PasswordScorer, UsePasswordStrengthOptions, } from "./src/hooks/use_password_strength.js";
34
38
  export { deriveInitials, currentUrl } from "./src/utils.js";
@@ -52,6 +56,8 @@ export { AuthorizedApps } from "./src/components/authorized_apps.js";
52
56
  export type { AuthorizedAppsProps } from "./src/components/authorized_apps.js";
53
57
  export { PasswordStrengthMeter } from "./src/components/password_strength_meter.js";
54
58
  export type { PasswordStrengthMeterProps } from "./src/components/password_strength_meter.js";
59
+ export { PasskeyButton } from "./src/components/passkey_button.js";
60
+ export type { PasskeyButtonProps } from "./src/components/passkey_button.js";
55
61
  export { OrganizationSwitcher } from "./src/components/organization_switcher.js";
56
62
  export type { OrganizationSwitcherProps } from "./src/components/organization_switcher.js";
57
63
  export { OrganizationProfile } from "./src/components/organization_profile.js";
package/build/index.js CHANGED
@@ -14,6 +14,8 @@ export { useSwitchOrganization } from "./src/hooks/use_switch_organization.js";
14
14
  export { useOrgInvitations } from "./src/hooks/use_org_invitations.js";
15
15
  export { jsonRequest, useResource } from "./src/hooks/use_resource.js";
16
16
  export { usePasskeyAutofill } from "./src/hooks/use_passkey_autofill.js";
17
+ export { usePasskeyLogin } from "./src/hooks/use_passkey_login.js";
18
+ export { authenticatePasskey, submitPasskeyVerification, loadStartAuthentication, } from "./src/passkey/authenticate.js";
17
19
  export { usePasswordStrength, heuristicScorer, } from "./src/hooks/use_password_strength.js";
18
20
  export { deriveInitials, currentUrl } from "./src/utils.js";
19
21
  export { Authenticated, Guest } from "./src/components/authenticated.js";
@@ -26,6 +28,7 @@ export { UserButton } from "./src/components/user_button.js";
26
28
  export { UserProfile } from "./src/components/user_profile.js";
27
29
  export { AuthorizedApps } from "./src/components/authorized_apps.js";
28
30
  export { PasswordStrengthMeter } from "./src/components/password_strength_meter.js";
31
+ export { PasskeyButton } from "./src/components/passkey_button.js";
29
32
  export { OrganizationSwitcher } from "./src/components/organization_switcher.js";
30
33
  export { OrganizationProfile } from "./src/components/organization_profile.js";
31
34
  export { KeyRotation } from "./src/components/key_rotation.js";
@@ -0,0 +1,11 @@
1
+ import { type ButtonHTMLAttributes, type ReactNode } from "react";
2
+ export interface PasskeyButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onClick"> {
3
+ optionsUrl: string;
4
+ verifyUrl: string;
5
+ csrfToken?: string;
6
+ children?: ReactNode;
7
+ errorContent?: ReactNode;
8
+ }
9
+ export declare function PasskeyButton({ optionsUrl, verifyUrl, csrfToken, children, errorContent, className, ...rest }: PasskeyButtonProps): import("react").DetailedReactHTMLElement<{
10
+ className: string;
11
+ }, HTMLElement>;
@@ -0,0 +1,24 @@
1
+ import { createElement, } from "react";
2
+ import { usePasskeyLogin } from "../hooks/use_passkey_login.js";
3
+ const DEFAULT_ERROR = "Não foi possível autenticar com a passkey. Tente novamente.";
4
+ export function PasskeyButton({ optionsUrl, verifyUrl, csrfToken, children = "Entrar com passkey", errorContent, className, ...rest }) {
5
+ const { authenticate, busy, failed } = usePasskeyLogin({
6
+ optionsUrl,
7
+ verifyUrl,
8
+ csrfToken,
9
+ });
10
+ const button = createElement("button", {
11
+ type: "button",
12
+ className: ["authkit-button", "authkit-button--ghost", className]
13
+ .filter(Boolean)
14
+ .join(" "),
15
+ onClick: () => {
16
+ void authenticate();
17
+ },
18
+ disabled: busy,
19
+ ...rest,
20
+ }, children);
21
+ if (!failed || errorContent === null)
22
+ return button;
23
+ return createElement("div", { className: "authkit-passkey" }, button, createElement("p", { className: "authkit-passkey__error" }, errorContent ?? DEFAULT_ERROR));
24
+ }
@@ -13,6 +13,7 @@ export declare function SignInButton({ children, returnTo, showWhenAuthenticated
13
13
  style?: import("react").CSSProperties | undefined;
14
14
  title?: string | undefined | undefined;
15
15
  role?: import("react").AriaRole | undefined;
16
+ hidden?: boolean | undefined | undefined;
16
17
  resource?: string | undefined | undefined;
17
18
  disabled?: boolean | undefined | undefined;
18
19
  formAction?: string | ((formData: FormData) => void | Promise<void>) | undefined;
@@ -33,7 +34,6 @@ export declare function SignInButton({ children, returnTo, showWhenAuthenticated
33
34
  dir?: string | undefined | undefined;
34
35
  draggable?: (boolean | "true" | "false") | undefined;
35
36
  enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
36
- hidden?: boolean | undefined | undefined;
37
37
  lang?: string | undefined | undefined;
38
38
  nonce?: string | undefined | undefined;
39
39
  spellCheck?: (boolean | "true" | "false") | undefined;
@@ -12,6 +12,7 @@ export declare function SignOutButton({ children, returnTo, className, ...rest }
12
12
  style?: import("react").CSSProperties | undefined;
13
13
  title?: string | undefined | undefined;
14
14
  role?: import("react").AriaRole | undefined;
15
+ hidden?: boolean | undefined | undefined;
15
16
  resource?: string | undefined | undefined;
16
17
  disabled?: boolean | undefined | undefined;
17
18
  formAction?: string | ((formData: FormData) => void | Promise<void>) | undefined;
@@ -32,7 +33,6 @@ export declare function SignOutButton({ children, returnTo, className, ...rest }
32
33
  dir?: string | undefined | undefined;
33
34
  draggable?: (boolean | "true" | "false") | undefined;
34
35
  enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined | undefined;
35
- hidden?: boolean | undefined | undefined;
36
36
  lang?: string | undefined | undefined;
37
37
  nonce?: string | undefined | undefined;
38
38
  spellCheck?: (boolean | "true" | "false") | undefined;
@@ -1,19 +1,12 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
- import { useEffect, useRef } from 'react';
1
+ import { useEffect, useRef } from "react";
2
+ import { loadStartAuthentication, } from "../passkey/authenticate.js";
10
3
  export function usePasskeyAutofill(options) {
11
- const { optionsUrl, verifyUrl, onSuccess, csrfToken, enabled = true } = options;
4
+ const { optionsUrl, verifyUrl, onSuccess, csrfToken, enabled = true, } = options;
12
5
  const abortRef = useRef(null);
13
6
  useEffect(() => {
14
7
  if (!enabled)
15
8
  return;
16
- if (typeof window === 'undefined' || typeof navigator === 'undefined')
9
+ if (typeof window === "undefined" || typeof navigator === "undefined")
17
10
  return;
18
11
  if (!window.PublicKeyCredential)
19
12
  return;
@@ -22,32 +15,28 @@ export function usePasskeyAutofill(options) {
22
15
  abortRef.current = ac;
23
16
  const run = async () => {
24
17
  try {
25
- const supported = typeof PublicKeyCredential.isConditionalMediationAvailable === 'function'
18
+ const supported = typeof PublicKeyCredential
19
+ .isConditionalMediationAvailable === "function"
26
20
  ? await PublicKeyCredential.isConditionalMediationAvailable()
27
21
  : false;
28
22
  if (!supported || cancelled)
29
23
  return;
30
24
  let startAuthentication;
31
25
  try {
32
- const mod = await import(__rewriteRelativeImportExtension('@simplewebauthn/browser'));
33
- startAuthentication = mod.startAuthentication;
26
+ startAuthentication = await loadStartAuthentication();
34
27
  }
35
28
  catch {
36
- try {
37
- const mod = await import(__rewriteRelativeImportExtension('https://cdn.jsdelivr.net/npm/@simplewebauthn/browser@13/dist/bundle/index.js'));
38
- startAuthentication = mod.startAuthentication;
39
- }
40
- catch {
41
- return;
42
- }
29
+ return;
43
30
  }
44
- if (!startAuthentication || cancelled)
31
+ if (cancelled)
45
32
  return;
46
- const headers = { 'content-type': 'application/json' };
33
+ const headers = {
34
+ "content-type": "application/json",
35
+ };
47
36
  if (csrfToken)
48
- headers['x-csrf-token'] = csrfToken;
37
+ headers["x-csrf-token"] = csrfToken;
49
38
  const optRes = await fetch(optionsUrl, {
50
- method: 'POST',
39
+ method: "POST",
51
40
  headers,
52
41
  body: JSON.stringify({}),
53
42
  signal: ac.signal,
@@ -58,7 +47,11 @@ export function usePasskeyAutofill(options) {
58
47
  delete optionsJSON._discoverable;
59
48
  if (cancelled)
60
49
  return;
61
- const assertion = await startAuthentication({ optionsJSON, useBrowserAutofill: true, verifyBrowserAutofillInput: true }, ac.signal);
50
+ const assertion = await startAuthentication({
51
+ optionsJSON,
52
+ useBrowserAutofill: true,
53
+ verifyBrowserAutofillInput: true,
54
+ }, ac.signal);
62
55
  if (cancelled)
63
56
  return;
64
57
  onSuccess(JSON.stringify(assertion));
@@ -0,0 +1,12 @@
1
+ export interface UsePasskeyLoginOptions {
2
+ optionsUrl: string;
3
+ verifyUrl: string;
4
+ csrfToken?: string;
5
+ onSuccess?: (assertion: string) => void;
6
+ }
7
+ export interface UsePasskeyLoginResult {
8
+ authenticate: () => Promise<void>;
9
+ busy: boolean;
10
+ failed: boolean;
11
+ }
12
+ export declare function usePasskeyLogin(options: UsePasskeyLoginOptions): UsePasskeyLoginResult;
@@ -0,0 +1,25 @@
1
+ import { useCallback, useState } from "react";
2
+ import { authenticatePasskey, submitPasskeyVerification, } from "../passkey/authenticate.js";
3
+ export function usePasskeyLogin(options) {
4
+ const { optionsUrl, verifyUrl, csrfToken, onSuccess } = options;
5
+ const [busy, setBusy] = useState(false);
6
+ const [failed, setFailed] = useState(false);
7
+ const authenticate = useCallback(async () => {
8
+ setFailed(false);
9
+ setBusy(true);
10
+ try {
11
+ const assertion = await authenticatePasskey({ optionsUrl, csrfToken });
12
+ if (onSuccess) {
13
+ onSuccess(assertion);
14
+ setBusy(false);
15
+ return;
16
+ }
17
+ submitPasskeyVerification({ verifyUrl, assertion, csrfToken });
18
+ }
19
+ catch {
20
+ setFailed(true);
21
+ setBusy(false);
22
+ }
23
+ }, [optionsUrl, verifyUrl, csrfToken, onSuccess]);
24
+ return { authenticate, busy, failed };
25
+ }
@@ -0,0 +1,22 @@
1
+ export type StartAuthenticationFn = (opts: {
2
+ optionsJSON: unknown;
3
+ useBrowserAutofill?: boolean;
4
+ verifyBrowserAutofillInput?: boolean;
5
+ }, signal?: AbortSignal) => Promise<unknown>;
6
+ export declare function loadStartAuthentication(): Promise<StartAuthenticationFn>;
7
+ export interface AuthenticatePasskeyOptions {
8
+ optionsUrl: string;
9
+ csrfToken?: string;
10
+ signal?: AbortSignal;
11
+ }
12
+ export interface PasskeyCeremonyDeps {
13
+ fetch?: typeof globalThis.fetch;
14
+ loadStartAuthentication?: () => Promise<StartAuthenticationFn>;
15
+ }
16
+ export declare function authenticatePasskey(options: AuthenticatePasskeyOptions, deps?: PasskeyCeremonyDeps): Promise<string>;
17
+ export interface SubmitPasskeyVerificationOptions {
18
+ verifyUrl: string;
19
+ assertion: string;
20
+ csrfToken?: string;
21
+ }
22
+ export declare function submitPasskeyVerification(options: SubmitPasskeyVerificationOptions): void;
@@ -0,0 +1,63 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ const CDN_URL = "https://cdn.jsdelivr.net/npm/@simplewebauthn/browser@13/dist/bundle/index.js";
10
+ export async function loadStartAuthentication() {
11
+ try {
12
+ const mod = await import(__rewriteRelativeImportExtension("@simplewebauthn/browser"));
13
+ return mod.startAuthentication;
14
+ }
15
+ catch {
16
+ const mod = await import(__rewriteRelativeImportExtension(CDN_URL));
17
+ return mod.startAuthentication;
18
+ }
19
+ }
20
+ export async function authenticatePasskey(options, deps = {}) {
21
+ const { optionsUrl, csrfToken, signal } = options;
22
+ const doFetch = deps.fetch ?? globalThis.fetch;
23
+ const loadFn = deps.loadStartAuthentication ?? loadStartAuthentication;
24
+ const headers = {
25
+ "content-type": "application/json",
26
+ };
27
+ if (csrfToken)
28
+ headers["x-csrf-token"] = csrfToken;
29
+ const res = await doFetch(optionsUrl, {
30
+ method: "POST",
31
+ headers,
32
+ body: JSON.stringify({}),
33
+ signal,
34
+ });
35
+ if (!res.ok) {
36
+ throw new Error(`passkey options request failed: ${res.status}`);
37
+ }
38
+ const optionsJSON = await res.json();
39
+ const startAuthentication = await loadFn();
40
+ const assertion = await startAuthentication({ optionsJSON }, signal);
41
+ return JSON.stringify(assertion);
42
+ }
43
+ export function submitPasskeyVerification(options) {
44
+ const { verifyUrl, assertion, csrfToken } = options;
45
+ if (typeof document === "undefined")
46
+ return;
47
+ const form = document.createElement("form");
48
+ form.method = "POST";
49
+ form.action = verifyUrl;
50
+ form.hidden = true;
51
+ if (csrfToken)
52
+ form.appendChild(hiddenInput("_csrf", csrfToken));
53
+ form.appendChild(hiddenInput("response", assertion));
54
+ document.body.appendChild(form);
55
+ form.submit();
56
+ }
57
+ function hiddenInput(name, value) {
58
+ const input = document.createElement("input");
59
+ input.type = "hidden";
60
+ input.name = name;
61
+ input.value = value;
62
+ return input;
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-react",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Frontend ergonomics over AuthKit for AdonisJS + Inertia + React apps: a typed useAuth() hook, role-gating hooks and gating components.",
5
5
  "license": "MIT",
6
6
  "author": "dudousxd",
package/styles.css CHANGED
@@ -47,6 +47,17 @@
47
47
  border-color: var(--authkit-border);
48
48
  }
49
49
 
50
+ .authkit-passkey {
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: var(--authkit-gap);
54
+ }
55
+ .authkit-passkey__error {
56
+ margin: 0;
57
+ font-size: 0.85rem;
58
+ color: var(--authkit-danger);
59
+ }
60
+
50
61
  .authkit-avatar {
51
62
  display: inline-flex;
52
63
  align-items: center;
@@ -206,16 +217,16 @@
206
217
  transition: background-color 0.15s ease;
207
218
  }
208
219
  /* Cor por score, do mais fraco ao mais forte (via data-score no bar). */
209
- .authkit-strength__bar[data-score='1'] .authkit-strength__segment--filled {
220
+ .authkit-strength__bar[data-score="1"] .authkit-strength__segment--filled {
210
221
  background: var(--authkit-danger);
211
222
  }
212
- .authkit-strength__bar[data-score='2'] .authkit-strength__segment--filled {
223
+ .authkit-strength__bar[data-score="2"] .authkit-strength__segment--filled {
213
224
  background: #f5a524;
214
225
  }
215
- .authkit-strength__bar[data-score='3'] .authkit-strength__segment--filled {
226
+ .authkit-strength__bar[data-score="3"] .authkit-strength__segment--filled {
216
227
  background: #d6c20a;
217
228
  }
218
- .authkit-strength__bar[data-score='4'] .authkit-strength__segment--filled {
229
+ .authkit-strength__bar[data-score="4"] .authkit-strength__segment--filled {
219
230
  background: #2fb344;
220
231
  }
221
232
  .authkit-strength__label {
@@ -246,8 +257,14 @@
246
257
  font-size: 0.875rem;
247
258
  color: var(--authkit-fg);
248
259
  }
249
- .authkit-orgswitcher__trigger:disabled { opacity: 0.6; cursor: default; }
250
- .authkit-orgswitcher__chevron { font-size: 0.65rem; color: var(--authkit-muted); }
260
+ .authkit-orgswitcher__trigger:disabled {
261
+ opacity: 0.6;
262
+ cursor: default;
263
+ }
264
+ .authkit-orgswitcher__chevron {
265
+ font-size: 0.65rem;
266
+ color: var(--authkit-muted);
267
+ }
251
268
  .authkit-orgswitcher__menu {
252
269
  position: absolute;
253
270
  top: calc(100% + 4px);
@@ -256,7 +273,7 @@
256
273
  background: var(--authkit-bg);
257
274
  border: 1px solid var(--authkit-border);
258
275
  border-radius: var(--authkit-radius);
259
- box-shadow: 0 4px 16px rgba(0,0,0,.1);
276
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
260
277
  z-index: 50;
261
278
  overflow: hidden;
262
279
  }
@@ -273,31 +290,118 @@
273
290
  color: var(--authkit-fg);
274
291
  text-align: left;
275
292
  }
276
- .authkit-orgswitcher__item:hover { background: color-mix(in srgb, var(--authkit-primary) 8%, transparent); }
277
- .authkit-orgswitcher__item--active { font-weight: 600; }
278
- .authkit-orgswitcher__item-name { flex: 1; }
279
- .authkit-orgswitcher__item-role { font-size: 0.75rem; color: var(--authkit-muted); margin-left: 0.5rem; }
293
+ .authkit-orgswitcher__item:hover {
294
+ background: color-mix(in srgb, var(--authkit-primary) 8%, transparent);
295
+ }
296
+ .authkit-orgswitcher__item--active {
297
+ font-weight: 600;
298
+ }
299
+ .authkit-orgswitcher__item-name {
300
+ flex: 1;
301
+ }
302
+ .authkit-orgswitcher__item-role {
303
+ font-size: 0.75rem;
304
+ color: var(--authkit-muted);
305
+ margin-left: 0.5rem;
306
+ }
280
307
 
281
308
  /* ─── OrganizationProfile ──────────────────────────────────────────────── */
282
- .authkit-org-profile { display: flex; flex-direction: column; gap: var(--authkit-gap); }
283
- .authkit-org-profile__header { margin-bottom: 0.5rem; }
284
- .authkit-org-profile__name { font-weight: 600; font-size: 1rem; color: var(--authkit-fg); }
285
- .authkit-org-profile__slug { font-size: 0.8rem; color: var(--authkit-muted); }
286
- .authkit-org-profile__section-title { font-size: 0.85rem; font-weight: 600; color: var(--authkit-fg); margin-bottom: 0.4rem; }
287
- .authkit-org-profile__members { display: flex; flex-direction: column; gap: 0.25rem; }
288
- .authkit-org-profile__member { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0; border-bottom: 1px solid var(--authkit-border); }
289
- .authkit-org-profile__member:last-child { border-bottom: none; }
290
- .authkit-org-profile__member-info { display: flex; flex-direction: column; }
291
- .authkit-org-profile__member-email { font-size: 0.875rem; color: var(--authkit-fg); }
292
- .authkit-org-profile__member-role { font-size: 0.75rem; color: var(--authkit-muted); }
293
- .authkit-org-profile__invite-form { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.75rem; }
294
- .authkit-org-profile__empty { font-size: 0.875rem; color: var(--authkit-muted); }
309
+ .authkit-org-profile {
310
+ display: flex;
311
+ flex-direction: column;
312
+ gap: var(--authkit-gap);
313
+ }
314
+ .authkit-org-profile__header {
315
+ margin-bottom: 0.5rem;
316
+ }
317
+ .authkit-org-profile__name {
318
+ font-weight: 600;
319
+ font-size: 1rem;
320
+ color: var(--authkit-fg);
321
+ }
322
+ .authkit-org-profile__slug {
323
+ font-size: 0.8rem;
324
+ color: var(--authkit-muted);
325
+ }
326
+ .authkit-org-profile__section-title {
327
+ font-size: 0.85rem;
328
+ font-weight: 600;
329
+ color: var(--authkit-fg);
330
+ margin-bottom: 0.4rem;
331
+ }
332
+ .authkit-org-profile__members {
333
+ display: flex;
334
+ flex-direction: column;
335
+ gap: 0.25rem;
336
+ }
337
+ .authkit-org-profile__member {
338
+ display: flex;
339
+ align-items: center;
340
+ gap: 0.5rem;
341
+ padding: 0.3rem 0;
342
+ border-bottom: 1px solid var(--authkit-border);
343
+ }
344
+ .authkit-org-profile__member:last-child {
345
+ border-bottom: none;
346
+ }
347
+ .authkit-org-profile__member-info {
348
+ display: flex;
349
+ flex-direction: column;
350
+ }
351
+ .authkit-org-profile__member-email {
352
+ font-size: 0.875rem;
353
+ color: var(--authkit-fg);
354
+ }
355
+ .authkit-org-profile__member-role {
356
+ font-size: 0.75rem;
357
+ color: var(--authkit-muted);
358
+ }
359
+ .authkit-org-profile__invite-form {
360
+ display: flex;
361
+ flex-direction: column;
362
+ gap: 0.4rem;
363
+ margin-top: 0.75rem;
364
+ }
365
+ .authkit-org-profile__empty {
366
+ font-size: 0.875rem;
367
+ color: var(--authkit-muted);
368
+ }
295
369
 
296
370
  /* ─── KeyRotation ──────────────────────────────────────────────────────────── */
297
- .authkit-keys { display: flex; flex-direction: column; gap: var(--authkit-gap); }
298
- .authkit-keys__title { font-size: 1rem; font-weight: 600; color: var(--authkit-fg); margin: 0 0 0.5rem; }
299
- .authkit-keys__stats { display: grid; grid-template-columns: max-content 1fr; gap: 0.2rem 0.75rem; margin: 0 0 0.5rem; font-size: 0.875rem; }
300
- .authkit-keys__stats dt { color: var(--authkit-muted); }
301
- .authkit-keys__stats dd { margin: 0; color: var(--authkit-fg); }
302
- .authkit-keys__retire { display: flex; align-items: center; gap: 0.4rem; font-size: 0.875rem; color: var(--authkit-fg); cursor: pointer; }
303
- .authkit-keys__loading { color: var(--authkit-muted); font-size: 0.9rem; }
371
+ .authkit-keys {
372
+ display: flex;
373
+ flex-direction: column;
374
+ gap: var(--authkit-gap);
375
+ }
376
+ .authkit-keys__title {
377
+ font-size: 1rem;
378
+ font-weight: 600;
379
+ color: var(--authkit-fg);
380
+ margin: 0 0 0.5rem;
381
+ }
382
+ .authkit-keys__stats {
383
+ display: grid;
384
+ grid-template-columns: max-content 1fr;
385
+ gap: 0.2rem 0.75rem;
386
+ margin: 0 0 0.5rem;
387
+ font-size: 0.875rem;
388
+ }
389
+ .authkit-keys__stats dt {
390
+ color: var(--authkit-muted);
391
+ }
392
+ .authkit-keys__stats dd {
393
+ margin: 0;
394
+ color: var(--authkit-fg);
395
+ }
396
+ .authkit-keys__retire {
397
+ display: flex;
398
+ align-items: center;
399
+ gap: 0.4rem;
400
+ font-size: 0.875rem;
401
+ color: var(--authkit-fg);
402
+ cursor: pointer;
403
+ }
404
+ .authkit-keys__loading {
405
+ color: var(--authkit-muted);
406
+ font-size: 0.9rem;
407
+ }