more error handling
This commit is contained in:
parent
11874c6c1d
commit
f66015cd77
|
@ -15,9 +15,10 @@
|
|||
.authorDetails {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
// padding-right: 1.2rem;
|
||||
width: max-content;
|
||||
|
||||
// padding-right: 1.2rem;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import styles from './AuthModal.module.scss'
|
||||
import { clsx } from 'clsx'
|
||||
import { t } from '../../../utils/intl'
|
||||
import { hideModal } from '../../../stores/ui'
|
||||
import { createMemo, onMount, Show } from 'solid-js'
|
||||
import { useRouter } from '../../../stores/router'
|
||||
import { hideModal, locale } from '../../../stores/ui'
|
||||
import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
||||
import { handleClientRouteLinkClick, useRouter } from '../../../stores/router'
|
||||
import type { ConfirmEmailSearchParams } from './types'
|
||||
import { useAuth } from '../../../context/auth'
|
||||
import { signSendLink, useAuth } from '../../../context/auth'
|
||||
import { ApiError } from '../../../utils/apiClient'
|
||||
import { email } from './sharedLogic'
|
||||
|
||||
export const EmailConfirm = () => {
|
||||
const {
|
||||
|
@ -13,6 +15,9 @@ export const EmailConfirm = () => {
|
|||
actions: { confirmEmail }
|
||||
} = useAuth()
|
||||
|
||||
const [isTokenExpired, setIsTokenExpired] = createSignal(false)
|
||||
const [isTokenInvalid, setIsTokenInvalid] = createSignal(false)
|
||||
|
||||
const confirmedEmail = createMemo(() => session()?.user?.email || '')
|
||||
|
||||
const { searchParams } = useRouter<ConfirmEmailSearchParams>()
|
||||
|
@ -22,23 +27,54 @@ export const EmailConfirm = () => {
|
|||
try {
|
||||
await confirmEmail(token)
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) {
|
||||
if (error.code === 'token_expired') {
|
||||
setIsTokenExpired(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (error.code === 'token_invalid') {
|
||||
setIsTokenInvalid(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log(error)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class={styles.title}>{t('Hooray! Welcome!')}</div>
|
||||
{/* TODO: texts */}
|
||||
<Show when={isTokenExpired()}>
|
||||
<div class={styles.title}>Ссылка больше не действительна</div>
|
||||
<div class={styles.text}>
|
||||
<a href="/?modal=auth&mode=login" class={styles.sendLink} onClick={handleClientRouteLinkClick}>
|
||||
{/*TODO: temp solution, should be send link again, but we don't have email here*/}
|
||||
Вход
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={isTokenInvalid()}>
|
||||
<div class={styles.title}>Неправильная ссылка</div>
|
||||
<div class={styles.text}>
|
||||
<a href="/?modal=auth&mode=login" class={styles.sendLink} onClick={handleClientRouteLinkClick}>
|
||||
{/*TODO: temp solution, should be send link again, but we don't have email here*/}
|
||||
Вход
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={Boolean(confirmedEmail())}>
|
||||
<div class={styles.title}>{t('Hooray! Welcome!')}</div>
|
||||
<div class={styles.text}>
|
||||
{t("You've confirmed email")} {confirmedEmail()}
|
||||
</div>
|
||||
<div>
|
||||
<button class={clsx('button', styles.submitButton)} onClick={() => hideModal()}>
|
||||
{t('Go to main page')}
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<div>
|
||||
<button class={clsx('button', styles.submitButton)} onClick={() => hideModal()}>
|
||||
{t('Go to main page')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import type { AuthModalSearchParams } from './types'
|
|||
import { isValidEmail } from './validators'
|
||||
import { locale } from '../../../stores/ui'
|
||||
import { signSendLink } from '../../../context/auth'
|
||||
import { ApiError } from '../../../utils/apiClient'
|
||||
|
||||
type FormFields = {
|
||||
email: string
|
||||
|
@ -26,11 +27,13 @@ export const ForgotPasswordForm = () => {
|
|||
const [submitError, setSubmitError] = createSignal('')
|
||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||
const [isUserNotFount, setIsUserNotFound] = createSignal(false)
|
||||
|
||||
const handleSubmit = async (event: Event) => {
|
||||
event.preventDefault()
|
||||
|
||||
setSubmitError('')
|
||||
setIsUserNotFound(false)
|
||||
|
||||
const newValidationErrors: ValidationErrors = {}
|
||||
|
||||
|
@ -51,9 +54,12 @@ export const ForgotPasswordForm = () => {
|
|||
setIsSubmitting(true)
|
||||
|
||||
try {
|
||||
const result = await signSendLink({ email: email(), lang: locale() })
|
||||
if (result.error) setSubmitError(result.error)
|
||||
await signSendLink({ email: email(), lang: locale() })
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && error.code === 'user_not_found') {
|
||||
setIsUserNotFound(true)
|
||||
return
|
||||
}
|
||||
setSubmitError(error.message)
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
|
@ -71,6 +77,21 @@ export const ForgotPasswordForm = () => {
|
|||
</ul>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={isUserNotFount()}>
|
||||
<div class={styles.authSubtitle}>
|
||||
{/*TODO: text*/}
|
||||
{t("We can't find you, check email or")}{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
changeSearchParam('mode', 'register')
|
||||
}}
|
||||
>
|
||||
{t('register')}
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={validationErrors().email}>
|
||||
<div class={styles.validationError}>{validationErrors().email}</div>
|
||||
</Show>
|
||||
|
|
|
@ -57,6 +57,7 @@ export const LoginForm = () => {
|
|||
event.preventDefault()
|
||||
|
||||
setIsLinkSent(false)
|
||||
setIsEmailNotConfirmed(false)
|
||||
setSubmitError('')
|
||||
|
||||
const newValidationErrors: ValidationErrors = {}
|
||||
|
|
|
@ -20,6 +20,9 @@ const AuthContext = createContext<AuthContextType>()
|
|||
const refreshSession = async (): Promise<AuthResult> => {
|
||||
try {
|
||||
const authResult = await apiClient.getSession()
|
||||
if (!authResult) {
|
||||
return null
|
||||
}
|
||||
setToken(authResult.token)
|
||||
return authResult
|
||||
} catch (error) {
|
||||
|
|
|
@ -10,6 +10,10 @@ if (isDev) {
|
|||
exchanges.unshift(devtoolsExchange)
|
||||
}
|
||||
|
||||
export const getToken = (): string => {
|
||||
return localStorage.getItem(TOKEN_LOCAL_STORAGE_KEY)
|
||||
}
|
||||
|
||||
export const setToken = (token: string) => {
|
||||
localStorage.setItem(TOKEN_LOCAL_STORAGE_KEY, token)
|
||||
}
|
||||
|
|
|
@ -169,5 +169,7 @@
|
|||
"Send link again": "Прислать ссылку ещё раз",
|
||||
"Link sent, check your email": "Ссылка отправлена, проверьте почту",
|
||||
"Create post": "Создать публикацию",
|
||||
"Just start typing...": "Просто начните печатать..."
|
||||
"Just start typing...": "Просто начните печатать...",
|
||||
"We can't find you, check email or": "Не можем вас найти, проверьте адрес электронной почты или",
|
||||
"register": "зарегистрируйтесь"
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import type {
|
|||
Author
|
||||
} from '../graphql/types.gen'
|
||||
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
||||
import { privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
||||
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
||||
import articleBySlug from '../graphql/query/article-by-slug'
|
||||
import articlesRecentAll from '../graphql/query/articles-recent-all'
|
||||
import articlesRecentPublished from '../graphql/query/articles-recent-published'
|
||||
|
@ -41,7 +41,13 @@ import topicBySlug from '../graphql/query/topic-by-slug'
|
|||
|
||||
const FEED_SIZE = 50
|
||||
|
||||
type ApiErrorCode = 'unknown' | 'email_not_confirmed' | 'user_not_found' | 'user_already_exists'
|
||||
type ApiErrorCode =
|
||||
| 'unknown'
|
||||
| 'email_not_confirmed'
|
||||
| 'user_not_found'
|
||||
| 'user_already_exists'
|
||||
| 'token_expired'
|
||||
| 'token_invalid'
|
||||
|
||||
export class ApiError extends Error {
|
||||
code: ApiErrorCode
|
||||
|
@ -109,16 +115,32 @@ export const apiClient = {
|
|||
const response = await publicGraphQLClient.mutation(authSendLinkMutation, { email, lang }).toPromise()
|
||||
|
||||
if (response.error) {
|
||||
if (response.error.message === '[GraphQL] User not found') {
|
||||
throw new ApiError('user_not_found', response.error.message)
|
||||
}
|
||||
|
||||
throw new ApiError('unknown', response.error.message)
|
||||
}
|
||||
|
||||
if (response.data.sendLink.error) {
|
||||
throw new ApiError('unknown', response.data.sendLink.message)
|
||||
}
|
||||
|
||||
return response.data.sendLink
|
||||
},
|
||||
confirmEmail: async ({ token }: { token: string }) => {
|
||||
// confirm email with code from link
|
||||
const response = await publicGraphQLClient.mutation(authConfirmEmailMutation, { token }).toPromise()
|
||||
|
||||
if (response.error) {
|
||||
// TODO: better error communication
|
||||
if (response.error.message === '[GraphQL] check token lifetime') {
|
||||
throw new ApiError('token_expired', response.error.message)
|
||||
}
|
||||
|
||||
if (response.error.message === '[GraphQL] token is not valid') {
|
||||
throw new ApiError('token_invalid', response.error.message)
|
||||
}
|
||||
|
||||
throw new ApiError('unknown', response.error.message)
|
||||
}
|
||||
|
||||
|
@ -251,6 +273,10 @@ export const apiClient = {
|
|||
},
|
||||
|
||||
getSession: async (): Promise<AuthResult> => {
|
||||
if (!getToken()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// renew session with auth token in header (!)
|
||||
const response = await privateGraphQLClient.mutation(mySession, {}).toPromise()
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user