sozsoft-platform/ui/src/views/auth/Login.tsx

302 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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