+
-
- {result.error && !result.loading ? (
+ {isPasskeySupported() && (
+
+
+
+
+
+ {t("auth.login.or")}
+
+
+
+
+
+ )}
+ {(result.error || passkeyResult.error) &&
+ !result.loading &&
+ !passkeyResult.loading ? (
- {result.error.message}
+ {result.error?.message || passkeyResult.error?.message}
) : null}
diff --git a/src/pages/parts/auth/PassphraseGeneratePart.tsx b/src/pages/parts/auth/PassphraseGeneratePart.tsx
index 444b217f..498772f7 100644
--- a/src/pages/parts/auth/PassphraseGeneratePart.tsx
+++ b/src/pages/parts/auth/PassphraseGeneratePart.tsx
@@ -1,7 +1,12 @@
import { useCallback, useState } from "react";
import { Trans, useTranslation } from "react-i18next";
+import { useAsyncFn } from "react-use";
-import { genMnemonic } from "@/backend/accounts/crypto";
+import {
+ createPasskey,
+ genMnemonic,
+ isPasskeySupported,
+} from "@/backend/accounts/crypto";
import { Button } from "@/components/buttons/Button";
import { PassphraseDisplay } from "@/components/form/PassphraseDisplay";
import { Icon, Icons } from "@/components/Icon";
@@ -13,6 +18,7 @@ import {
interface PassphraseGeneratePartProps {
onNext?: (mnemonic: string) => void;
+ onPasskeyNext?: (credentialId: string) => void;
}
export function PassphraseGeneratePart(props: PassphraseGeneratePartProps) {
@@ -23,6 +29,29 @@ export function PassphraseGeneratePart(props: PassphraseGeneratePartProps) {
setMnemonic(customPassphrase);
}, []);
+ const [passkeyResult, createPasskeyFn] = useAsyncFn(async () => {
+ if (!isPasskeySupported()) {
+ throw new Error("Passkeys are not supported in this browser");
+ }
+
+ const credential = await createPasskey(
+ `user-${Date.now()}`,
+ "P-Stream User",
+ );
+ return credential.id;
+ }, []);
+
+ const handlePasskeyClick = useCallback(async () => {
+ try {
+ const credentialId = await createPasskeyFn();
+ if (credentialId) {
+ props.onPasskeyNext?.(credentialId);
+ }
+ } catch (error) {
+ // Error is handled by passkeyResult.error
+ }
+ }, [createPasskeyFn, props]);
+
return (