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

373 lines
12 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 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 { getTenantByNameDetail } from '@/services/tenant.service'
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 { useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import * as Yup from 'yup'
import { defaultDomain, getSubdomain } from '@/utils/subdomain'
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('Please enter your user name'),
password: Yup.string().required('Please enter your password'),
rememberMe: Yup.bool(),
twoFactor: Yup.boolean(),
twoFactorCode: Yup.string().when('twoFactor', {
is: true,
then: (schema) => schema.required('Mail adresinize gönderilen doğrulama kodunu giriniz'),
otherwise: (schema) => schema.notRequired(),
}),
})
const Login = () => {
const navigate = useNavigate()
const isMultiTenant = useStoreState((a) => a.abpConfig.config?.multiTenancy.isEnabled)
const { setTenant } = useStoreActions((a) => a.auth.tenant)
const UiVersion = useStoreState((state) => state.locale.currentUiVersion)
const { setUiVersion } = useStoreActions((a) => a.locale)
const tenantName = useStoreState((state) => state.locale.currentTenantName)
const { setTenantName } = useStoreActions((actions) => actions.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('')
2026-03-10 13:42:16 +00:00
//Tenant belirlenmişse
fetchDataByName(tenantName || '')
2026-02-24 20:44:16 +00:00
}
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)
}
2026-03-10 13:42:16 +00:00
const fetchDataByName = async (name: string, isSubdomain = false) => {
2026-02-24 20:44:16 +00:00
if (name) {
2026-03-10 13:42:16 +00:00
try {
const response = await getTenantByNameDetail(name)
2026-02-24 20:44:16 +00:00
2026-03-10 13:42:16 +00:00
if (response.data) {
setTenant({ tenantId: response.data.id, tenantName: response.data.name, menuGroup: response.data.menuGroup });
} else {
setTenant(undefined)
if (isSubdomain) redirectToMainDomain(name)
}
} catch {
2026-02-24 20:44:16 +00:00
setTenant(undefined)
2026-03-10 13:42:16 +00:00
if (isSubdomain) redirectToMainDomain(name)
2026-02-24 20:44:16 +00:00
}
} else {
setTenant(undefined)
}
}
2026-03-10 13:42:16 +00:00
const redirectToMainDomain = (name: string) => {
setTenantName(undefined)
const parts = window.location.hostname.split('.')
const mainDomain = parts.length >= 3 ? parts.slice(1).join('.') : window.location.hostname
setWarningTimeout(`"${name}" kurumuna ait kayıt bulunamadı. Ana sayfaya yönlendiriliyorsunuz...`)
setTimeout(() => {
window.location.href = `${window.location.protocol}//${mainDomain}`
}, 3000)
}
2026-02-24 20:44:16 +00:00
const subDomainName = getSubdomain()
useEffect(() => {
if (subDomainName) {
setTenantName(subDomainName)
2026-03-10 13:42:16 +00:00
fetchDataByName(subDomainName, true)
2026-02-24 20:44:16 +00:00
}
}, [subDomainName])
const tenantStyle: React.CSSProperties | undefined =
subDomainName && subDomainName !== defaultDomain
? {
opacity: 0,
position: 'absolute',
pointerEvents: 'none',
height: 0,
margin: 0,
padding: 0,
border: 'none',
}
: undefined
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>
{isMultiTenant && (
<>
<label className="form-label mb-2" style={tenantStyle}>
{translate('::Organization')}
</label>
<div className="mb-4">
<Input
placeholder={translate('::Organization')}
value={tenantName}
onChange={(e) => setTenantName(e.target.value)}
style={tenantStyle}
aria-hidden={subDomainName && subDomainName !== defaultDomain ? 'true' : 'false'}
/>
</div>
</>
)}
<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-05-19 20:22:25 +00:00
inputClassName="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