@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
282 lines • 8.24 kB
TypeScript
import { AuthError } from '../provider/types';
export interface UseMagicLinkReturn {
isLoading: boolean;
error: AuthError | null;
lastSentEmail: string | null;
lastSentAt: Date | null;
canResend: boolean;
timeUntilResend: number;
sendMagicLink: (email: string, options?: MagicLinkOptions) => Promise<MagicLinkSendResult>;
verifyMagicLink: (token: string) => Promise<MagicLinkVerifyResult>;
resendMagicLink: () => Promise<MagicLinkSendResult>;
verifyFromUrl: (url?: string) => Promise<MagicLinkVerifyResult>;
extractTokenFromUrl: (url?: string) => string | null;
isValidEmail: (email: string) => boolean;
clearState: () => void;
}
export interface MagicLinkOptions {
redirectUrl?: string;
organizationId?: string;
customData?: Record<string, any>;
template?: string;
expiresIn?: number;
locale?: string;
}
export interface MagicLinkSendResult {
success: boolean;
email: string;
message: string;
expiresAt: Date;
error?: string;
}
export interface MagicLinkVerifyResult {
success: boolean;
user?: any;
session?: any;
error?: string;
requiresAdditionalVerification?: boolean;
mfaToken?: string;
}
export declare const MAGIC_LINK_CONFIG: {
readonly DEFAULT_EXPIRES_IN: number;
readonly RESEND_COOLDOWN: 60;
readonly EMAIL_REGEX: RegExp;
readonly TEMPLATES: {
readonly SIGN_IN: "magic-link-sign-in";
readonly SIGN_UP: "magic-link-sign-up";
readonly VERIFY_EMAIL: "magic-link-verify-email";
readonly PASSWORD_RESET: "magic-link-password-reset";
};
readonly URL_PARAMS: {
readonly TOKEN: "token";
readonly EMAIL: "email";
readonly TYPE: "type";
readonly REDIRECT: "redirect_to";
};
};
/**
* Magic link authentication hook for passwordless email authentication
*
* @example Basic magic link sign-in
* ```tsx
* import { useMagicLink } from '@frank-auth/react';
*
* function MagicLinkSignIn() {
* const {
* sendMagicLink,
* isLoading,
* error,
* lastSentEmail,
* canResend,
* resendMagicLink,
* isValidEmail
* } = useMagicLink();
*
* const [email, setEmail] = useState('');
* const [sent, setSent] = useState(false);
*
* const handleSend = async () => {
* if (!isValidEmail(email)) {
* alert('Please enter a valid email address');
* return;
* }
*
* try {
* const result = await sendMagicLink(email, {
* redirectUrl: '/dashboard',
* template: 'sign-in'
* });
*
* if (result.success) {
* setSent(true);
* }
* } catch (error) {
* console.error('Failed to send magic link:', error);
* }
* };
*
* const handleResend = async () => {
* try {
* await resendMagicLink();
* } catch (error) {
* console.error('Failed to resend magic link:', error);
* }
* };
*
* if (sent) {
* return (
* <div>
* <h3>Check your email</h3>
* <p>We sent a magic link to {lastSentEmail}</p>
* <p>Click the link in your email to sign in.</p>
*
* {canResend ? (
* <button onClick={handleResend} disabled={isLoading}>
* Resend magic link
* </button>
* ) : (
* <p>You can resend the link in a few seconds</p>
* )}
*
* {error && <p style={{color: 'red'}}>{error.message}</p>}
* </div>
* );
* }
*
* return (
* <div>
* <h3>Sign in with magic link</h3>
* <input
* type="email"
* value={email}
* onChange={(e) => setEmail(e.target.value)}
* placeholder="Enter your email address"
* disabled={isLoading}
* />
* <button onClick={handleSend} disabled={isLoading || !email}>
* {isLoading ? 'Sending...' : 'Send magic link'}
* </button>
*
* {error && <p style={{color: 'red'}}>{error.message}</p>}
* </div>
* );
* }
* ```
*
* @example Magic link verification page
* ```tsx
* import { useEffect, useState } from 'react';
* import { useMagicLink } from '@frank-auth/react';
* import { useSearchParams, useNavigate } from 'react-router-dom';
*
* function MagicLinkVerify() {
* const { verifyFromUrl, isLoading } = useMagicLink();
* const [searchParams] = useSearchParams();
* const navigate = useNavigate();
* const [status, setStatus] = useState('verifying');
*
* useEffect(() => {
* const verify = async () => {
* try {
* const result = await verifyFromUrl();
*
* if (result.success) {
* setStatus('success');
*
* // Check for MFA requirement
* if (result.requiresAdditionalVerification) {
* navigate('/mfa', { state: { mfaToken: result.mfaToken } });
* } else {
* // Redirect to dashboard or intended page
* const redirectTo = searchParams.get('redirect_to') || '/dashboard';
* navigate(redirectTo);
* }
* } else {
* setStatus('error');
* }
* } catch (error) {
* console.error('Magic link verification failed:', error);
* setStatus('error');
* }
* };
*
* verify();
* }, [verifyFromUrl, navigate, searchParams]);
*
* if (isLoading || status === 'verifying') {
* return <div>Verifying magic link...</div>;
* }
*
* if (status === 'success') {
* return <div>Success! Redirecting...</div>;
* }
*
* return (
* <div>
* <h3>Invalid or expired magic link</h3>
* <p>The magic link you clicked is invalid or has expired.</p>
* <button onClick={() => navigate('/sign-in')}>
* Try again
* </button>
* </div>
* );
* }
* ```
*
* @example Magic link with organization invitation
* ```tsx
* function OrganizationInvite({ invitationToken, organizationId }) {
* const { sendMagicLink } = useMagicLink();
* const [email, setEmail] = useState('');
*
* const handleAcceptInvite = async () => {
* try {
* await sendMagicLink(email, {
* organizationId,
* customData: {
* invitationToken,
* action: 'accept_invitation'
* },
* template: 'organization-invite',
* redirectUrl: `/organizations/${organizationId}/welcome`
* });
* } catch (error) {
* console.error('Failed to send invitation magic link:', error);
* }
* };
*
* return (
* <div>
* <h3>Join Organization</h3>
* <p>Enter your email to receive a secure link to join the organization.</p>
* <input
* type="email"
* value={email}
* onChange={(e) => setEmail(e.target.value)}
* placeholder="Enter your email address"
* />
* <button onClick={handleAcceptInvite}>
* Send invitation link
* </button>
* </div>
* );
* }
* ```
*/
export declare function useMagicLink(): UseMagicLinkReturn;
/**
* Hook for magic link sign-in flow
*/
export declare function useMagicLinkSignIn(): {
signIn: (email: string, redirectUrl?: string) => Promise<MagicLinkSendResult>;
resend: () => Promise<MagicLinkSendResult>;
reset: () => void;
state: "idle" | "verified" | "email_sent";
sentTo: string | null;
canResend: boolean;
isLoading: boolean;
error: AuthError | null;
isValidEmail: (email: string) => boolean;
};
/**
* Hook for magic link verification flow
*/
export declare function useMagicLinkVerification(): {
verify: (token?: string) => Promise<MagicLinkVerifyResult>;
state: "idle" | "success" | "error" | "verifying";
result: MagicLinkVerifyResult | null;
isVerifying: boolean;
isSuccess: boolean;
isError: boolean;
error: string | AuthError | undefined;
requiresMFA: boolean | undefined;
mfaToken: string | undefined;
};
/**
* Hook for magic link password reset flow
*/
export declare function useMagicLinkPasswordReset(): {
sendResetLink: (email: string) => Promise<MagicLinkSendResult>;
isValidEmail: (email: string) => boolean;
};
//# sourceMappingURL=use-magic-link.d.ts.map