better-auth-ui-svelte 0.1.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -7
- package/dist/components/account/account-view.svelte +2 -7
- package/dist/components/auth/auth-view.svelte +2 -2
- package/dist/components/form-error.svelte +55 -0
- package/dist/components/form-error.svelte.d.ts +29 -0
- package/dist/components/redirect-to-sign-in.svelte +12 -0
- package/dist/components/redirect-to-sign-in.svelte.d.ts +18 -0
- package/dist/components/redirect-to-sign-up.svelte +12 -0
- package/dist/components/redirect-to-sign-up.svelte.d.ts +18 -0
- package/dist/components/settings/api-key/api-key-cell.svelte +90 -0
- package/dist/components/settings/api-key/api-key-cell.svelte.d.ts +14 -0
- package/dist/components/settings/api-key/api-key-delete-dialog.svelte +133 -0
- package/dist/components/settings/api-key/api-key-delete-dialog.svelte.d.ts +15 -0
- package/dist/components/settings/api-key/api-key-display-dialog.svelte +89 -0
- package/dist/components/settings/api-key/api-key-display-dialog.svelte.d.ts +12 -0
- package/dist/components/settings/api-key/api-keys-card.svelte +98 -0
- package/dist/components/settings/api-key/api-keys-card.svelte.d.ts +13 -0
- package/dist/components/settings/api-key/create-api-key-dialog.svelte +337 -0
- package/dist/components/settings/api-key/create-api-key-dialog.svelte.d.ts +15 -0
- package/dist/components/settings/api-key/index.d.ts +7 -0
- package/dist/components/settings/api-key/index.js +5 -0
- package/dist/components/settings/index.d.ts +1 -0
- package/dist/components/settings/index.js +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -11
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
Pre-built, customizable authentication UI components for [Better Auth](https://www.better-auth.com) in Svelte 5.
|
|
10
10
|
|
|
11
|
-
This is a Svelte 5 port of the [Better Auth UI React library](https://github.com/daveyplate/better-auth-ui)
|
|
11
|
+
This is a **complete Svelte 5 port** of the [Better Auth UI React library](https://github.com/daveyplate/better-auth-ui) with **full feature parity**. The API is nearly identical to the original React library, making it easy to follow the [official documentation](https://better-auth-ui.com). All credits for the original design and architecture go to [daveycodez](https://github.com/daveycodez).
|
|
12
12
|
|
|
13
|
-
## Warning
|
|
13
|
+
## ⚠️ Early Stage Warning
|
|
14
14
|
|
|
15
|
-
This library is
|
|
15
|
+
> **Important:** This library is in early stage development. While we have achieved full feature parity with the React version and all components have been ported, the library has not been battle-tested in production environments yet. Issues may arise. **Use at your own risk until we reach v1.0 stable.**
|
|
16
16
|
|
|
17
17
|
## Why Choose Better Auth UI?
|
|
18
18
|
|
|
@@ -22,6 +22,7 @@ This library is currently in **active development** and not yet on NPM. Breaking
|
|
|
22
22
|
|
|
23
23
|
## Key Features
|
|
24
24
|
|
|
25
|
+
- ✨ **Full Feature Parity** - Complete port of all React components and functionality
|
|
25
26
|
- 🚀 **Svelte 5 Runes** - Built with the latest Svelte 5 reactive primitives
|
|
26
27
|
- 🔐 **Better Auth Integration** - Native integration with Better Auth's Svelte client
|
|
27
28
|
- 🛣️ **Path Helpers** - Server & client-side path utilities (unique to Svelte port)
|
|
@@ -33,7 +34,9 @@ This library is currently in **active development** and not yet on NPM. Breaking
|
|
|
33
34
|
|
|
34
35
|
## Installation
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
> **Note:** The package is not yet published to NPM. For now, you can clone the repository and link it locally.
|
|
38
|
+
|
|
39
|
+
Once published, install the package using your preferred package manager:
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
42
|
pnpm install better-auth-ui-svelte better-auth zod svelte-sonner
|
|
@@ -233,10 +236,46 @@ Display a user button with avatar and dropdown menu:
|
|
|
233
236
|
- **`<SignedIn />`** - Renders children only when authenticated
|
|
234
237
|
- **`<SignedOut />`** - Renders children only when not authenticated
|
|
235
238
|
|
|
236
|
-
###
|
|
239
|
+
### Settings Components
|
|
240
|
+
|
|
241
|
+
#### Account Settings
|
|
242
|
+
|
|
243
|
+
- **`<AccountSettingsCards />`** - Complete account settings UI
|
|
244
|
+
- **`<AccountsCard />`** - Manage connected accounts
|
|
245
|
+
- **`<UpdateAvatarCard />`** - Avatar upload and management
|
|
246
|
+
- **`<UpdateNameCard />`** - Update user name
|
|
247
|
+
- **`<UpdateUsernameCard />`** - Update username
|
|
248
|
+
- **`<UpdateFieldCard />`** - Generic field update card
|
|
249
|
+
- **`<DeleteAccountCard />`** - Account deletion with confirmation
|
|
250
|
+
|
|
251
|
+
#### Security Settings
|
|
252
|
+
|
|
253
|
+
- **`<SecuritySettingsCards />`** - Complete security settings UI
|
|
254
|
+
- **`<ChangeEmailCard />`** - Email change with verification
|
|
255
|
+
- **`<ChangePasswordCard />`** - Password change with validation
|
|
256
|
+
- **`<SessionsCard />`** - Active sessions management
|
|
257
|
+
- **`<PasskeysCard />`** - Passkey authentication management
|
|
258
|
+
- **`<TwoFactorCard />`** - Two-factor authentication setup
|
|
259
|
+
- **`<ApiKeysCard />`** - API key management
|
|
260
|
+
|
|
261
|
+
### Organization Components
|
|
262
|
+
|
|
263
|
+
- **`<OrganizationSwitcher />`** - Switch between organizations
|
|
264
|
+
- **`<CreateOrganizationDialog />`** - Create new organization
|
|
265
|
+
- **`<OrganizationView />`** - Organization details view
|
|
266
|
+
- **`<OrganizationSettingsCards />`** - Organization settings
|
|
267
|
+
- **`<OrganizationMembersCard />`** - Member management
|
|
268
|
+
- **`<OrganizationInvitationsCard />`** - Invitation management
|
|
269
|
+
- **`<UserInvitationsCard />`** - User's received invitations
|
|
270
|
+
|
|
271
|
+
### Utility Components
|
|
237
272
|
|
|
238
273
|
- **`<AuthCallback />`** - OAuth callback handler
|
|
239
274
|
- **`<SignOut />`** - Sign out component
|
|
275
|
+
- **`<RedirectToSignIn />`** - Redirect unauthenticated users to sign in
|
|
276
|
+
- **`<RedirectToSignUp />`** - Redirect users to sign up
|
|
277
|
+
- **`<PasswordInput />`** - Password input with visibility toggle
|
|
278
|
+
- **`<FormError />`** - Form error display component
|
|
240
279
|
|
|
241
280
|
## Customization
|
|
242
281
|
|
|
@@ -579,7 +618,7 @@ Better Auth UI for Svelte is built with:
|
|
|
579
618
|
|
|
580
619
|
## Differences from React Version
|
|
581
620
|
|
|
582
|
-
|
|
621
|
+
This Svelte port maintains **full feature parity** with the React version. The API is nearly identical, with these framework-specific differences:
|
|
583
622
|
|
|
584
623
|
1. **Path Helpers**: Unique to this Svelte port - utility functions for generating auth paths that work client and server-side (see [Path Helpers](#path-helpers-svelte-specific-feature))
|
|
585
624
|
2. **Navigation**: Instead of Next.js router, use SvelteKit's `goto` function
|
|
@@ -587,6 +626,7 @@ While the API is nearly identical, there are some Svelte-specific differences:
|
|
|
587
626
|
4. **Dynamic Routes**: Use SvelteKit's `[path]` syntax instead of Next.js `[path]`
|
|
588
627
|
5. **Reactivity**: Built with Svelte 5 runes instead of React hooks
|
|
589
628
|
6. **Link Component**: SvelteKit doesn't need a custom Link component
|
|
629
|
+
7. **Form Handling**: Uses TanStack Svelte Form instead of React Hook Form (same API)
|
|
590
630
|
|
|
591
631
|
## Contributing
|
|
592
632
|
|
|
@@ -599,7 +639,7 @@ MIT
|
|
|
599
639
|
## Credits
|
|
600
640
|
|
|
601
641
|
- Original React version by [daveycodez](https://github.com/daveyplate/better-auth-ui)
|
|
602
|
-
- Svelte 5 port by Chris Jayden [multiplehats](https://github.com/multiplehats)
|
|
642
|
+
- Complete Svelte 5 port with full feature parity by Chris Jayden [multiplehats](https://github.com/multiplehats)
|
|
603
643
|
- Built with [Better Auth](https://www.better-auth.com)
|
|
604
644
|
- UI components from [shadcn-svelte](https://www.shadcn-svelte.com)
|
|
605
645
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
2
|
import type { AuthLocalization } from '../../types/index.js';
|
|
4
3
|
import type { AccountViewPath } from '../../utils/view-paths.js';
|
|
5
4
|
import type { SettingsCardClassNames } from '../settings/shared/settings-card.svelte';
|
|
@@ -26,10 +25,8 @@
|
|
|
26
25
|
import { cn, getViewByPath } from '../../utils/utils.js';
|
|
27
26
|
import { useAuthenticate } from '../../hooks/use-authenticate.svelte.js';
|
|
28
27
|
import { AccountSettingsCards } from '../settings/account/index.js';
|
|
29
|
-
// TODO: Import SecuritySettingsCards when security directory is ported
|
|
30
28
|
import SecuritySettingsCards from '../settings/security-settings-cards.svelte';
|
|
31
|
-
|
|
32
|
-
// import ApiKeysCard from '../settings/api-key/api-keys-card.svelte';
|
|
29
|
+
import ApiKeysCard from '../settings/api-key/api-keys-card.svelte';
|
|
33
30
|
import OrganizationsCard from '../organization/organizations-card.svelte';
|
|
34
31
|
import UserInvitationsCard from '../organization/user-invitations-card.svelte';
|
|
35
32
|
import { Button } from '../ui/button/index.js';
|
|
@@ -183,9 +180,7 @@
|
|
|
183
180
|
{:else if view === 'SECURITY'}
|
|
184
181
|
<SecuritySettingsCards {classNames} {localization} />
|
|
185
182
|
{:else if view === 'API_KEYS'}
|
|
186
|
-
|
|
187
|
-
<div class="text-muted-foreground">API Keys coming soon (ApiKeysCard not yet ported)</div>
|
|
188
|
-
<!-- <ApiKeysCard classNames={classNames?.card} localization={localization} /> -->
|
|
183
|
+
<ApiKeysCard classNames={classNames?.card} {localization} /> -->
|
|
189
184
|
{:else if view === 'ORGANIZATIONS' && organization}
|
|
190
185
|
<div class="grid w-full gap-4 md:gap-6">
|
|
191
186
|
<OrganizationsCard classNames={classNames?.card} {localization} />
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import SignOut from './sign-out.svelte';
|
|
20
20
|
import type { AuthViewPath } from '../../utils/view-paths.js';
|
|
21
21
|
import type { AuthLocalization } from '../../localization/auth-localization.js';
|
|
22
|
+
import AcceptInvitationCard from '../organization/accept-invitation-card.svelte';
|
|
22
23
|
|
|
23
24
|
export interface AuthViewClassNames {
|
|
24
25
|
base?: string;
|
|
@@ -128,8 +129,7 @@
|
|
|
128
129
|
{:else if view === 'SIGN_OUT'}
|
|
129
130
|
<SignOut />
|
|
130
131
|
{:else if view === 'ACCEPT_INVITATION'}
|
|
131
|
-
|
|
132
|
-
<div>Accept Invitation (not yet implemented)</div>
|
|
132
|
+
<AcceptInvitationCard {localization} />
|
|
133
133
|
{:else}
|
|
134
134
|
<!-- Main card layout for all other views -->
|
|
135
135
|
<Card.Root class={cn('w-full max-w-sm', className, classNames?.base)}>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import AlertCircle from '@lucide/svelte/icons/alert-circle';
|
|
3
|
+
import { cn } from '../utils/utils.js';
|
|
4
|
+
import type { AuthFormClassNames } from './auth/auth-form.svelte';
|
|
5
|
+
import Alert from './ui/alert/alert.svelte';
|
|
6
|
+
import AlertTitle from './ui/alert/alert-title.svelte';
|
|
7
|
+
import AlertDescription from './ui/alert/alert-description.svelte';
|
|
8
|
+
|
|
9
|
+
export interface FormErrorProps {
|
|
10
|
+
/**
|
|
11
|
+
* Optional form instance from @tanstack/svelte-form
|
|
12
|
+
* Used to access form-level errors from form state
|
|
13
|
+
*/
|
|
14
|
+
form?: {
|
|
15
|
+
state: {
|
|
16
|
+
errorMap?: Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Optional error message to display directly
|
|
21
|
+
* If provided, this takes precedence over form errors
|
|
22
|
+
*/
|
|
23
|
+
message?: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Optional title for the error alert
|
|
26
|
+
* @default "Error"
|
|
27
|
+
*/
|
|
28
|
+
title?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Optional class names for styling
|
|
31
|
+
*/
|
|
32
|
+
classNames?: AuthFormClassNames;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let { form, message, title, classNames }: FormErrorProps = $props();
|
|
36
|
+
|
|
37
|
+
// Determine error message from props or form state
|
|
38
|
+
// Priority: message prop > form.state.errorMap
|
|
39
|
+
const errorMessage = $derived(
|
|
40
|
+
message ||
|
|
41
|
+
(form?.state.errorMap?.onSubmit
|
|
42
|
+
? String(form.state.errorMap.onSubmit)
|
|
43
|
+
: form?.state.errorMap?.root
|
|
44
|
+
? String(form.state.errorMap.root)
|
|
45
|
+
: null)
|
|
46
|
+
);
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
{#if errorMessage}
|
|
50
|
+
<Alert variant="destructive" class={cn(classNames?.error)}>
|
|
51
|
+
<AlertCircle class="self-center" />
|
|
52
|
+
<AlertTitle>{title || 'Error'}</AlertTitle>
|
|
53
|
+
<AlertDescription>{errorMessage}</AlertDescription>
|
|
54
|
+
</Alert>
|
|
55
|
+
{/if}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AuthFormClassNames } from './auth/auth-form.svelte';
|
|
2
|
+
export interface FormErrorProps {
|
|
3
|
+
/**
|
|
4
|
+
* Optional form instance from @tanstack/svelte-form
|
|
5
|
+
* Used to access form-level errors from form state
|
|
6
|
+
*/
|
|
7
|
+
form?: {
|
|
8
|
+
state: {
|
|
9
|
+
errorMap?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Optional error message to display directly
|
|
14
|
+
* If provided, this takes precedence over form errors
|
|
15
|
+
*/
|
|
16
|
+
message?: string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Optional title for the error alert
|
|
19
|
+
* @default "Error"
|
|
20
|
+
*/
|
|
21
|
+
title?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Optional class names for styling
|
|
24
|
+
*/
|
|
25
|
+
classNames?: AuthFormClassNames;
|
|
26
|
+
}
|
|
27
|
+
declare const FormError: import("svelte").Component<FormErrorProps, {}, "">;
|
|
28
|
+
type FormError = ReturnType<typeof FormError>;
|
|
29
|
+
export default FormError;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useAuthenticate } from '../hooks/use-authenticate.svelte.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Redirects the user to the sign-in page
|
|
6
|
+
*
|
|
7
|
+
* Renders nothing and automatically redirects the current user to the authentication
|
|
8
|
+
* sign-in page. Useful for protecting routes that require authentication or
|
|
9
|
+
* redirecting users to sign in from various parts of the application.
|
|
10
|
+
*/
|
|
11
|
+
useAuthenticate({ authView: 'SIGN_IN' });
|
|
12
|
+
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const RedirectToSignIn: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type RedirectToSignIn = InstanceType<typeof RedirectToSignIn>;
|
|
18
|
+
export default RedirectToSignIn;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useAuthenticate } from '../hooks/use-authenticate.svelte.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Redirects the user to the sign-up page
|
|
6
|
+
*
|
|
7
|
+
* Renders nothing and automatically redirects the current user to the authentication
|
|
8
|
+
* sign-up page. Useful for directing new users to create an account or
|
|
9
|
+
* for redirecting from marketing pages to the registration flow.
|
|
10
|
+
*/
|
|
11
|
+
useAuthenticate({ authView: 'SIGN_UP' });
|
|
12
|
+
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const RedirectToSignUp: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type RedirectToSignUp = InstanceType<typeof RedirectToSignUp>;
|
|
18
|
+
export default RedirectToSignUp;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import KeyRound from '@lucide/svelte/icons/key-round';
|
|
3
|
+
import { getAuthUIConfig } from '../../../context/auth-ui-config.svelte.js';
|
|
4
|
+
import { cn } from '../../../utils/utils.js';
|
|
5
|
+
import type { AuthLocalization, ApiKey, Refetch } from '../../../types/index.js';
|
|
6
|
+
import { Button } from '../../ui/button/index.js';
|
|
7
|
+
import { Card } from '../../ui/card/index.js';
|
|
8
|
+
import type { SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
9
|
+
import ApiKeyDeleteDialog from './api-key-delete-dialog.svelte';
|
|
10
|
+
import { useLang } from '../../../hooks/use-lang.svelte.js';
|
|
11
|
+
|
|
12
|
+
export interface ApiKeyCellProps {
|
|
13
|
+
className?: string;
|
|
14
|
+
classNames?: SettingsCardClassNames;
|
|
15
|
+
apiKey: ApiKey;
|
|
16
|
+
localization?: Partial<AuthLocalization>;
|
|
17
|
+
refetch?: Refetch;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Props extends ApiKeyCellProps {}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
className,
|
|
24
|
+
classNames,
|
|
25
|
+
apiKey,
|
|
26
|
+
localization: propLocalization,
|
|
27
|
+
refetch
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
const { localization: contextLocalization } = getAuthUIConfig();
|
|
31
|
+
|
|
32
|
+
const mergedLocalization = $derived({ ...contextLocalization, ...propLocalization });
|
|
33
|
+
|
|
34
|
+
const { lang } = useLang();
|
|
35
|
+
|
|
36
|
+
let showDeleteDialog = $state(false);
|
|
37
|
+
|
|
38
|
+
// Format expiration date or show "Never expires"
|
|
39
|
+
const formatExpiration = () => {
|
|
40
|
+
if (!apiKey.expiresAt) return mergedLocalization.NEVER_EXPIRES;
|
|
41
|
+
|
|
42
|
+
const expiresDate = new Date(apiKey.expiresAt);
|
|
43
|
+
return `${mergedLocalization.EXPIRES} ${expiresDate.toLocaleDateString(
|
|
44
|
+
lang ?? 'en',
|
|
45
|
+
{
|
|
46
|
+
month: 'short',
|
|
47
|
+
day: 'numeric',
|
|
48
|
+
year: 'numeric'
|
|
49
|
+
}
|
|
50
|
+
)}`;
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<Card class={cn('flex-row items-center gap-3 truncate px-4 py-3', className, classNames?.cell)}>
|
|
55
|
+
<KeyRound class={cn('size-4 flex-shrink-0', classNames?.icon)} />
|
|
56
|
+
|
|
57
|
+
<div class="flex flex-col truncate">
|
|
58
|
+
<div class="flex items-center gap-2">
|
|
59
|
+
<span class="truncate font-semibold text-sm">
|
|
60
|
+
{apiKey.name}
|
|
61
|
+
</span>
|
|
62
|
+
|
|
63
|
+
<span class="flex-1 truncate text-muted-foreground text-sm">
|
|
64
|
+
{apiKey.start}{'******'}
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="truncate text-muted-foreground text-xs">
|
|
69
|
+
{formatExpiration()}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<Button
|
|
74
|
+
class={cn('relative ms-auto', classNames?.button, classNames?.outlineButton)}
|
|
75
|
+
size="sm"
|
|
76
|
+
variant="outline"
|
|
77
|
+
onclick={() => (showDeleteDialog = true)}
|
|
78
|
+
>
|
|
79
|
+
{mergedLocalization.DELETE}
|
|
80
|
+
</Button>
|
|
81
|
+
</Card>
|
|
82
|
+
|
|
83
|
+
<ApiKeyDeleteDialog
|
|
84
|
+
{classNames}
|
|
85
|
+
{apiKey}
|
|
86
|
+
localization={mergedLocalization}
|
|
87
|
+
open={showDeleteDialog}
|
|
88
|
+
onOpenChange={(open) => (showDeleteDialog = open)}
|
|
89
|
+
{refetch}
|
|
90
|
+
/>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuthLocalization, ApiKey, Refetch } from '../../../types/index.js';
|
|
2
|
+
import type { SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
3
|
+
export interface ApiKeyCellProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
classNames?: SettingsCardClassNames;
|
|
6
|
+
apiKey: ApiKey;
|
|
7
|
+
localization?: Partial<AuthLocalization>;
|
|
8
|
+
refetch?: Refetch;
|
|
9
|
+
}
|
|
10
|
+
interface Props extends ApiKeyCellProps {
|
|
11
|
+
}
|
|
12
|
+
declare const ApiKeyCell: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type ApiKeyCell = ReturnType<typeof ApiKeyCell>;
|
|
14
|
+
export default ApiKeyCell;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import KeyRound from '@lucide/svelte/icons/key-round';
|
|
3
|
+
import Loader2 from '@lucide/svelte/icons/loader-2';
|
|
4
|
+
import { getAuthUIConfig, getLocalization } from '../../../context/auth-ui-config.svelte.js';
|
|
5
|
+
import { useLang } from '../../../hooks/use-lang.svelte.js';
|
|
6
|
+
import { cn, getLocalizedError } from '../../../utils/utils.js';
|
|
7
|
+
import type { AuthLocalization } from '../../../types/index.js';
|
|
8
|
+
import type { ApiKey } from '../../../types/api-key.js';
|
|
9
|
+
import type { Refetch } from '../../../types/refetch.js';
|
|
10
|
+
import { Button } from '../../ui/button/index.js';
|
|
11
|
+
import { Card } from '../../ui/card/index.js';
|
|
12
|
+
import * as Dialog from '../../ui/dialog/index.js';
|
|
13
|
+
import type { SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
classNames?: SettingsCardClassNames;
|
|
17
|
+
apiKey: ApiKey;
|
|
18
|
+
localization?: Partial<AuthLocalization>;
|
|
19
|
+
refetch?: Refetch;
|
|
20
|
+
open?: boolean;
|
|
21
|
+
onOpenChange?: (open: boolean) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
classNames,
|
|
26
|
+
apiKey,
|
|
27
|
+
localization: propLocalization,
|
|
28
|
+
refetch,
|
|
29
|
+
open = $bindable(false),
|
|
30
|
+
onOpenChange
|
|
31
|
+
}: Props = $props();
|
|
32
|
+
|
|
33
|
+
const config = getAuthUIConfig();
|
|
34
|
+
const contextLocalization = getLocalization();
|
|
35
|
+
|
|
36
|
+
const toast = config.toast;
|
|
37
|
+
const mutators = config.mutators;
|
|
38
|
+
|
|
39
|
+
const localization = $derived({ ...contextLocalization, ...propLocalization });
|
|
40
|
+
|
|
41
|
+
const { lang } = useLang();
|
|
42
|
+
let isLoading = $state(false);
|
|
43
|
+
|
|
44
|
+
async function handleDelete() {
|
|
45
|
+
isLoading = true;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await mutators.deleteApiKey({ keyId: apiKey.id });
|
|
49
|
+
await refetch?.();
|
|
50
|
+
handleOpenChange(false);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
toast.error(getLocalizedError({ error, localization }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isLoading = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Format expiration date or show "Never expires"
|
|
59
|
+
function formatExpiration() {
|
|
60
|
+
if (!apiKey.expiresAt) return localization.NEVER_EXPIRES;
|
|
61
|
+
|
|
62
|
+
const expiresDate = new Date(apiKey.expiresAt);
|
|
63
|
+
return `${localization.EXPIRES} ${expiresDate.toLocaleDateString(lang ?? 'en', {
|
|
64
|
+
month: 'short',
|
|
65
|
+
day: 'numeric',
|
|
66
|
+
year: 'numeric'
|
|
67
|
+
})}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleOpenChange(newOpen: boolean) {
|
|
71
|
+
open = newOpen;
|
|
72
|
+
onOpenChange?.(newOpen);
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<Dialog.Root {open} onOpenChange={handleOpenChange}>
|
|
77
|
+
<Dialog.Content class={classNames?.dialog?.content} onOpenAutoFocus={(e) => e.preventDefault()}>
|
|
78
|
+
<Dialog.Header class={classNames?.dialog?.header}>
|
|
79
|
+
<Dialog.Title class={cn('text-lg md:text-xl', classNames?.title)}>
|
|
80
|
+
{localization.DELETE} {localization.API_KEY}
|
|
81
|
+
</Dialog.Title>
|
|
82
|
+
|
|
83
|
+
<Dialog.Description class={cn('text-xs md:text-sm', classNames?.description)}>
|
|
84
|
+
{localization.DELETE_API_KEY_CONFIRM}
|
|
85
|
+
</Dialog.Description>
|
|
86
|
+
</Dialog.Header>
|
|
87
|
+
|
|
88
|
+
<Card class={cn('my-2 flex-row items-center gap-3 px-4 py-3', classNames?.cell)}>
|
|
89
|
+
<KeyRound class={cn('size-4', classNames?.icon)} />
|
|
90
|
+
|
|
91
|
+
<div class="flex flex-col">
|
|
92
|
+
<div class="flex items-center gap-2">
|
|
93
|
+
<span class="text-sm font-semibold">
|
|
94
|
+
{apiKey.name}
|
|
95
|
+
</span>
|
|
96
|
+
|
|
97
|
+
<span class="text-sm text-muted-foreground">
|
|
98
|
+
{apiKey.start}{'******'}
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div class="text-xs text-muted-foreground">
|
|
103
|
+
{formatExpiration()}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</Card>
|
|
107
|
+
|
|
108
|
+
<Dialog.Footer class={classNames?.dialog?.footer}>
|
|
109
|
+
<Button
|
|
110
|
+
type="button"
|
|
111
|
+
variant="secondary"
|
|
112
|
+
onclick={() => handleOpenChange(false)}
|
|
113
|
+
disabled={isLoading}
|
|
114
|
+
class={cn(classNames?.button, classNames?.secondaryButton)}
|
|
115
|
+
>
|
|
116
|
+
{localization.CANCEL}
|
|
117
|
+
</Button>
|
|
118
|
+
|
|
119
|
+
<Button
|
|
120
|
+
type="button"
|
|
121
|
+
variant="destructive"
|
|
122
|
+
onclick={handleDelete}
|
|
123
|
+
disabled={isLoading}
|
|
124
|
+
class={cn(classNames?.button, classNames?.destructiveButton)}
|
|
125
|
+
>
|
|
126
|
+
{#if isLoading}
|
|
127
|
+
<Loader2 class="animate-spin" />
|
|
128
|
+
{/if}
|
|
129
|
+
{localization.DELETE}
|
|
130
|
+
</Button>
|
|
131
|
+
</Dialog.Footer>
|
|
132
|
+
</Dialog.Content>
|
|
133
|
+
</Dialog.Root>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AuthLocalization } from '../../../types/index.js';
|
|
2
|
+
import type { ApiKey } from '../../../types/api-key.js';
|
|
3
|
+
import type { Refetch } from '../../../types/refetch.js';
|
|
4
|
+
import type { SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
5
|
+
interface Props {
|
|
6
|
+
classNames?: SettingsCardClassNames;
|
|
7
|
+
apiKey: ApiKey;
|
|
8
|
+
localization?: Partial<AuthLocalization>;
|
|
9
|
+
refetch?: Refetch;
|
|
10
|
+
open?: boolean;
|
|
11
|
+
onOpenChange?: (open: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
declare const ApiKeyDeleteDialog: import("svelte").Component<Props, {}, "open">;
|
|
14
|
+
type ApiKeyDeleteDialog = ReturnType<typeof ApiKeyDeleteDialog>;
|
|
15
|
+
export default ApiKeyDeleteDialog;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Check from '@lucide/svelte/icons/check';
|
|
3
|
+
import Copy from '@lucide/svelte/icons/copy';
|
|
4
|
+
import { getLocalization } from '../../../context/auth-ui-config.svelte';
|
|
5
|
+
import { cn } from '../../../utils/utils.js';
|
|
6
|
+
import type { AuthLocalization } from '../../../types/index.js';
|
|
7
|
+
import type { SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
8
|
+
import { Button } from '../../ui/button/index.js';
|
|
9
|
+
import * as Dialog from '../../ui/dialog/index.js';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
classNames?: SettingsCardClassNames;
|
|
13
|
+
localization?: Partial<AuthLocalization>;
|
|
14
|
+
apiKey: string;
|
|
15
|
+
open?: boolean;
|
|
16
|
+
onOpenChange?: (open: boolean) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
classNames,
|
|
21
|
+
apiKey,
|
|
22
|
+
localization: propLocalization,
|
|
23
|
+
open = $bindable(false),
|
|
24
|
+
onOpenChange
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
const contextLocalization = getLocalization();
|
|
28
|
+
const localization = $derived({ ...contextLocalization, ...propLocalization });
|
|
29
|
+
|
|
30
|
+
let copied = $state(false);
|
|
31
|
+
|
|
32
|
+
function handleCopy() {
|
|
33
|
+
navigator.clipboard.writeText(apiKey);
|
|
34
|
+
copied = true;
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
copied = false;
|
|
37
|
+
}, 2000);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleOpenChange(newOpen: boolean) {
|
|
41
|
+
open = newOpen;
|
|
42
|
+
onOpenChange?.(newOpen);
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<Dialog.Root {open} onOpenChange={handleOpenChange}>
|
|
47
|
+
<Dialog.Content onOpenAutoFocus={(e) => e.preventDefault()} class={classNames?.dialog?.content}>
|
|
48
|
+
<Dialog.Header class={classNames?.dialog?.header}>
|
|
49
|
+
<Dialog.Title class={cn('text-lg md:text-xl', classNames?.title)}>
|
|
50
|
+
{localization.API_KEY_CREATED}
|
|
51
|
+
</Dialog.Title>
|
|
52
|
+
|
|
53
|
+
<Dialog.Description class={cn('text-xs md:text-sm', classNames?.description)}>
|
|
54
|
+
{localization.CREATE_API_KEY_SUCCESS}
|
|
55
|
+
</Dialog.Description>
|
|
56
|
+
</Dialog.Header>
|
|
57
|
+
|
|
58
|
+
<div class="break-all rounded-md bg-muted p-4 font-mono text-sm">
|
|
59
|
+
{apiKey}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<Dialog.Footer class={classNames?.dialog?.footer}>
|
|
63
|
+
<Button
|
|
64
|
+
type="button"
|
|
65
|
+
variant="outline"
|
|
66
|
+
onclick={handleCopy}
|
|
67
|
+
disabled={copied}
|
|
68
|
+
class={cn(classNames?.button, classNames?.outlineButton)}
|
|
69
|
+
>
|
|
70
|
+
{#if copied}
|
|
71
|
+
<Check class={classNames?.icon} />
|
|
72
|
+
{localization.COPIED_TO_CLIPBOARD}
|
|
73
|
+
{:else}
|
|
74
|
+
<Copy class={classNames?.icon} />
|
|
75
|
+
{localization.COPY_TO_CLIPBOARD}
|
|
76
|
+
{/if}
|
|
77
|
+
</Button>
|
|
78
|
+
|
|
79
|
+
<Button
|
|
80
|
+
type="button"
|
|
81
|
+
variant="default"
|
|
82
|
+
onclick={() => handleOpenChange(false)}
|
|
83
|
+
class={cn(classNames?.button, classNames?.primaryButton)}
|
|
84
|
+
>
|
|
85
|
+
{localization.DONE}
|
|
86
|
+
</Button>
|
|
87
|
+
</Dialog.Footer>
|
|
88
|
+
</Dialog.Content>
|
|
89
|
+
</Dialog.Root>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AuthLocalization } from '../../../types/index.js';
|
|
2
|
+
import type { SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
3
|
+
interface Props {
|
|
4
|
+
classNames?: SettingsCardClassNames;
|
|
5
|
+
localization?: Partial<AuthLocalization>;
|
|
6
|
+
apiKey: string;
|
|
7
|
+
open?: boolean;
|
|
8
|
+
onOpenChange?: (open: boolean) => void;
|
|
9
|
+
}
|
|
10
|
+
declare const ApiKeyDisplayDialog: import("svelte").Component<Props, {}, "open">;
|
|
11
|
+
type ApiKeyDisplayDialog = ReturnType<typeof ApiKeyDisplayDialog>;
|
|
12
|
+
export default ApiKeyDisplayDialog;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getAuthUIConfig } from '../../../context/auth-ui-config.svelte.js';
|
|
3
|
+
import { cn } from '../../../utils/utils.js';
|
|
4
|
+
import type { AuthLocalization } from '../../../types/index.js';
|
|
5
|
+
import { CardContent } from '../../ui/card/index.js';
|
|
6
|
+
import SettingsCard, { type SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
7
|
+
import ApiKeyCell from './api-key-cell.svelte';
|
|
8
|
+
import ApiKeyDisplayDialog from './api-key-display-dialog.svelte';
|
|
9
|
+
import CreateApiKeyDialog from './create-api-key-dialog.svelte';
|
|
10
|
+
|
|
11
|
+
export interface ApiKeysCardProps {
|
|
12
|
+
className?: string;
|
|
13
|
+
classNames?: SettingsCardClassNames;
|
|
14
|
+
localization?: Partial<AuthLocalization>;
|
|
15
|
+
organizationId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Props extends ApiKeysCardProps {}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
className,
|
|
22
|
+
classNames,
|
|
23
|
+
localization: propLocalization,
|
|
24
|
+
organizationId,
|
|
25
|
+
...restProps
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
hooks: { useListApiKeys },
|
|
30
|
+
localization: contextLocalization
|
|
31
|
+
} = getAuthUIConfig();
|
|
32
|
+
|
|
33
|
+
const mergedLocalization = $derived({ ...contextLocalization, ...propLocalization });
|
|
34
|
+
|
|
35
|
+
const listApiKeys = useListApiKeys();
|
|
36
|
+
const apiKeys = $derived(listApiKeys.data);
|
|
37
|
+
const isPending = $derived(listApiKeys.isPending);
|
|
38
|
+
const refetch = $derived(listApiKeys.refetch);
|
|
39
|
+
|
|
40
|
+
// Filter API keys by organizationId
|
|
41
|
+
const filteredApiKeys = $derived(
|
|
42
|
+
!apiKeys ? null : !organizationId ? apiKeys : apiKeys.filter(
|
|
43
|
+
(apiKey) => organizationId === apiKey.metadata?.organizationId
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
let createDialogOpen = $state(false);
|
|
48
|
+
let displayDialogOpen = $state(false);
|
|
49
|
+
let createdApiKey = $state('');
|
|
50
|
+
|
|
51
|
+
const handleCreateApiKey = (apiKey: string) => {
|
|
52
|
+
createdApiKey = apiKey;
|
|
53
|
+
displayDialogOpen = true;
|
|
54
|
+
};
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<SettingsCard
|
|
58
|
+
{className}
|
|
59
|
+
{classNames}
|
|
60
|
+
actionLabel={mergedLocalization.CREATE_API_KEY}
|
|
61
|
+
description={mergedLocalization.API_KEYS_DESCRIPTION}
|
|
62
|
+
instructions={mergedLocalization.API_KEYS_INSTRUCTIONS}
|
|
63
|
+
{isPending}
|
|
64
|
+
title={mergedLocalization.API_KEYS}
|
|
65
|
+
action={() => (createDialogOpen = true)}
|
|
66
|
+
{...restProps}
|
|
67
|
+
>
|
|
68
|
+
{#if filteredApiKeys && filteredApiKeys.length > 0}
|
|
69
|
+
<CardContent class={cn('grid gap-4', classNames?.content)}>
|
|
70
|
+
{#each filteredApiKeys as apiKey (apiKey.id)}
|
|
71
|
+
<ApiKeyCell
|
|
72
|
+
{classNames}
|
|
73
|
+
{apiKey}
|
|
74
|
+
localization={mergedLocalization}
|
|
75
|
+
{refetch}
|
|
76
|
+
/>
|
|
77
|
+
{/each}
|
|
78
|
+
</CardContent>
|
|
79
|
+
{/if}
|
|
80
|
+
</SettingsCard>
|
|
81
|
+
|
|
82
|
+
<CreateApiKeyDialog
|
|
83
|
+
{classNames}
|
|
84
|
+
localization={mergedLocalization}
|
|
85
|
+
open={createDialogOpen}
|
|
86
|
+
onOpenChange={(open) => (createDialogOpen = open)}
|
|
87
|
+
onSuccess={handleCreateApiKey}
|
|
88
|
+
{refetch}
|
|
89
|
+
{organizationId}
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
<ApiKeyDisplayDialog
|
|
93
|
+
{classNames}
|
|
94
|
+
apiKey={createdApiKey}
|
|
95
|
+
localization={mergedLocalization}
|
|
96
|
+
open={displayDialogOpen}
|
|
97
|
+
onOpenChange={(open) => (displayDialogOpen = open)}
|
|
98
|
+
/>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AuthLocalization } from '../../../types/index.js';
|
|
2
|
+
import { type SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
3
|
+
export interface ApiKeysCardProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
classNames?: SettingsCardClassNames;
|
|
6
|
+
localization?: Partial<AuthLocalization>;
|
|
7
|
+
organizationId?: string;
|
|
8
|
+
}
|
|
9
|
+
interface Props extends ApiKeysCardProps {
|
|
10
|
+
}
|
|
11
|
+
declare const ApiKeysCard: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type ApiKeysCard = ReturnType<typeof ApiKeysCard>;
|
|
13
|
+
export default ApiKeysCard;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createForm } from '@tanstack/svelte-form';
|
|
4
|
+
import Loader2 from '@lucide/svelte/icons/loader-2';
|
|
5
|
+
import type { Organization } from 'better-auth/plugins/organization';
|
|
6
|
+
import {
|
|
7
|
+
getAuthClient,
|
|
8
|
+
getAuthUIConfig,
|
|
9
|
+
getLocalization
|
|
10
|
+
} from '../../../context/auth-ui-config.svelte';
|
|
11
|
+
import { useLang } from '../../../hooks/use-lang.svelte.js';
|
|
12
|
+
import { cn, getLocalizedError, getFieldError } from '../../../utils/utils.js';
|
|
13
|
+
import type { AuthLocalization } from '../../../types/index.js';
|
|
14
|
+
import type { SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
15
|
+
import { Button } from '../../ui/button/index.js';
|
|
16
|
+
import * as Dialog from '../../ui/dialog/index.js';
|
|
17
|
+
import { Input } from '../../ui/input/index.js';
|
|
18
|
+
import { Label } from '../../ui/label/index.js';
|
|
19
|
+
import * as Select from '../../ui/select/index.js';
|
|
20
|
+
import OrganizationCellView from '../../organization/organization-cell-view.svelte';
|
|
21
|
+
import PersonalAccountView from '../../organization/personal-account-view.svelte';
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
class?: string;
|
|
25
|
+
classNames?: SettingsCardClassNames;
|
|
26
|
+
open?: boolean;
|
|
27
|
+
onOpenChange?: (open: boolean) => void;
|
|
28
|
+
localization?: Partial<AuthLocalization>;
|
|
29
|
+
onSuccess: (key: string) => void;
|
|
30
|
+
refetch?: import('../../../types/refetch.js').Refetch;
|
|
31
|
+
organizationId?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let {
|
|
35
|
+
class: className,
|
|
36
|
+
classNames,
|
|
37
|
+
open = $bindable(false),
|
|
38
|
+
onOpenChange,
|
|
39
|
+
localization: propLocalization,
|
|
40
|
+
onSuccess,
|
|
41
|
+
refetch,
|
|
42
|
+
organizationId
|
|
43
|
+
}: Props = $props();
|
|
44
|
+
|
|
45
|
+
const authClient = getAuthClient();
|
|
46
|
+
const config = getAuthUIConfig();
|
|
47
|
+
const contextLocalization = getLocalization();
|
|
48
|
+
|
|
49
|
+
const { hooks, toast, apiKey, organization: contextOrganization } = config;
|
|
50
|
+
|
|
51
|
+
const localization = $derived({ ...contextLocalization, ...propLocalization });
|
|
52
|
+
|
|
53
|
+
// Use the lang hook
|
|
54
|
+
const { lang } = useLang();
|
|
55
|
+
|
|
56
|
+
// Get current user from session - must be at top level
|
|
57
|
+
const sessionStore = hooks.useSession();
|
|
58
|
+
const sessionData = $derived('data' in $sessionStore ? $sessionStore.data : undefined);
|
|
59
|
+
const user = $derived(sessionData?.user);
|
|
60
|
+
|
|
61
|
+
// Fetch organizations if organization plugin is available - must be at top level
|
|
62
|
+
const organizationsStore = contextOrganization ? hooks.useListOrganizations() : null;
|
|
63
|
+
const organizations = $derived(
|
|
64
|
+
organizationsStore && 'data' in organizationsStore
|
|
65
|
+
? (organizationsStore.data as Organization[] | null | undefined)
|
|
66
|
+
: undefined
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const showOrganizationSelect = $derived(contextOrganization?.apiKey);
|
|
70
|
+
|
|
71
|
+
// Form validation schema
|
|
72
|
+
const formSchema = $derived(
|
|
73
|
+
z.object({
|
|
74
|
+
name: z.string().min(1, `${localization.NAME} ${localization.IS_REQUIRED}`),
|
|
75
|
+
expiresInDays: z.string().optional(),
|
|
76
|
+
organizationId: showOrganizationSelect
|
|
77
|
+
? z.string().min(1, `${localization.ORGANIZATION} ${localization.IS_REQUIRED}`)
|
|
78
|
+
: z.string().optional()
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Track selected values for Select components
|
|
83
|
+
let selectedExpiresInDays = $state('none');
|
|
84
|
+
let selectedOrganizationId = $state(organizationId ?? 'personal');
|
|
85
|
+
|
|
86
|
+
// Create form
|
|
87
|
+
const form = createForm(() => ({
|
|
88
|
+
defaultValues: {
|
|
89
|
+
name: '',
|
|
90
|
+
expiresInDays: 'none',
|
|
91
|
+
organizationId: organizationId ?? 'personal'
|
|
92
|
+
},
|
|
93
|
+
onSubmit: async ({ value }) => {
|
|
94
|
+
try {
|
|
95
|
+
const expiresIn =
|
|
96
|
+
value.expiresInDays && value.expiresInDays !== 'none'
|
|
97
|
+
? Number.parseInt(value.expiresInDays) * 60 * 60 * 24
|
|
98
|
+
: undefined;
|
|
99
|
+
|
|
100
|
+
const selectedOrgId =
|
|
101
|
+
value.organizationId === 'personal' ? undefined : value.organizationId;
|
|
102
|
+
|
|
103
|
+
const metadata = {
|
|
104
|
+
...(typeof apiKey === 'object' ? apiKey.metadata : {}),
|
|
105
|
+
...(contextOrganization && selectedOrgId ? { organizationId: selectedOrgId } : {})
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = await (authClient as any).apiKey.create({
|
|
109
|
+
name: value.name,
|
|
110
|
+
expiresIn,
|
|
111
|
+
prefix: typeof apiKey === 'object' ? apiKey.prefix : undefined,
|
|
112
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
113
|
+
fetchOptions: { throw: true }
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await refetch?.();
|
|
117
|
+
onSuccess(result.key);
|
|
118
|
+
handleOpenChange(false);
|
|
119
|
+
form.reset();
|
|
120
|
+
selectedExpiresInDays = 'none';
|
|
121
|
+
selectedOrganizationId = organizationId ?? 'personal';
|
|
122
|
+
} catch (error) {
|
|
123
|
+
toast.error(getLocalizedError({ error, localization }));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
const isSubmitting = $derived(form.state.isSubmitting);
|
|
129
|
+
|
|
130
|
+
function handleOpenChange(newOpen: boolean) {
|
|
131
|
+
open = newOpen;
|
|
132
|
+
onOpenChange?.(newOpen);
|
|
133
|
+
|
|
134
|
+
// Reset form when dialog closes
|
|
135
|
+
if (!newOpen) {
|
|
136
|
+
form.reset();
|
|
137
|
+
selectedExpiresInDays = 'none';
|
|
138
|
+
selectedOrganizationId = organizationId ?? 'personal';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Create relative time formatter
|
|
143
|
+
const rtf = $derived(new Intl.RelativeTimeFormat(lang ?? 'en'));
|
|
144
|
+
|
|
145
|
+
// Expiration options
|
|
146
|
+
const expirationDays = [1, 7, 30, 60, 90, 180, 365];
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<Dialog.Root {open} onOpenChange={handleOpenChange}>
|
|
150
|
+
<Dialog.Content
|
|
151
|
+
class={classNames?.dialog?.content}
|
|
152
|
+
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
153
|
+
>
|
|
154
|
+
<Dialog.Header class={classNames?.dialog?.header}>
|
|
155
|
+
<Dialog.Title class={cn('text-lg md:text-xl', classNames?.title)}>
|
|
156
|
+
{localization.CREATE_API_KEY}
|
|
157
|
+
</Dialog.Title>
|
|
158
|
+
|
|
159
|
+
<Dialog.Description class={cn('text-xs md:text-sm', classNames?.description)}>
|
|
160
|
+
{localization.CREATE_API_KEY_DESCRIPTION}
|
|
161
|
+
</Dialog.Description>
|
|
162
|
+
</Dialog.Header>
|
|
163
|
+
|
|
164
|
+
<form
|
|
165
|
+
onsubmit={(e) => {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
form.handleSubmit();
|
|
168
|
+
}}
|
|
169
|
+
class="space-y-6"
|
|
170
|
+
>
|
|
171
|
+
{#if showOrganizationSelect}
|
|
172
|
+
<form.Field name="organizationId">
|
|
173
|
+
{#snippet children(field)}
|
|
174
|
+
<div class="space-y-2">
|
|
175
|
+
<Label for="organizationId" class={classNames?.label}>
|
|
176
|
+
{localization.ORGANIZATION}
|
|
177
|
+
</Label>
|
|
178
|
+
|
|
179
|
+
<Select.Root
|
|
180
|
+
type="single"
|
|
181
|
+
value={selectedOrganizationId}
|
|
182
|
+
onValueChange={(value?: string) => {
|
|
183
|
+
if (value) {
|
|
184
|
+
selectedOrganizationId = value;
|
|
185
|
+
field.handleChange(value);
|
|
186
|
+
}
|
|
187
|
+
}}
|
|
188
|
+
disabled={isSubmitting}
|
|
189
|
+
>
|
|
190
|
+
<Select.Trigger id="organizationId" class={cn('w-full p-2', classNames?.input)}>
|
|
191
|
+
{#snippet children()}
|
|
192
|
+
{#if selectedOrganizationId === 'personal'}
|
|
193
|
+
<PersonalAccountView {user} {localization} size="sm" />
|
|
194
|
+
{:else}
|
|
195
|
+
{@const org = organizations?.find((o) => o.id === selectedOrganizationId)}
|
|
196
|
+
{#if org}
|
|
197
|
+
<OrganizationCellView organization={org} {localization} size="sm" />
|
|
198
|
+
{/if}
|
|
199
|
+
{/if}
|
|
200
|
+
{/snippet}
|
|
201
|
+
</Select.Trigger>
|
|
202
|
+
|
|
203
|
+
<Select.Content class="w-[--radix-select-trigger-width]">
|
|
204
|
+
<Select.Item value="personal" label="">
|
|
205
|
+
{#snippet children()}
|
|
206
|
+
<PersonalAccountView {user} {localization} size="sm" />
|
|
207
|
+
{/snippet}
|
|
208
|
+
</Select.Item>
|
|
209
|
+
|
|
210
|
+
{#if organizations}
|
|
211
|
+
{#each organizations as org (org.id)}
|
|
212
|
+
<Select.Item value={org.id} label="">
|
|
213
|
+
{#snippet children()}
|
|
214
|
+
<OrganizationCellView organization={org} {localization} size="sm" />
|
|
215
|
+
{/snippet}
|
|
216
|
+
</Select.Item>
|
|
217
|
+
{/each}
|
|
218
|
+
{/if}
|
|
219
|
+
</Select.Content>
|
|
220
|
+
</Select.Root>
|
|
221
|
+
|
|
222
|
+
{#if field.state.meta.errors.length > 0}
|
|
223
|
+
<p class="text-sm font-medium text-destructive">
|
|
224
|
+
{getFieldError(field.state.meta.errors[0])}
|
|
225
|
+
</p>
|
|
226
|
+
{/if}
|
|
227
|
+
</div>
|
|
228
|
+
{/snippet}
|
|
229
|
+
</form.Field>
|
|
230
|
+
{/if}
|
|
231
|
+
|
|
232
|
+
<div class="flex gap-4">
|
|
233
|
+
<form.Field name="name" validators={{ onChange: formSchema.shape.name }}>
|
|
234
|
+
{#snippet children(field)}
|
|
235
|
+
<div class="flex-1 space-y-2">
|
|
236
|
+
<Label for="name" class={classNames?.label}>
|
|
237
|
+
{localization.NAME}
|
|
238
|
+
</Label>
|
|
239
|
+
|
|
240
|
+
<Input
|
|
241
|
+
id="name"
|
|
242
|
+
placeholder={localization.API_KEY_NAME_PLACEHOLDER}
|
|
243
|
+
autofocus
|
|
244
|
+
value={field.state.value}
|
|
245
|
+
oninput={(e) => field.handleChange(e.currentTarget.value)}
|
|
246
|
+
onblur={field.handleBlur}
|
|
247
|
+
disabled={isSubmitting}
|
|
248
|
+
class={classNames?.input}
|
|
249
|
+
/>
|
|
250
|
+
|
|
251
|
+
{#if field.state.meta.errors.length > 0}
|
|
252
|
+
<p class="text-sm font-medium text-destructive">
|
|
253
|
+
{getFieldError(field.state.meta.errors[0])}
|
|
254
|
+
</p>
|
|
255
|
+
{/if}
|
|
256
|
+
</div>
|
|
257
|
+
{/snippet}
|
|
258
|
+
</form.Field>
|
|
259
|
+
|
|
260
|
+
<form.Field name="expiresInDays">
|
|
261
|
+
{#snippet children(field)}
|
|
262
|
+
<div class="space-y-2">
|
|
263
|
+
<Label for="expiresInDays" class={classNames?.label}>
|
|
264
|
+
{localization.EXPIRES}
|
|
265
|
+
</Label>
|
|
266
|
+
|
|
267
|
+
<Select.Root
|
|
268
|
+
type="single"
|
|
269
|
+
value={selectedExpiresInDays}
|
|
270
|
+
onValueChange={(value?: string) => {
|
|
271
|
+
if (value !== undefined) {
|
|
272
|
+
selectedExpiresInDays = value;
|
|
273
|
+
field.handleChange(value);
|
|
274
|
+
}
|
|
275
|
+
}}
|
|
276
|
+
disabled={isSubmitting}
|
|
277
|
+
>
|
|
278
|
+
<Select.Trigger id="expiresInDays" class={classNames?.input}>
|
|
279
|
+
{#snippet children()}
|
|
280
|
+
{#if selectedExpiresInDays === 'none'}
|
|
281
|
+
{localization.NO_EXPIRATION}
|
|
282
|
+
{:else if selectedExpiresInDays === '365'}
|
|
283
|
+
{rtf.format(1, 'year')}
|
|
284
|
+
{:else}
|
|
285
|
+
{rtf.format(Number.parseInt(selectedExpiresInDays), 'day')}
|
|
286
|
+
{/if}
|
|
287
|
+
{/snippet}
|
|
288
|
+
</Select.Trigger>
|
|
289
|
+
|
|
290
|
+
<Select.Content>
|
|
291
|
+
<Select.Item value="none" label={localization.NO_EXPIRATION} />
|
|
292
|
+
|
|
293
|
+
{#each expirationDays as days (days)}
|
|
294
|
+
<Select.Item
|
|
295
|
+
value={days.toString()}
|
|
296
|
+
label={days === 365 ? rtf.format(1, 'year') : rtf.format(days, 'day')}
|
|
297
|
+
/>
|
|
298
|
+
{/each}
|
|
299
|
+
</Select.Content>
|
|
300
|
+
</Select.Root>
|
|
301
|
+
|
|
302
|
+
{#if field.state.meta.errors.length > 0}
|
|
303
|
+
<p class="text-sm font-medium text-destructive">
|
|
304
|
+
{getFieldError(field.state.meta.errors[0])}
|
|
305
|
+
</p>
|
|
306
|
+
{/if}
|
|
307
|
+
</div>
|
|
308
|
+
{/snippet}
|
|
309
|
+
</form.Field>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<Dialog.Footer class={classNames?.dialog?.footer}>
|
|
313
|
+
<Button
|
|
314
|
+
type="button"
|
|
315
|
+
variant="outline"
|
|
316
|
+
onclick={() => handleOpenChange(false)}
|
|
317
|
+
class={cn(classNames?.button, classNames?.outlineButton)}
|
|
318
|
+
disabled={isSubmitting}
|
|
319
|
+
>
|
|
320
|
+
{localization.CANCEL}
|
|
321
|
+
</Button>
|
|
322
|
+
|
|
323
|
+
<Button
|
|
324
|
+
type="submit"
|
|
325
|
+
variant="default"
|
|
326
|
+
class={cn(classNames?.button, classNames?.primaryButton)}
|
|
327
|
+
disabled={isSubmitting}
|
|
328
|
+
>
|
|
329
|
+
{#if isSubmitting}
|
|
330
|
+
<Loader2 class="animate-spin" />
|
|
331
|
+
{/if}
|
|
332
|
+
{localization.CREATE_API_KEY}
|
|
333
|
+
</Button>
|
|
334
|
+
</Dialog.Footer>
|
|
335
|
+
</form>
|
|
336
|
+
</Dialog.Content>
|
|
337
|
+
</Dialog.Root>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AuthLocalization } from '../../../types/index.js';
|
|
2
|
+
import type { SettingsCardClassNames } from '../shared/settings-card.svelte';
|
|
3
|
+
interface Props {
|
|
4
|
+
class?: string;
|
|
5
|
+
classNames?: SettingsCardClassNames;
|
|
6
|
+
open?: boolean;
|
|
7
|
+
onOpenChange?: (open: boolean) => void;
|
|
8
|
+
localization?: Partial<AuthLocalization>;
|
|
9
|
+
onSuccess: (key: string) => void;
|
|
10
|
+
refetch?: import('../../../types/refetch.js').Refetch;
|
|
11
|
+
organizationId?: string;
|
|
12
|
+
}
|
|
13
|
+
declare const CreateApiKeyDialog: import("svelte").Component<Props, {}, "open">;
|
|
14
|
+
type CreateApiKeyDialog = ReturnType<typeof CreateApiKeyDialog>;
|
|
15
|
+
export default CreateApiKeyDialog;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as ApiKeysCard } from './api-keys-card.svelte';
|
|
2
|
+
export { default as ApiKeyCell } from './api-key-cell.svelte';
|
|
3
|
+
export { default as ApiKeyDisplayDialog } from './api-key-display-dialog.svelte';
|
|
4
|
+
export { default as ApiKeyDeleteDialog } from './api-key-delete-dialog.svelte';
|
|
5
|
+
export { default as CreateApiKeyDialog } from './create-api-key-dialog.svelte';
|
|
6
|
+
export type { ApiKeysCardProps } from './api-keys-card.svelte';
|
|
7
|
+
export type { ApiKeyCellProps } from './api-key-cell.svelte';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as ApiKeysCard } from './api-keys-card.svelte';
|
|
2
|
+
export { default as ApiKeyCell } from './api-key-cell.svelte';
|
|
3
|
+
export { default as ApiKeyDisplayDialog } from './api-key-display-dialog.svelte';
|
|
4
|
+
export { default as ApiKeyDeleteDialog } from './api-key-delete-dialog.svelte';
|
|
5
|
+
export { default as CreateApiKeyDialog } from './create-api-key-dialog.svelte';
|
|
@@ -4,5 +4,6 @@ export * from './security/index.js';
|
|
|
4
4
|
export * from './providers/index.js';
|
|
5
5
|
export * from './two-factor/index.js';
|
|
6
6
|
export * from './passkey/index.js';
|
|
7
|
+
export * from './api-key/index.js';
|
|
7
8
|
export { default as SecuritySettingsCards } from './security-settings-cards.svelte';
|
|
8
9
|
export type { SecuritySettingsCardsProps } from './security-settings-cards.svelte';
|
|
@@ -4,4 +4,5 @@ export * from './security/index.js';
|
|
|
4
4
|
export * from './providers/index.js';
|
|
5
5
|
export * from './two-factor/index.js';
|
|
6
6
|
export * from './passkey/index.js';
|
|
7
|
+
export * from './api-key/index.js';
|
|
7
8
|
export { default as SecuritySettingsCards } from './security-settings-cards.svelte';
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export { default as UserButton } from './components/user-button.svelte';
|
|
|
6
6
|
export { default as UserView } from './components/user-view.svelte';
|
|
7
7
|
export { default as AuthLoading } from './components/auth-loading.svelte';
|
|
8
8
|
export * as ProviderIcons from './components/provider-icons/index.js';
|
|
9
|
+
export { default as PasswordInput } from './components/password-input.svelte';
|
|
10
|
+
export { default as RedirectToSignIn } from './components/redirect-to-sign-in.svelte';
|
|
11
|
+
export { default as RedirectToSignUp } from './components/redirect-to-sign-up.svelte';
|
|
9
12
|
export * from './components/account/index.js';
|
|
10
13
|
export { default as AuthCallback } from './components/auth/auth-callback.svelte';
|
|
11
14
|
export { default as AuthForm } from './components/auth/auth-form.svelte';
|
|
@@ -18,6 +21,8 @@ export { default as ResetPasswordForm } from './components/auth/forms/reset-pass
|
|
|
18
21
|
export { default as SignInForm } from './components/auth/forms/sign-in-form.svelte';
|
|
19
22
|
export { default as SignUpForm } from './components/auth/forms/sign-up-form.svelte';
|
|
20
23
|
export { default as TwoFactorForm } from './components/auth/forms/two-factor-form.svelte';
|
|
24
|
+
export { default as FormError } from './components/form-error.svelte';
|
|
25
|
+
export type { FormErrorProps } from './components/form-error.svelte';
|
|
21
26
|
export { default as CreateOrganizationDialog } from './components/organization/create-organization-dialog.svelte';
|
|
22
27
|
export { default as DeleteOrganizationCard } from './components/organization/delete-organization-card.svelte';
|
|
23
28
|
export { default as DeleteOrganizationDialog } from './components/organization/delete-organization-dialog.svelte';
|
|
@@ -49,9 +54,18 @@ export { default as UpdateFieldCard } from './components/settings/account/update
|
|
|
49
54
|
export { default as UpdateNameCard } from './components/settings/account/update-name-card.svelte';
|
|
50
55
|
export { default as UpdateUsernameCard } from './components/settings/account/update-username-card.svelte';
|
|
51
56
|
export { default as AccountSettingsCards } from './components/settings/account/account-settings-cards.svelte';
|
|
57
|
+
export { default as ApiKeysCard } from './components/settings/api-key/api-keys-card.svelte';
|
|
58
|
+
export type { ApiKeysCardProps } from './components/settings/api-key/api-keys-card.svelte';
|
|
59
|
+
export { default as PasskeysCard } from './components/settings/passkey/passkeys-card.svelte';
|
|
52
60
|
export { default as ProvidersCard } from './components/settings/providers/providers-card.svelte';
|
|
53
61
|
export type { ProvidersCardProps } from './components/settings/providers/providers-card.svelte';
|
|
62
|
+
export { default as ChangeEmailCard } from './components/settings/security/change-email-card.svelte';
|
|
63
|
+
export { default as ChangePasswordCard } from './components/settings/security/change-password-card.svelte';
|
|
64
|
+
export { default as SessionsCard } from './components/settings/security/sessions-card.svelte';
|
|
65
|
+
export { default as SecuritySettingsCards } from './components/settings/security-settings-cards.svelte';
|
|
66
|
+
export { default as SettingsCard } from './components/settings/shared/settings-card.svelte';
|
|
54
67
|
export { default as SettingsCellSkeleton } from './components/settings/skeletons/settings-cell-skeleton.svelte';
|
|
68
|
+
export { default as TwoFactorCard } from './components/settings/two-factor/two-factor-card.svelte';
|
|
55
69
|
export * from './stores/use-auth-data.svelte.js';
|
|
56
70
|
export * from './hooks/use-authenticate.svelte.js';
|
|
57
71
|
export * from './hooks/use-current-organization.svelte.js';
|
package/dist/index.js
CHANGED
|
@@ -7,9 +7,9 @@ export { default as UserButton } from './components/user-button.svelte';
|
|
|
7
7
|
export { default as UserView } from './components/user-view.svelte';
|
|
8
8
|
export { default as AuthLoading } from './components/auth-loading.svelte';
|
|
9
9
|
export * as ProviderIcons from './components/provider-icons/index.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export { default as PasswordInput } from './components/password-input.svelte';
|
|
11
|
+
export { default as RedirectToSignIn } from './components/redirect-to-sign-in.svelte';
|
|
12
|
+
export { default as RedirectToSignUp } from './components/redirect-to-sign-up.svelte';
|
|
13
13
|
// Account Components
|
|
14
14
|
export * from './components/account/index.js';
|
|
15
15
|
// Auth Components
|
|
@@ -25,6 +25,8 @@ export { default as ResetPasswordForm } from './components/auth/forms/reset-pass
|
|
|
25
25
|
export { default as SignInForm } from './components/auth/forms/sign-in-form.svelte';
|
|
26
26
|
export { default as SignUpForm } from './components/auth/forms/sign-up-form.svelte';
|
|
27
27
|
export { default as TwoFactorForm } from './components/auth/forms/two-factor-form.svelte';
|
|
28
|
+
// Form Utilities
|
|
29
|
+
export { default as FormError } from './components/form-error.svelte';
|
|
28
30
|
// Organization Components
|
|
29
31
|
// export { default as AcceptInvitationCard } from './components/organization/accept-invitation-card.svelte';
|
|
30
32
|
export { default as CreateOrganizationDialog } from './components/organization/create-organization-dialog.svelte';
|
|
@@ -60,23 +62,23 @@ export { default as UpdateNameCard } from './components/settings/account/update-
|
|
|
60
62
|
export { default as UpdateUsernameCard } from './components/settings/account/update-username-card.svelte';
|
|
61
63
|
export { default as AccountSettingsCards } from './components/settings/account/account-settings-cards.svelte';
|
|
62
64
|
// Settings Components - API Key
|
|
63
|
-
|
|
65
|
+
export { default as ApiKeysCard } from './components/settings/api-key/api-keys-card.svelte';
|
|
64
66
|
// Settings Components - Passkey
|
|
65
|
-
|
|
67
|
+
export { default as PasskeysCard } from './components/settings/passkey/passkeys-card.svelte';
|
|
66
68
|
// Settings Components - Providers
|
|
67
69
|
export { default as ProvidersCard } from './components/settings/providers/providers-card.svelte';
|
|
68
70
|
// Settings Components - Security
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
export { default as ChangeEmailCard } from './components/settings/security/change-email-card.svelte';
|
|
72
|
+
export { default as ChangePasswordCard } from './components/settings/security/change-password-card.svelte';
|
|
73
|
+
export { default as SessionsCard } from './components/settings/security/sessions-card.svelte';
|
|
74
|
+
export { default as SecuritySettingsCards } from './components/settings/security-settings-cards.svelte';
|
|
73
75
|
// Settings Components - Shared
|
|
74
|
-
|
|
76
|
+
export { default as SettingsCard } from './components/settings/shared/settings-card.svelte';
|
|
75
77
|
// Settings Components - Skeletons
|
|
76
78
|
// export { default as InputFieldSkeleton } from './components/settings/skeletons/input-field-skeleton.svelte';
|
|
77
79
|
export { default as SettingsCellSkeleton } from './components/settings/skeletons/settings-cell-skeleton.svelte';
|
|
78
80
|
// Settings Components - Two Factor
|
|
79
|
-
|
|
81
|
+
export { default as TwoFactorCard } from './components/settings/two-factor/two-factor-card.svelte';
|
|
80
82
|
// Hooks
|
|
81
83
|
export * from './stores/use-auth-data.svelte.js';
|
|
82
84
|
export * from './hooks/use-authenticate.svelte.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth-ui-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Pre-built authentication UI components for Better Auth in Svelte 5",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
|
+
"./css": "./dist/style.css",
|
|
17
18
|
".": {
|
|
18
19
|
"types": "./dist/index.d.ts",
|
|
19
20
|
"svelte": "./dist/index.js"
|