feat: adds gql auth example (#2115)
This commit is contained in:
34
examples/auth/nextjs/components/Auth/gql.ts
Normal file
34
examples/auth/nextjs/components/Auth/gql.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export const USER = `
|
||||||
|
id
|
||||||
|
email
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
`
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const gql = async (query): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/graphql`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
query,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data, errors } = await res.json()
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
throw new Error(errors[0].message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.ok && data) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
} catch (e: unknown) {
|
||||||
|
throw new Error(e as string)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,67 +1,157 @@
|
|||||||
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { User } from '../../payload-types'
|
import { User } from '../../payload-types'
|
||||||
|
import { gql, USER } from './gql'
|
||||||
type Login = (args: { email: string; password: string }) => Promise<void> // eslint-disable-line no-unused-vars
|
import { rest } from './rest'
|
||||||
|
import { AuthContext, Create, ForgotPassword, Login, Logout, ResetPassword } from './types'
|
||||||
type Logout = () => Promise<void>
|
|
||||||
|
|
||||||
type AuthContext = {
|
|
||||||
user?: User | null
|
|
||||||
setUser: (user: User | null) => void // eslint-disable-line no-unused-vars
|
|
||||||
logout: Logout
|
|
||||||
login: Login
|
|
||||||
}
|
|
||||||
|
|
||||||
const Context = createContext({} as AuthContext)
|
const Context = createContext({} as AuthContext)
|
||||||
|
|
||||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const AuthProvider: React.FC<{ children: React.ReactNode; api?: 'rest' | 'gql' }> = ({
|
||||||
|
children,
|
||||||
|
api = 'rest',
|
||||||
|
}) => {
|
||||||
const [user, setUser] = useState<User | null>()
|
const [user, setUser] = useState<User | null>()
|
||||||
|
|
||||||
const login = useCallback<Login>(async args => {
|
const create = useCallback<Create>(
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/login`, {
|
async args => {
|
||||||
method: 'POST',
|
if (api === 'rest') {
|
||||||
body: JSON.stringify(args),
|
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users`, args)
|
||||||
credentials: 'include',
|
setUser(user)
|
||||||
headers: {
|
return user
|
||||||
'Content-Type': 'application/json',
|
}
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.ok) {
|
if (api === 'gql') {
|
||||||
const json = await res.json()
|
const { createUser: user } = await gql(`mutation {
|
||||||
setUser(json.user)
|
createUser(data: { email: "${args.email}", password: "${args.password}", firstName: "${args.firstName}", lastName: "${args.lastName}" }) {
|
||||||
} else {
|
${USER}
|
||||||
throw new Error('Invalid login')
|
}
|
||||||
}
|
}`)
|
||||||
}, [])
|
|
||||||
|
setUser(user)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[api],
|
||||||
|
)
|
||||||
|
|
||||||
|
const login = useCallback<Login>(
|
||||||
|
async args => {
|
||||||
|
if (api === 'rest') {
|
||||||
|
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/login`, args)
|
||||||
|
setUser(user)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api === 'gql') {
|
||||||
|
const { loginUser } = await gql(`mutation {
|
||||||
|
loginUser(email: "${args.email}", password: "${args.password}") {
|
||||||
|
user {
|
||||||
|
${USER}
|
||||||
|
}
|
||||||
|
exp
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
setUser(loginUser?.user)
|
||||||
|
return loginUser?.user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[api],
|
||||||
|
)
|
||||||
|
|
||||||
const logout = useCallback<Logout>(async () => {
|
const logout = useCallback<Logout>(async () => {
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/logout`, {
|
if (api === 'rest') {
|
||||||
method: 'POST',
|
await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/logout`)
|
||||||
// Make sure to include cookies with fetch
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
setUser(null)
|
setUser(null)
|
||||||
} else {
|
return
|
||||||
throw new Error('There was a problem while logging out.')
|
|
||||||
}
|
}
|
||||||
}, [])
|
|
||||||
|
if (api === 'gql') {
|
||||||
|
await gql(`mutation {
|
||||||
|
logoutUser
|
||||||
|
}`)
|
||||||
|
|
||||||
|
setUser(null)
|
||||||
|
}
|
||||||
|
}, [api])
|
||||||
|
|
||||||
// On mount, get user and set
|
// On mount, get user and set
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchMe = async () => {
|
const fetchMe = async () => {
|
||||||
const result = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`, {
|
if (api === 'rest') {
|
||||||
// Make sure to include cookies with fetch
|
const user = await rest(
|
||||||
credentials: 'include',
|
`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/me`,
|
||||||
}).then(req => req.json())
|
{},
|
||||||
setUser(result.user || null)
|
{
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
setUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api === 'gql') {
|
||||||
|
const { meUser } = await gql(`query {
|
||||||
|
meUser {
|
||||||
|
user {
|
||||||
|
${USER}
|
||||||
|
}
|
||||||
|
exp
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
setUser(meUser.user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchMe()
|
fetchMe()
|
||||||
}, [])
|
}, [api])
|
||||||
|
|
||||||
|
const forgotPassword = useCallback<ForgotPassword>(
|
||||||
|
async args => {
|
||||||
|
if (api === 'rest') {
|
||||||
|
const user = await rest(
|
||||||
|
`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/forgot-password`,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
setUser(user)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api === 'gql') {
|
||||||
|
const { forgotPasswordUser } = await gql(`mutation {
|
||||||
|
forgotPasswordUser(email: "${args.email}")
|
||||||
|
}`)
|
||||||
|
|
||||||
|
return forgotPasswordUser
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[api],
|
||||||
|
)
|
||||||
|
|
||||||
|
const resetPassword = useCallback<ResetPassword>(
|
||||||
|
async args => {
|
||||||
|
if (api === 'rest') {
|
||||||
|
const user = await rest(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/reset-password`, args)
|
||||||
|
setUser(user)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api === 'gql') {
|
||||||
|
const { resetPasswordUser } = await gql(`mutation {
|
||||||
|
resetPasswordUser(password: "${args.password}", token: "${args.token}") {
|
||||||
|
user {
|
||||||
|
${USER}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
setUser(resetPasswordUser.user)
|
||||||
|
return resetPasswordUser.user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[api],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Context.Provider
|
<Context.Provider
|
||||||
@@ -70,6 +160,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
setUser,
|
setUser,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
|
create,
|
||||||
|
resetPassword,
|
||||||
|
forgotPassword,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
34
examples/auth/nextjs/components/Auth/rest.ts
Normal file
34
examples/auth/nextjs/components/Auth/rest.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { User } from '../../payload-types'
|
||||||
|
|
||||||
|
export const rest = async (
|
||||||
|
url: string,
|
||||||
|
args?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
options?: RequestInit,
|
||||||
|
): Promise<User | null> => {
|
||||||
|
const method = options?.method || 'POST'
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
|
...(method === 'POST' ? { body: JSON.stringify(args) } : {}),
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options?.headers,
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { errors, user } = await res.json()
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
throw new Error(errors[0].message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
} catch (e: unknown) {
|
||||||
|
throw new Error(e as string)
|
||||||
|
}
|
||||||
|
}
|
||||||
31
examples/auth/nextjs/components/Auth/types.ts
Normal file
31
examples/auth/nextjs/components/Auth/types.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import type { User } from '../../payload-types'
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
export type ResetPassword = (args: {
|
||||||
|
password: string
|
||||||
|
passwordConfirm: string
|
||||||
|
token: string
|
||||||
|
}) => Promise<User>
|
||||||
|
|
||||||
|
export type ForgotPassword = (args: { email: string }) => Promise<User> // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
|
export type Create = (args: {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
}) => Promise<User> // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
|
export type Login = (args: { email: string; password: string }) => Promise<User> // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
|
export type Logout = () => Promise<void>
|
||||||
|
|
||||||
|
export interface AuthContext {
|
||||||
|
user?: User | null
|
||||||
|
setUser: (user: User | null) => void // eslint-disable-line no-unused-vars
|
||||||
|
logout: Logout
|
||||||
|
login: Login
|
||||||
|
create: Create
|
||||||
|
resetPassword: ResetPassword
|
||||||
|
forgotPassword: ForgotPassword
|
||||||
|
}
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react'
|
||||||
import { FieldValues, UseFormRegister } from 'react-hook-form';
|
import { FieldValues, UseFormRegister } from 'react-hook-form'
|
||||||
|
|
||||||
import classes from './index.module.css';
|
import classes from './index.module.css'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string;
|
name: string
|
||||||
label: string;
|
label: string
|
||||||
register: UseFormRegister<FieldValues & any>;
|
register: UseFormRegister<FieldValues & any>
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
error: any;
|
error: any
|
||||||
type?: 'text' | 'number' | 'password';
|
type?: 'text' | 'number' | 'password'
|
||||||
};
|
}
|
||||||
|
|
||||||
export const Input: React.FC<Props> = ({ name, label, required, register, error, type = 'text' }) => {
|
export const Input: React.FC<Props> = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
required,
|
||||||
|
register,
|
||||||
|
error,
|
||||||
|
type = 'text',
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={classes.input}>
|
<div className={classes.input}>
|
||||||
<label htmlFor="name" className={classes.label}>
|
<label htmlFor="name" className={classes.label}>
|
||||||
@@ -21,5 +28,5 @@ export const Input: React.FC<Props> = ({ name, label, required, register, error,
|
|||||||
<input {...{ type }} {...register(name, { required })} />
|
<input {...{ type }} {...register(name, { required })} />
|
||||||
{error && <div className={classes.error}>This field is required</div>}
|
{error && <div className={classes.error}>This field is required</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import '../css/app.scss'
|
|||||||
|
|
||||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<AuthProvider>
|
// The `AuthProvider` can be used with either REST or GraphQL APIs
|
||||||
|
// Just change the `api` prop to "graphql" or "rest", that's it!
|
||||||
|
<AuthProvider api="rest">
|
||||||
<Header />
|
<Header />
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.success,
|
.success,
|
||||||
.error {
|
.error,
|
||||||
margin-bottom: 15px;
|
.message {
|
||||||
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
@@ -13,4 +14,4 @@
|
|||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ const Account: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Gutter>
|
<Gutter>
|
||||||
<h1>Account</h1>
|
<h1>Account</h1>
|
||||||
|
{router.query.message && <div className={classes.message}>{router.query.message}</div>}
|
||||||
{error && <div className={classes.error}>{error}</div>}
|
{error && <div className={classes.error}>{error}</div>}
|
||||||
{success && <div className={classes.success}>{success}</div>}
|
{success && <div className={classes.success}>{success}</div>}
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className={classes.form}>
|
<form onSubmit={handleSubmit(onSubmit)} className={classes.form}>
|
||||||
|
|||||||
@@ -4,4 +4,5 @@
|
|||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type FormData = {
|
|||||||
const CreateAccount: React.FC = () => {
|
const CreateAccount: React.FC = () => {
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [success, setSuccess] = useState(false)
|
const [success, setSuccess] = useState(false)
|
||||||
const { login } = useAuth()
|
const { login, create, user } = useAuth()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -27,28 +27,16 @@ const CreateAccount: React.FC = () => {
|
|||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async (data: FormData) => {
|
async (data: FormData) => {
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users`, {
|
try {
|
||||||
method: 'POST',
|
await create(data as Parameters<typeof create>[0])
|
||||||
body: JSON.stringify(data),
|
// Automatically log the user in after creating their account
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
// Automatically log the user in
|
|
||||||
await login({ email: data.email, password: data.password })
|
await login({ email: data.email, password: data.password })
|
||||||
|
|
||||||
// Set success message for user
|
|
||||||
setSuccess(true)
|
setSuccess(true)
|
||||||
|
} catch (err) {
|
||||||
// Clear any existing errors
|
setError(err?.message || 'An error occurred while attempting to create your account.')
|
||||||
setError('')
|
|
||||||
} else {
|
|
||||||
setError('There was a problem creating your account. Please try again later.')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[login],
|
[login, create],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ const Home: React.FC = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
{". This example demonstrates how to implement Payload's "}
|
{". This example demonstrates how to implement Payload's "}
|
||||||
<Link href="https://payloadcms.com/docs/authentication/overview">Authentication</Link>
|
<Link href="https://payloadcms.com/docs/authentication/overview">Authentication</Link>
|
||||||
{' strategies.'}
|
{' strategies in both the REST and GraphQL APIs.'}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{'Visit the '}
|
{'Visit the '}
|
||||||
<Link href="/login">Login</Link>
|
<Link href="/login">Login</Link>
|
||||||
{' page to start the authentication flow. Once logged in, you will be redirected to the '}
|
{' page to start the authentication flow. Once logged in, you will be redirected to the '}
|
||||||
<Link href="/account">Account</Link>
|
<Link href="/account">Account</Link>
|
||||||
{" page which is restricted to user's only."}
|
{` page which is restricted to user's only. To toggle APIs, simply toggle the "api" prop in _app.tsx between "rest" and "gql".`}
|
||||||
</p>
|
</p>
|
||||||
</Gutter>
|
</Gutter>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ const Login: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
await login(data)
|
await login(data)
|
||||||
router.push('/account')
|
router.push('/account')
|
||||||
} catch (_) {
|
} catch (err) {
|
||||||
setError('There was an error with the credentials provided. Please try again.')
|
setError(err?.message || 'An error occurred while attempting to login.')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[login, router],
|
[login, router],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.error {
|
.error {
|
||||||
color: red;
|
color: red;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ const Logout: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
await logout()
|
await logout()
|
||||||
setSuccess('Logged out successfully.')
|
setSuccess('Logged out successfully.')
|
||||||
} catch (_) {
|
} catch (err) {
|
||||||
setError('You are already logged out.')
|
setError(err?.message || 'An error occurred while attempting to logout.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.error {
|
.error {
|
||||||
color: red;
|
color: red;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
|
|
||||||
|
import { useAuth } from '../../components/Auth'
|
||||||
import { Gutter } from '../../components/Gutter'
|
import { Gutter } from '../../components/Gutter'
|
||||||
import { Input } from '../../components/Input'
|
import { Input } from '../../components/Input'
|
||||||
import classes from './index.module.css'
|
import classes from './index.module.css'
|
||||||
@@ -12,6 +13,7 @@ type FormData = {
|
|||||||
const RecoverPassword: React.FC = () => {
|
const RecoverPassword: React.FC = () => {
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [success, setSuccess] = useState(false)
|
const [success, setSuccess] = useState(false)
|
||||||
|
const { forgotPassword } = useAuth()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -19,27 +21,21 @@ const RecoverPassword: React.FC = () => {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<FormData>()
|
} = useForm<FormData>()
|
||||||
|
|
||||||
const onSubmit = useCallback(async (data: FormData) => {
|
const onSubmit = useCallback(
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/forgot-password`, {
|
async (data: FormData) => {
|
||||||
method: 'POST',
|
try {
|
||||||
body: JSON.stringify(data),
|
const user = await forgotPassword(data as Parameters<typeof forgotPassword>[0])
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (user) {
|
||||||
// Set success message for user
|
setSuccess(true)
|
||||||
setSuccess(true)
|
setError('')
|
||||||
|
}
|
||||||
// Clear any existing errors
|
} catch (err) {
|
||||||
setError('')
|
setError(err?.message || 'An error occurred while attempting to recover password.')
|
||||||
} else {
|
}
|
||||||
setError(
|
},
|
||||||
'There was a problem while attempting to send you a password reset email. Please try again later.',
|
[forgotPassword],
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Gutter>
|
<Gutter>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type FormData = {
|
|||||||
|
|
||||||
const ResetPassword: React.FC = () => {
|
const ResetPassword: React.FC = () => {
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const { login } = useAuth()
|
const { login, resetPassword } = useAuth()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const token = typeof router.query.token === 'string' ? router.query.token : undefined
|
const token = typeof router.query.token === 'string' ? router.query.token : undefined
|
||||||
@@ -28,31 +28,23 @@ const ResetPassword: React.FC = () => {
|
|||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async (data: FormData) => {
|
async (data: FormData) => {
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_URL}/api/users/reset-password`, {
|
try {
|
||||||
method: 'POST',
|
const user = await resetPassword(data as Parameters<typeof resetPassword>[0])
|
||||||
body: JSON.stringify(data),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (user) {
|
||||||
const json = await response.json()
|
// Automatically log the user in after they successfully reset password
|
||||||
|
// Then redirect them to /account with success message in URL
|
||||||
// Automatically log the user in after they successfully reset password
|
await login({ email: user.email, password: data.password })
|
||||||
await login({ email: json.user.email, password: data.password })
|
router.push('/account?success=Password reset successfully.')
|
||||||
|
}
|
||||||
// Redirect them to /account with success message in URL
|
} catch (err) {
|
||||||
router.push('/account?success=Password reset successfully.')
|
setError(err?.message || 'An error occurred while attempting to reset password.')
|
||||||
} else {
|
|
||||||
setError('There was a problem while resetting your password. Please try again later.')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[router, login],
|
[router, login, resetPassword],
|
||||||
)
|
)
|
||||||
|
|
||||||
// when NextJS populates token within router,
|
// When Next.js populates token within router, reset form with new token value
|
||||||
// reset form with new token value
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset({ token })
|
reset({ token })
|
||||||
}, [reset, token])
|
}, [reset, token])
|
||||||
|
|||||||
Reference in New Issue
Block a user