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

303 lines
10 KiB
TypeScript
Raw Normal View History

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'
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'
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({
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,
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()
}
if (showCaptcha) {
setFieldValue('captchaResponse', '')
captchaRef.current?.reset()
}
2026-02-24 20:44:16 +00:00
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 />
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}
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