@better-auth-ui/heroui 1.6.6 → 1.6.8
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/dist/components/auth/api-key/api-key-skeleton.d.ts +1 -0
- package/dist/components/auth/api-key/api-key-skeleton.js +21 -0
- package/dist/components/auth/api-key/api-key.d.ts +5 -0
- package/dist/components/auth/api-key/api-key.js +55 -0
- package/dist/components/auth/api-key/api-keys-empty.d.ts +4 -0
- package/dist/components/auth/api-key/api-keys-empty.js +35 -0
- package/dist/components/auth/api-key/api-keys.d.ts +6 -0
- package/dist/components/auth/api-key/api-keys.js +41 -0
- package/dist/components/auth/api-key/create-api-key-dialog.d.ts +5 -0
- package/dist/components/auth/api-key/create-api-key-dialog.js +74 -0
- package/dist/components/auth/api-key/delete-api-key-dialog.d.ts +7 -0
- package/dist/components/auth/api-key/delete-api-key-dialog.js +51 -0
- package/dist/components/auth/api-key/new-api-key-dialog.d.ts +7 -0
- package/dist/components/auth/api-key/new-api-key-dialog.js +60 -0
- package/dist/components/auth/multi-session/switch-account-submenu-content.js +14 -15
- package/dist/components/auth/passkey/add-passkey-dialog.d.ts +5 -0
- package/dist/components/auth/passkey/add-passkey-dialog.js +65 -0
- package/dist/components/auth/passkey/delete-passkey-dialog.d.ts +11 -0
- package/dist/components/auth/passkey/delete-passkey-dialog.js +47 -0
- package/dist/components/auth/passkey/passkey-skeleton.d.ts +1 -0
- package/dist/components/auth/passkey/passkey-skeleton.js +17 -0
- package/dist/components/auth/passkey/passkey.d.ts +2 -5
- package/dist/components/auth/passkey/passkey.js +16 -13
- package/dist/components/auth/passkey/passkeys-empty.d.ts +4 -0
- package/dist/components/auth/passkey/passkeys-empty.js +35 -0
- package/dist/components/auth/passkey/passkeys.js +35 -99
- package/dist/components/auth/settings/security/linked-account.js +1 -1
- package/dist/lib/auth/api-key-plugin.d.ts +23 -0
- package/dist/lib/auth/api-key-plugin.js +10 -0
- package/dist/lib/auth/passkey-plugin.d.ts +4 -1
- package/dist/plugins.d.ts +2 -0
- package/dist/plugins.js +22 -20
- package/package.json +3 -3
- package/src/components/auth/api-key/api-key-skeleton.tsx +17 -0
- package/src/components/auth/api-key/api-key.tsx +64 -0
- package/src/components/auth/api-key/api-keys-empty.tsx +33 -0
- package/src/components/auth/api-key/api-keys.tsx +71 -0
- package/src/components/auth/api-key/create-api-key-dialog.tsx +134 -0
- package/src/components/auth/api-key/delete-api-key-dialog.tsx +92 -0
- package/src/components/auth/api-key/new-api-key-dialog.tsx +94 -0
- package/src/components/auth/multi-session/switch-account-submenu-content.tsx +1 -3
- package/src/components/auth/passkey/add-passkey-dialog.tsx +109 -0
- package/src/components/auth/passkey/delete-passkey-dialog.tsx +95 -0
- package/src/components/auth/passkey/passkey-skeleton.tsx +16 -0
- package/src/components/auth/passkey/passkey.tsx +23 -22
- package/src/components/auth/passkey/passkeys-empty.tsx +35 -0
- package/src/components/auth/passkey/passkeys.tsx +33 -154
- package/src/components/auth/settings/security/linked-account.tsx +2 -4
- package/src/lib/auth/api-key-plugin.ts +15 -0
- package/src/plugins.ts +2 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ApiKeyAuthClient,
|
|
3
|
+
type ListedApiKey,
|
|
4
|
+
useAuth,
|
|
5
|
+
useAuthPlugin,
|
|
6
|
+
useDeleteApiKey
|
|
7
|
+
} from "@better-auth-ui/react"
|
|
8
|
+
import { Key } from "@gravity-ui/icons"
|
|
9
|
+
import {
|
|
10
|
+
AlertDialog,
|
|
11
|
+
Button,
|
|
12
|
+
Input,
|
|
13
|
+
Label,
|
|
14
|
+
Spinner,
|
|
15
|
+
TextField
|
|
16
|
+
} from "@heroui/react"
|
|
17
|
+
|
|
18
|
+
import { apiKeyPlugin } from "../../../lib/auth/api-key-plugin"
|
|
19
|
+
|
|
20
|
+
export type DeleteApiKeyDialogProps = {
|
|
21
|
+
isOpen: boolean
|
|
22
|
+
onOpenChange: (open: boolean) => void
|
|
23
|
+
apiKey: ListedApiKey
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function DeleteApiKeyDialog({
|
|
27
|
+
isOpen,
|
|
28
|
+
onOpenChange,
|
|
29
|
+
apiKey
|
|
30
|
+
}: DeleteApiKeyDialogProps) {
|
|
31
|
+
const { authClient, localization } = useAuth()
|
|
32
|
+
const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)
|
|
33
|
+
const preview = `${apiKey.start}${"*".repeat(16)}`
|
|
34
|
+
const { mutate: deleteApiKey, isPending: isDeleting } = useDeleteApiKey(
|
|
35
|
+
authClient as ApiKeyAuthClient,
|
|
36
|
+
{
|
|
37
|
+
onSuccess: () => onOpenChange(false)
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<AlertDialog.Backdrop isOpen={isOpen} onOpenChange={onOpenChange}>
|
|
43
|
+
<AlertDialog.Container>
|
|
44
|
+
<AlertDialog.Dialog>
|
|
45
|
+
<AlertDialog.CloseTrigger />
|
|
46
|
+
|
|
47
|
+
<AlertDialog.Header>
|
|
48
|
+
<AlertDialog.Icon status="danger">
|
|
49
|
+
<Key />
|
|
50
|
+
</AlertDialog.Icon>
|
|
51
|
+
|
|
52
|
+
<AlertDialog.Heading>
|
|
53
|
+
{apiKeyLocalization.deleteApiKey}
|
|
54
|
+
</AlertDialog.Heading>
|
|
55
|
+
</AlertDialog.Header>
|
|
56
|
+
|
|
57
|
+
<AlertDialog.Body className="flex flex-col gap-4 overflow-visible">
|
|
58
|
+
<p className="text-muted text-sm">
|
|
59
|
+
{apiKeyLocalization.deleteApiKeyWarning}
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
<TextField
|
|
63
|
+
value={preview}
|
|
64
|
+
className="font-mono text-xs"
|
|
65
|
+
variant="secondary"
|
|
66
|
+
>
|
|
67
|
+
<Label>{apiKey.name || apiKeyLocalization.apiKey}</Label>
|
|
68
|
+
|
|
69
|
+
<Input readOnly className="font-mono text-xs" />
|
|
70
|
+
</TextField>
|
|
71
|
+
</AlertDialog.Body>
|
|
72
|
+
|
|
73
|
+
<AlertDialog.Footer>
|
|
74
|
+
<Button slot="close" variant="tertiary" isDisabled={isDeleting}>
|
|
75
|
+
{localization.settings.cancel}
|
|
76
|
+
</Button>
|
|
77
|
+
|
|
78
|
+
<Button
|
|
79
|
+
variant="danger"
|
|
80
|
+
onPress={() => deleteApiKey({ keyId: apiKey.id })}
|
|
81
|
+
isPending={isDeleting}
|
|
82
|
+
>
|
|
83
|
+
{isDeleting && <Spinner color="current" size="sm" />}
|
|
84
|
+
|
|
85
|
+
{apiKeyLocalization.deleteApiKey}
|
|
86
|
+
</Button>
|
|
87
|
+
</AlertDialog.Footer>
|
|
88
|
+
</AlertDialog.Dialog>
|
|
89
|
+
</AlertDialog.Container>
|
|
90
|
+
</AlertDialog.Backdrop>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useAuth, useAuthPlugin } from "@better-auth-ui/react"
|
|
2
|
+
import { Check, Copy, Key } from "@gravity-ui/icons"
|
|
3
|
+
import {
|
|
4
|
+
AlertDialog,
|
|
5
|
+
Button,
|
|
6
|
+
InputGroup,
|
|
7
|
+
Label,
|
|
8
|
+
TextField,
|
|
9
|
+
toast
|
|
10
|
+
} from "@heroui/react"
|
|
11
|
+
import { useState } from "react"
|
|
12
|
+
|
|
13
|
+
import { apiKeyPlugin } from "../../../lib/auth/api-key-plugin"
|
|
14
|
+
|
|
15
|
+
export type NewApiKeyDialogProps = {
|
|
16
|
+
isOpen: boolean
|
|
17
|
+
onOpenChange: (open: boolean) => void
|
|
18
|
+
name: string | null
|
|
19
|
+
secretKey: string | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function NewApiKeyDialog({
|
|
23
|
+
isOpen,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
name,
|
|
26
|
+
secretKey
|
|
27
|
+
}: NewApiKeyDialogProps) {
|
|
28
|
+
const { localization } = useAuth()
|
|
29
|
+
const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)
|
|
30
|
+
|
|
31
|
+
const [copied, setCopied] = useState(false)
|
|
32
|
+
|
|
33
|
+
const copySecretKey = async () => {
|
|
34
|
+
if (!secretKey) return
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await navigator.clipboard.writeText(secretKey)
|
|
38
|
+
setCopied(true)
|
|
39
|
+
setTimeout(() => setCopied(false), 1500)
|
|
40
|
+
} catch (error) {
|
|
41
|
+
toast.danger(error instanceof Error ? error.message : String(error))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<AlertDialog.Backdrop isOpen={isOpen} onOpenChange={onOpenChange}>
|
|
47
|
+
<AlertDialog.Container>
|
|
48
|
+
<AlertDialog.Dialog>
|
|
49
|
+
<AlertDialog.CloseTrigger />
|
|
50
|
+
|
|
51
|
+
<AlertDialog.Header>
|
|
52
|
+
<AlertDialog.Icon status="warning">
|
|
53
|
+
<Key />
|
|
54
|
+
</AlertDialog.Icon>
|
|
55
|
+
|
|
56
|
+
<AlertDialog.Heading>
|
|
57
|
+
{apiKeyLocalization.newApiKey}
|
|
58
|
+
</AlertDialog.Heading>
|
|
59
|
+
</AlertDialog.Header>
|
|
60
|
+
|
|
61
|
+
<AlertDialog.Body className="flex flex-col gap-4 overflow-visible">
|
|
62
|
+
<p className="text-muted text-sm">
|
|
63
|
+
{apiKeyLocalization.newApiKeyWarning}
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
<TextField value={secretKey ?? ""} className="font-mono text-xs">
|
|
67
|
+
<Label>{name || apiKeyLocalization.apiKey}</Label>
|
|
68
|
+
|
|
69
|
+
<InputGroup variant="secondary">
|
|
70
|
+
<InputGroup.Input readOnly className="font-mono text-xs" />
|
|
71
|
+
|
|
72
|
+
<InputGroup.Suffix className="px-0">
|
|
73
|
+
<Button
|
|
74
|
+
isIconOnly
|
|
75
|
+
aria-label={localization.settings.copyToClipboard}
|
|
76
|
+
size="sm"
|
|
77
|
+
variant="ghost"
|
|
78
|
+
onPress={copySecretKey}
|
|
79
|
+
>
|
|
80
|
+
{copied ? <Check /> : <Copy />}
|
|
81
|
+
</Button>
|
|
82
|
+
</InputGroup.Suffix>
|
|
83
|
+
</InputGroup>
|
|
84
|
+
</TextField>
|
|
85
|
+
</AlertDialog.Body>
|
|
86
|
+
|
|
87
|
+
<AlertDialog.Footer>
|
|
88
|
+
<Button slot="close">{apiKeyLocalization.dismissNewKey}</Button>
|
|
89
|
+
</AlertDialog.Footer>
|
|
90
|
+
</AlertDialog.Dialog>
|
|
91
|
+
</AlertDialog.Container>
|
|
92
|
+
</AlertDialog.Backdrop>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useSession
|
|
7
7
|
} from "@better-auth-ui/react"
|
|
8
8
|
import { Check, CirclePlus } from "@gravity-ui/icons"
|
|
9
|
-
import { Dropdown, Label
|
|
9
|
+
import { Dropdown, Label } from "@heroui/react"
|
|
10
10
|
|
|
11
11
|
import { multiSessionPlugin } from "../../../lib/auth/multi-session-plugin"
|
|
12
12
|
import { UserView } from "../user/user-view"
|
|
@@ -50,8 +50,6 @@ export function SwitchAccountSubmenuContent() {
|
|
|
50
50
|
/>
|
|
51
51
|
))}
|
|
52
52
|
|
|
53
|
-
<Separator />
|
|
54
|
-
|
|
55
53
|
<Dropdown.Item
|
|
56
54
|
textValue={multiSessionLocalization.addAccount}
|
|
57
55
|
href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PasskeyAuthClient,
|
|
3
|
+
useAddPasskey,
|
|
4
|
+
useAuth,
|
|
5
|
+
useAuthPlugin
|
|
6
|
+
} from "@better-auth-ui/react"
|
|
7
|
+
import { Fingerprint } from "@gravity-ui/icons"
|
|
8
|
+
import {
|
|
9
|
+
AlertDialog,
|
|
10
|
+
Button,
|
|
11
|
+
FieldError,
|
|
12
|
+
Form,
|
|
13
|
+
Input,
|
|
14
|
+
Label,
|
|
15
|
+
Spinner,
|
|
16
|
+
TextField
|
|
17
|
+
} from "@heroui/react"
|
|
18
|
+
import type { SyntheticEvent } from "react"
|
|
19
|
+
|
|
20
|
+
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
|
|
21
|
+
|
|
22
|
+
export type AddPasskeyDialogProps = {
|
|
23
|
+
isOpen: boolean
|
|
24
|
+
onOpenChange: (open: boolean) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function AddPasskeyDialog({
|
|
28
|
+
isOpen,
|
|
29
|
+
onOpenChange
|
|
30
|
+
}: AddPasskeyDialogProps) {
|
|
31
|
+
const { authClient, localization } = useAuth()
|
|
32
|
+
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
|
|
33
|
+
|
|
34
|
+
const { mutate: addPasskey, isPending: isAdding } = useAddPasskey(
|
|
35
|
+
authClient as PasskeyAuthClient
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const handleOpenChange = (open: boolean) => {
|
|
39
|
+
onOpenChange(open)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
|
|
43
|
+
e.preventDefault()
|
|
44
|
+
|
|
45
|
+
const formData = new FormData(e.target as HTMLFormElement)
|
|
46
|
+
const name = (formData.get("name") as string)?.trim()
|
|
47
|
+
|
|
48
|
+
addPasskey(name ? { name } : undefined, {
|
|
49
|
+
onSuccess: () => handleOpenChange(false)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<AlertDialog.Backdrop isOpen={isOpen} onOpenChange={handleOpenChange}>
|
|
55
|
+
<AlertDialog.Container>
|
|
56
|
+
<AlertDialog.Dialog>
|
|
57
|
+
<Form onSubmit={handleSubmit}>
|
|
58
|
+
<AlertDialog.CloseTrigger />
|
|
59
|
+
|
|
60
|
+
<AlertDialog.Header>
|
|
61
|
+
<AlertDialog.Icon status="default">
|
|
62
|
+
<Fingerprint />
|
|
63
|
+
</AlertDialog.Icon>
|
|
64
|
+
|
|
65
|
+
<AlertDialog.Heading>
|
|
66
|
+
{passkeyLocalization.addPasskey}
|
|
67
|
+
</AlertDialog.Heading>
|
|
68
|
+
</AlertDialog.Header>
|
|
69
|
+
|
|
70
|
+
<AlertDialog.Body className="overflow-visible">
|
|
71
|
+
<p className="text-muted text-sm">
|
|
72
|
+
{passkeyLocalization.passkeysDescription}
|
|
73
|
+
</p>
|
|
74
|
+
|
|
75
|
+
<TextField
|
|
76
|
+
className="mt-4"
|
|
77
|
+
id="name"
|
|
78
|
+
name="name"
|
|
79
|
+
isDisabled={isAdding}
|
|
80
|
+
>
|
|
81
|
+
<Label>{passkeyLocalization.name}</Label>
|
|
82
|
+
|
|
83
|
+
<Input
|
|
84
|
+
autoFocus
|
|
85
|
+
placeholder={localization.settings.optional}
|
|
86
|
+
variant="secondary"
|
|
87
|
+
/>
|
|
88
|
+
|
|
89
|
+
<FieldError />
|
|
90
|
+
</TextField>
|
|
91
|
+
</AlertDialog.Body>
|
|
92
|
+
|
|
93
|
+
<AlertDialog.Footer>
|
|
94
|
+
<Button slot="close" variant="tertiary" isDisabled={isAdding}>
|
|
95
|
+
{localization.settings.cancel}
|
|
96
|
+
</Button>
|
|
97
|
+
|
|
98
|
+
<Button type="submit" isPending={isAdding}>
|
|
99
|
+
{isAdding && <Spinner color="current" size="sm" />}
|
|
100
|
+
|
|
101
|
+
{passkeyLocalization.addPasskey}
|
|
102
|
+
</Button>
|
|
103
|
+
</AlertDialog.Footer>
|
|
104
|
+
</Form>
|
|
105
|
+
</AlertDialog.Dialog>
|
|
106
|
+
</AlertDialog.Container>
|
|
107
|
+
</AlertDialog.Backdrop>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PasskeyAuthClient,
|
|
3
|
+
useAuth,
|
|
4
|
+
useAuthPlugin,
|
|
5
|
+
useDeletePasskey
|
|
6
|
+
} from "@better-auth-ui/react"
|
|
7
|
+
import { Fingerprint } from "@gravity-ui/icons"
|
|
8
|
+
import {
|
|
9
|
+
AlertDialog,
|
|
10
|
+
Button,
|
|
11
|
+
Input,
|
|
12
|
+
Label,
|
|
13
|
+
Spinner,
|
|
14
|
+
TextField
|
|
15
|
+
} from "@heroui/react"
|
|
16
|
+
|
|
17
|
+
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
|
|
18
|
+
|
|
19
|
+
export type ListedPasskey = {
|
|
20
|
+
id: string
|
|
21
|
+
name?: string | null
|
|
22
|
+
createdAt: Date
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type DeletePasskeyDialogProps = {
|
|
26
|
+
isOpen: boolean
|
|
27
|
+
onOpenChange: (open: boolean) => void
|
|
28
|
+
passkey: ListedPasskey
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function DeletePasskeyDialog({
|
|
32
|
+
isOpen,
|
|
33
|
+
onOpenChange,
|
|
34
|
+
passkey
|
|
35
|
+
}: DeletePasskeyDialogProps) {
|
|
36
|
+
const { authClient, localization } = useAuth()
|
|
37
|
+
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
|
|
38
|
+
|
|
39
|
+
const passkeyName = passkey.name || passkeyLocalization.passkey
|
|
40
|
+
|
|
41
|
+
const { mutate: deletePasskey, isPending: isDeleting } = useDeletePasskey(
|
|
42
|
+
authClient as PasskeyAuthClient,
|
|
43
|
+
{
|
|
44
|
+
onSuccess: () => onOpenChange(false)
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<AlertDialog.Backdrop isOpen={isOpen} onOpenChange={onOpenChange}>
|
|
50
|
+
<AlertDialog.Container>
|
|
51
|
+
<AlertDialog.Dialog>
|
|
52
|
+
<AlertDialog.CloseTrigger />
|
|
53
|
+
|
|
54
|
+
<AlertDialog.Header>
|
|
55
|
+
<AlertDialog.Icon status="danger">
|
|
56
|
+
<Fingerprint />
|
|
57
|
+
</AlertDialog.Icon>
|
|
58
|
+
|
|
59
|
+
<AlertDialog.Heading>
|
|
60
|
+
{passkeyLocalization.deletePasskeyTitle}
|
|
61
|
+
</AlertDialog.Heading>
|
|
62
|
+
</AlertDialog.Header>
|
|
63
|
+
|
|
64
|
+
<AlertDialog.Body className="flex flex-col gap-4 overflow-visible">
|
|
65
|
+
<p className="text-muted text-sm">
|
|
66
|
+
{passkeyLocalization.deletePasskeyWarning}
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
<TextField value={passkeyName} variant="secondary">
|
|
70
|
+
<Label>{passkey.name || passkeyLocalization.passkey}</Label>
|
|
71
|
+
|
|
72
|
+
<Input readOnly />
|
|
73
|
+
</TextField>
|
|
74
|
+
</AlertDialog.Body>
|
|
75
|
+
|
|
76
|
+
<AlertDialog.Footer>
|
|
77
|
+
<Button slot="close" variant="tertiary" isDisabled={isDeleting}>
|
|
78
|
+
{localization.settings.cancel}
|
|
79
|
+
</Button>
|
|
80
|
+
|
|
81
|
+
<Button
|
|
82
|
+
variant="danger"
|
|
83
|
+
onPress={() => deletePasskey({ id: passkey.id })}
|
|
84
|
+
isPending={isDeleting}
|
|
85
|
+
>
|
|
86
|
+
{isDeleting && <Spinner color="current" size="sm" />}
|
|
87
|
+
|
|
88
|
+
{passkeyLocalization.deletePasskeyTitle}
|
|
89
|
+
</Button>
|
|
90
|
+
</AlertDialog.Footer>
|
|
91
|
+
</AlertDialog.Dialog>
|
|
92
|
+
</AlertDialog.Container>
|
|
93
|
+
</AlertDialog.Backdrop>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Skeleton } from "@heroui/react"
|
|
2
|
+
|
|
3
|
+
export function PasskeySkeleton() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="flex items-center justify-between">
|
|
6
|
+
<div className="flex items-center gap-3">
|
|
7
|
+
<Skeleton className="size-10 rounded-xl" />
|
|
8
|
+
|
|
9
|
+
<div className="flex flex-col gap-1">
|
|
10
|
+
<Skeleton className="h-4 w-28 rounded-lg" />
|
|
11
|
+
<Skeleton className="h-3 w-32 rounded-lg" />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -1,29 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type PasskeyAuthClient,
|
|
3
|
-
useAuth,
|
|
4
|
-
useAuthPlugin,
|
|
5
|
-
useDeletePasskey
|
|
6
|
-
} from "@better-auth-ui/react"
|
|
1
|
+
import { useAuth, useAuthPlugin } from "@better-auth-ui/react"
|
|
7
2
|
import { Fingerprint, Xmark } from "@gravity-ui/icons"
|
|
8
|
-
import { Button
|
|
3
|
+
import { Button } from "@heroui/react"
|
|
4
|
+
import { useState } from "react"
|
|
9
5
|
|
|
10
6
|
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
|
|
7
|
+
import {
|
|
8
|
+
DeletePasskeyDialog,
|
|
9
|
+
type ListedPasskey
|
|
10
|
+
} from "./delete-passkey-dialog"
|
|
11
11
|
|
|
12
12
|
export type PasskeyProps = {
|
|
13
|
-
passkey:
|
|
14
|
-
id: string
|
|
15
|
-
name?: string | null
|
|
16
|
-
createdAt: Date
|
|
17
|
-
}
|
|
13
|
+
passkey: ListedPasskey
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
export function Passkey({ passkey }: PasskeyProps) {
|
|
21
|
-
const {
|
|
17
|
+
const { localization } = useAuth()
|
|
22
18
|
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
|
|
23
|
-
|
|
24
|
-
const { mutate: deletePasskey, isPending } = useDeletePasskey(
|
|
25
|
-
authClient as PasskeyAuthClient
|
|
26
|
-
)
|
|
19
|
+
const [deleteOpen, setDeleteOpen] = useState(false)
|
|
27
20
|
|
|
28
21
|
const passkeyName = passkey.name || passkeyLocalization.passkey
|
|
29
22
|
|
|
@@ -33,8 +26,10 @@ export function Passkey({ passkey }: PasskeyProps) {
|
|
|
33
26
|
<Fingerprint className="size-4.5" />
|
|
34
27
|
</div>
|
|
35
28
|
|
|
36
|
-
<div className="flex
|
|
37
|
-
<span className="text-sm font-medium leading-tight">
|
|
29
|
+
<div className="flex min-w-0 flex-col">
|
|
30
|
+
<span className="truncate text-sm font-medium leading-tight">
|
|
31
|
+
{passkeyName}
|
|
32
|
+
</span>
|
|
38
33
|
|
|
39
34
|
<span className="text-xs text-muted">
|
|
40
35
|
{new Date(passkey.createdAt).toLocaleString(undefined, {
|
|
@@ -48,16 +43,22 @@ export function Passkey({ passkey }: PasskeyProps) {
|
|
|
48
43
|
className="ml-auto shrink-0"
|
|
49
44
|
variant="outline"
|
|
50
45
|
size="sm"
|
|
51
|
-
|
|
52
|
-
onPress={() => deletePasskey({ id: passkey.id })}
|
|
46
|
+
onPress={() => setDeleteOpen(true)}
|
|
53
47
|
aria-label={passkeyLocalization.deletePasskey.replace(
|
|
54
48
|
"{{name}}",
|
|
55
49
|
passkeyName
|
|
56
50
|
)}
|
|
57
51
|
>
|
|
58
|
-
|
|
52
|
+
<Xmark />
|
|
53
|
+
|
|
59
54
|
{localization.settings.delete}
|
|
60
55
|
</Button>
|
|
56
|
+
|
|
57
|
+
<DeletePasskeyDialog
|
|
58
|
+
isOpen={deleteOpen}
|
|
59
|
+
onOpenChange={setDeleteOpen}
|
|
60
|
+
passkey={passkey}
|
|
61
|
+
/>
|
|
61
62
|
</div>
|
|
62
63
|
)
|
|
63
64
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useAuthPlugin } from "@better-auth-ui/react"
|
|
2
|
+
import { Fingerprint } from "@gravity-ui/icons"
|
|
3
|
+
import { Button } from "@heroui/react"
|
|
4
|
+
|
|
5
|
+
import { passkeyPlugin } from "../../../lib/auth/passkey-plugin"
|
|
6
|
+
|
|
7
|
+
export type PasskeysEmptyProps = {
|
|
8
|
+
onAddPress: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function PasskeysEmpty({ onAddPress }: PasskeysEmptyProps) {
|
|
12
|
+
const { localization: passkeyLocalization } = useAuthPlugin(passkeyPlugin)
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex flex-col items-center justify-center gap-4">
|
|
16
|
+
<div className="flex size-10 items-center justify-center rounded-xl bg-surface-secondary">
|
|
17
|
+
<Fingerprint className="size-4.5" />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div className="flex flex-col items-center justify-center gap-1 text-center">
|
|
21
|
+
<p className="text-sm font-semibold">
|
|
22
|
+
{passkeyLocalization.noPasskeys}
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<p className="text-muted text-xs">
|
|
26
|
+
{passkeyLocalization.passkeysDescription}
|
|
27
|
+
</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<Button size="sm" onPress={onAddPress}>
|
|
31
|
+
{passkeyLocalization.addPasskey}
|
|
32
|
+
</Button>
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|