diff --git a/docs/hooks/useSignIn.md b/docs/hooks/useSignIn.md index dd3336e..2bf3064 100644 --- a/docs/hooks/useSignIn.md +++ b/docs/hooks/useSignIn.md @@ -28,13 +28,13 @@ await dispatch({ email, password, }); -// `state` is "ready" | "loading" | "authenticated" +// `state` is "ready" | "loading" | "authenticated" | "awaiting" ``` !!! warning `useSignIn` automatically listens to authentication state and will be `"authenticated"` if the user is authenticated. In `"authenticated"` state, `dispatch` will simply do nothing even if it is invoked. -`dispatch` method will return an instance of [`UserCredential`][UserCredentialDocRef]. +`dispatch` function will return an instance of [`UserCredential` | `undefined`][UserCredentialDocRef]. ```typescript const { state, dispatch } = useSignIn({ auth }); @@ -70,6 +70,7 @@ There are many sign-in methods available. The available ones are: | Method | `type` | `provider` | |---|---|---| | Email and password | `classic` | ❌ | +| Email link | `link` | ❌ | | Google | `google` | [`GoogleAuthProvider`][GoogleAuthProviderRefDoc] | | Facebook | `facebook` | [`FacebookAuthProvider`][FacebookAuthProviderRefDoc] | | Apple | `apple` | [`OAuthProvider`][OAuthProviderRefDoc] | @@ -78,19 +79,44 @@ There are many sign-in methods available. The available ones are: | Twitter | `twitter` | [`TwitterAuthProvider`][TwitterAuthProviderRefDoc] | | Github | `github` | [`GithubAuthProvider`][GithubAuthProviderRefDoc] | -`dispatch` method will require an object as parameter. This object will always have property of `type: string`. `type` will correspond to what kind of method you will prefer while signing in a visitor. +`dispatch` function will require an object as parameter. This object will always have property of `type: string`. `type` will correspond to what kind of method you will prefer while signing in a visitor. + +### Classic (Email and Password) Sign In Method If `type` is `"classic"` (email-password authentication), it's pretty simple: ```typescript -await dispatch({ +const credential = await dispatch({ type: "classic", email, password, }); ``` -If `type` is something else, you need to provide initialized `*AuthProvider` instance. An example for Google sign-in looks as such: +If no verification email was sent and the user is not verified, your `credential!.user.emailVerified` will return `false`. + +!!! note + Sending verification email feature has not been implemented by this library yet. It will be implemented in a future release. + +### Email Link Sign In Method + +If `type` is `"link"`, the example would be: + +```typescript +await dispatch({ + type: "link", + email, + actionCodeSettings, +}); +``` + +You will need `actionCodeSettings`. You can see [this section in Firebase Auth documentation](https://firebase.google.com/docs/auth/web/email-link-auth#send_an_authentication_link_to_the_users_email_address) to see it. + +`"link"` is **the only type that will return `undefined` credential** as the user has to click the link in the email to get authenticated. Also, it is **the only method to change the state to `"awaiting"`**. + +### Other Methods + +If `type` is something else, you need to provide an implementation of `AuthProvider`. An example for Google sign-in looks as such: ```typescript const { state, dispatch } = useSignIn({ auth }); @@ -99,7 +125,7 @@ await dispatch({ type: "google", provider, }); -// state is "loading" until user signs in +// state is "loading" until user successfully signs in, then "authenticated" ``` !!! warning diff --git a/src/auth/useSignIn.hook.test.ts b/src/auth/useSignIn.hook.test.ts index 5c0c80d..f06f482 100644 --- a/src/auth/useSignIn.hook.test.ts +++ b/src/auth/useSignIn.hook.test.ts @@ -74,10 +74,12 @@ describe("when anon, useSignIn hook", () => { const { dispatch } = result.current; const credential = await dispatch({ type: "classic", email, password }); - expect(credential.user.email).toBe(email); + expect(credential?.user.email).toBe(email); // teardown await signOut(auth); - await deleteUser(credential.user); + if (credential) { + await deleteUser(credential.user); + } }); }); diff --git a/src/auth/useSignIn.ts b/src/auth/useSignIn.ts index 58bb039..be1d51c 100644 --- a/src/auth/useSignIn.ts +++ b/src/auth/useSignIn.ts @@ -11,9 +11,11 @@ import { OAuthProvider, TwitterAuthProvider, UserCredential, + sendSignInLinkToEmail, signInWithEmailAndPassword, signInWithPopup, } from "@firebase/auth"; +import { ActionCodeSettings } from "firebase/auth"; import { useEffect, useState } from "react"; import { useUser } from "."; @@ -21,7 +23,7 @@ type UseSignInParams = { auth: Auth; }; -type UseSignInState = "ready" | "loading" | "authenticated"; +type UseSignInState = "ready" | "loading" | "authenticated" | "awaiting"; type UseSignInDispatcher = ( params: | { @@ -29,6 +31,11 @@ type UseSignInDispatcher = ( email: string; password: string; } + | { + type: "link"; + email: string; + actionCodeSetting: ActionCodeSettings; + } | { type: "google"; provider: GoogleAuthProvider; @@ -49,7 +56,7 @@ type UseSignInDispatcher = ( type: "github"; provider: GithubAuthProvider; }, -) => Promise; +) => Promise; type UseSignIn = { state: UseSignInState; dispatch: UseSignInDispatcher; @@ -79,6 +86,12 @@ export const useSignIn = ({ auth }: UseSignInParams): UseSignIn => { setState("authenticated"); return credential; } + case "link": { + const { email, actionCodeSetting } = params; + await sendSignInLinkToEmail(auth, email, actionCodeSetting); + setState("awaiting"); + return undefined; + } case "google": { const { provider } = params; const credential = await signInWithPopup(auth, provider);