2026-02-24 20:44:16 +00:00
|
|
|
|
import { FailedSignInResponse } from '@/proxy/auth/models'
|
|
|
|
|
|
import ActionLink from '@/components/shared/ActionLink'
|
|
|
|
|
|
import Captcha from '@/components/shared/Captcha'
|
|
|
|
|
|
import PasswordInput from '@/components/shared/PasswordInput'
|
2026-06-02 18:47:01 +00:00
|
|
|
|
import TenantSelector from '@/components/shared/TenantSelector'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
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'
|
2026-06-02 18:47:01 +00:00
|
|
|
|
import { useRef, useState } from 'react'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
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({
|
2026-06-02 18:47:01 +00:00
|
|
|
|
userName: Yup.string().required(),
|
|
|
|
|
|
password: Yup.string().required(),
|
2026-02-24 20:44:16 +00:00
|
|
|
|
rememberMe: Yup.bool(),
|
|
|
|
|
|
twoFactor: Yup.boolean(),
|
|
|
|
|
|
twoFactorCode: Yup.string().when('twoFactor', {
|
|
|
|
|
|
is: true,
|
2026-06-02 18:47:01 +00:00
|
|
|
|
then: (schema) => schema.required(),
|
2026-02-24 20:44:16 +00:00
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
|
2026-06-02 18:47:01 +00:00
|
|
|
|
<TenantSelector />
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<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}
|
2026-06-02 18:47:01 +00:00
|
|
|
|
className="dark:bg-gray-900 dark:text-gray-100"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
/>
|
|
|
|
|
|
</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>
|
2026-05-19 20:22:25 +00:00
|
|
|
|
{/* <div className="mt-4 text-center">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<span>{translate('::Abp.Account.SignUp.Message')} </span>
|
|
|
|
|
|
<ActionLink to={ROUTES_ENUM.authenticated.register}>
|
|
|
|
|
|
{translate('::Abp.Account.Register')}
|
|
|
|
|
|
</ActionLink>
|
2026-05-19 20:22:25 +00:00
|
|
|
|
</div> */}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</FormContainer>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Formik>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default Login
|