302 lines
10 KiB
TypeScript
302 lines
10 KiB
TypeScript
import { FailedSignInResponse } from '@/proxy/auth/models'
|
||
import ActionLink from '@/components/shared/ActionLink'
|
||
import Captcha from '@/components/shared/Captcha'
|
||
import PasswordInput from '@/components/shared/PasswordInput'
|
||
import TenantSelector from '@/components/shared/TenantSelector'
|
||
import Alert from '@/components/ui/Alert'
|
||
import Button from '@/components/ui/Button'
|
||
import Checkbox from '@/components/ui/Checkbox'
|
||
import { FormContainer, FormItem } from '@/components/ui/Form'
|
||
import Input from '@/components/ui/Input'
|
||
import PlatformLoginResultType from '@/constants/login.result.enum'
|
||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||
import { useStoreActions, useStoreState } from '@/store'
|
||
import useAuth from '@/utils/hooks/useAuth'
|
||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||
import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
|
||
import { TurnstileInstance } from '@marsidev/react-turnstile'
|
||
import { Field, Form, Formik } from 'formik'
|
||
import { motion } from 'framer-motion'
|
||
import { useRef, useState } from 'react'
|
||
import { useNavigate } from 'react-router-dom'
|
||
import * as Yup from 'yup'
|
||
import { Helmet } from 'react-helmet'
|
||
import { APP_NAME } from '@/constants/app.constant'
|
||
|
||
type SignInFormSchema = {
|
||
userName: string
|
||
password: string
|
||
rememberMe: boolean
|
||
twoFactorCode: string
|
||
captchaResponse: string
|
||
}
|
||
|
||
const validationSchema = Yup.object().shape({
|
||
userName: Yup.string().required(),
|
||
password: Yup.string().required(),
|
||
rememberMe: Yup.bool(),
|
||
twoFactor: Yup.boolean(),
|
||
twoFactorCode: Yup.string().when('twoFactor', {
|
||
is: true,
|
||
then: (schema) => schema.required(),
|
||
otherwise: (schema) => schema.notRequired(),
|
||
}),
|
||
})
|
||
|
||
const Login = () => {
|
||
const navigate = useNavigate()
|
||
|
||
const UiVersion = useStoreState((state) => state.locale.currentUiVersion)
|
||
const { setUiVersion } = useStoreActions((a) => a.locale)
|
||
|
||
const [message, setMessage] = useState('')
|
||
const [error, setError] = useTimeOutMessage(300000)
|
||
const [twoFactor, setTwoFactor] = useState(false)
|
||
const [showCaptcha, setShowCaptcha] = useState(false)
|
||
|
||
const captchaRef = useRef<TurnstileInstance>(null)
|
||
const { setWarning } = useStoreActions((actions) => actions.base.messages)
|
||
const setWarningTimeout = (message: string) => {
|
||
setTimeout(() => {
|
||
setWarning(message)
|
||
}, 100)
|
||
}
|
||
|
||
const { signIn } = useAuth()
|
||
const { translate } = useLocalization()
|
||
|
||
const onSignIn = async (
|
||
values: SignInFormSchema,
|
||
{ setSubmitting, isSubmitting, setFieldValue, setFieldTouched }: any,
|
||
) => {
|
||
if (isSubmitting) {
|
||
return
|
||
}
|
||
|
||
setSubmitting(true)
|
||
const { userName, password, twoFactorCode, captchaResponse } = values
|
||
|
||
const result = await signIn({
|
||
userName,
|
||
password,
|
||
twoFactorCode,
|
||
captchaResponse,
|
||
})
|
||
|
||
if (result.status === 'error') {
|
||
setError(result.message)
|
||
} else {
|
||
setError('')
|
||
}
|
||
|
||
if (result.status === 'failed') {
|
||
const data = result.data as FailedSignInResponse
|
||
setError(data.description)
|
||
|
||
if (data.pResult === PlatformLoginResultType.NotAllowed) {
|
||
setWarningTimeout(data.description)
|
||
navigate(ROUTES_ENUM.authenticated.sendConfirmationCode)
|
||
} else {
|
||
setWarning('')
|
||
}
|
||
|
||
if (
|
||
data.pResult === PlatformLoginResultType.RequiresTwoFactor ||
|
||
data.pResult === PlatformLoginResultType.WrongTwoFactorCode
|
||
) {
|
||
if (data.pResult === PlatformLoginResultType.RequiresTwoFactor) {
|
||
setError(undefined)
|
||
setMessage('Mail adresinize gönderilen doğrulama kodunu giriniz')
|
||
} else {
|
||
setMessage('')
|
||
}
|
||
setTwoFactor(true)
|
||
setFieldValue('twoFactor', true)
|
||
setFieldTouched('twoFactorCode', false)
|
||
} else {
|
||
setMessage('')
|
||
setTwoFactor(false)
|
||
setFieldValue('twoFactor', false)
|
||
}
|
||
|
||
setShowCaptcha(data.pResult === PlatformLoginResultType.ShowCaptcha)
|
||
if (data.pResult === PlatformLoginResultType.ShowCaptcha) {
|
||
captchaRef.current?.reset()
|
||
}
|
||
|
||
if (
|
||
data.pResult === PlatformLoginResultType.ShouldChangePasswordOnNextLogin ||
|
||
data.pResult === PlatformLoginResultType.ShouldChangePasswordPeriodic
|
||
) {
|
||
setWarningTimeout(data.description)
|
||
navigate(ROUTES_ENUM.authenticated.forgotPassword)
|
||
} else {
|
||
setWarning('')
|
||
}
|
||
|
||
if (data.pResult === PlatformLoginResultType.LoginEndDateDue) {
|
||
setWarningTimeout(data.description)
|
||
navigate(ROUTES_ENUM.authenticated.sendExtendLogin)
|
||
} else {
|
||
setWarning('')
|
||
}
|
||
} else {
|
||
// Temizlik
|
||
setTwoFactor(false)
|
||
setFieldValue('twoFactorCode', '')
|
||
setFieldValue('twoFactor', false)
|
||
setShowCaptcha(false)
|
||
setError('')
|
||
setMessage('')
|
||
|
||
// Versiyon kontrolü
|
||
findUiVersion()
|
||
}
|
||
|
||
if (showCaptcha) {
|
||
setFieldValue('captchaResponse', '')
|
||
captchaRef.current?.reset()
|
||
}
|
||
|
||
setSubmitting(false)
|
||
}
|
||
|
||
const findUiVersion = async () => {
|
||
try {
|
||
const res = await fetch(`/version.json?ts=${Date.now()}`)
|
||
const latest = (await res.json())?.releases?.[0]?.version
|
||
if (latest && UiVersion !== latest) {
|
||
setUiVersion(latest)
|
||
navigate(ROUTES_ENUM.protected.admin.changeLog)
|
||
}
|
||
} catch (e) {
|
||
console.warn('Versiyon okunamadı', e)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<Helmet
|
||
titleTemplate={`%s | ${APP_NAME}`}
|
||
title={translate('AbpAccount::' + 'Login')}
|
||
defaultTitle={APP_NAME}
|
||
></Helmet>
|
||
<motion.div
|
||
initial={{ opacity: 0, x: 100 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
transition={{ duration: 0.5, origin: 1 }}
|
||
>
|
||
<div className="mb-8 relative text-center">
|
||
<h3 className="mb-1 m-5">{translate('::Abp.Account.WelcomeBack')}</h3>
|
||
<p>{translate('::Abp.Account.WelcomeBack.Message')}</p>
|
||
</div>
|
||
|
||
<TenantSelector />
|
||
<div>
|
||
<Formik
|
||
initialValues={{
|
||
userName: '',
|
||
password: '',
|
||
twoFactorCode: '',
|
||
rememberMe: true,
|
||
captchaResponse: '',
|
||
}}
|
||
validationSchema={validationSchema}
|
||
onSubmit={onSignIn}
|
||
>
|
||
{({ touched, errors, isSubmitting, setFieldValue }) => (
|
||
<Form>
|
||
<FormContainer>
|
||
{!twoFactor && (
|
||
<FormItem
|
||
label={translate('::Abp.Account.EmailAddress')}
|
||
invalid={errors.userName && touched.userName}
|
||
errorMessage={errors.userName}
|
||
>
|
||
<Field
|
||
type="text"
|
||
autoComplete="off"
|
||
name="userName"
|
||
placeholder={translate('::Abp.Account.EmailAddress')}
|
||
component={Input}
|
||
className="dark:bg-gray-900 dark:text-gray-100"
|
||
/>
|
||
</FormItem>
|
||
)}
|
||
{!twoFactor && (
|
||
<FormItem
|
||
label={translate('::Abp.Identity.Password')}
|
||
invalid={errors.password && touched.password}
|
||
errorMessage={errors.password}
|
||
>
|
||
<Field
|
||
autoComplete="off"
|
||
name="password"
|
||
placeholder={translate('::Abp.Identity.Password')}
|
||
component={PasswordInput}
|
||
/>
|
||
</FormItem>
|
||
)}
|
||
{twoFactor && (
|
||
<FormItem
|
||
label={translate('::Abp.Account.2FACode')}
|
||
invalid={errors.twoFactorCode && touched.twoFactorCode}
|
||
errorMessage={errors.twoFactorCode}
|
||
>
|
||
<Field
|
||
autoComplete="off"
|
||
name="twoFactorCode"
|
||
placeholder={translate('::Abp.Account.2FACode')}
|
||
component={Input}
|
||
/>
|
||
</FormItem>
|
||
)}
|
||
|
||
<div className="flex justify-between mb-6">
|
||
<Field className="mb-0" name="rememberMe" component={Checkbox}>
|
||
{translate('::Abp.Account.RememberMe')}
|
||
</Field>
|
||
<ActionLink to={ROUTES_ENUM.authenticated.forgotPassword}>
|
||
{translate('::Abp.Account.ForgotPassword')}
|
||
</ActionLink>
|
||
</div>
|
||
{showCaptcha && (
|
||
<Captcha
|
||
ref={captchaRef}
|
||
onError={() => setFieldValue('captchaResponse', '')}
|
||
onExpire={() => setFieldValue('captchaResponse', '')}
|
||
onSuccess={(token: string) => setFieldValue('captchaResponse', token)}
|
||
/>
|
||
)}
|
||
|
||
{message && (
|
||
<Alert showIcon className="mb-4" type="success">
|
||
{message}
|
||
</Alert>
|
||
)}
|
||
{error && (
|
||
<Alert showIcon className="mb-4" type="danger">
|
||
{error}
|
||
</Alert>
|
||
)}
|
||
|
||
<Button block loading={isSubmitting} variant="solid" type="submit">
|
||
{isSubmitting ? 'Signing in...' : 'Sign In'}
|
||
</Button>
|
||
{/* <div className="mt-4 text-center">
|
||
<span>{translate('::Abp.Account.SignUp.Message')} </span>
|
||
<ActionLink to={ROUTES_ENUM.authenticated.register}>
|
||
{translate('::Abp.Account.Register')}
|
||
</ActionLink>
|
||
</div> */}
|
||
</FormContainer>
|
||
</Form>
|
||
)}
|
||
</Formik>
|
||
</div>
|
||
</motion.div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default Login
|