Login ekranında dil seçimi
This commit is contained in:
parent
d8000208d2
commit
2e62cfa950
3 changed files with 172 additions and 42 deletions
|
|
@ -4,21 +4,22 @@ import Simple from './Simple'
|
|||
import View from '@/views'
|
||||
import { useStoreState } from '@/store'
|
||||
import { LAYOUT_TYPE_BLANK } from '@/constants/theme.constant'
|
||||
import { HiArrowLeft } from 'react-icons/hi'
|
||||
|
||||
const AuthLayout = () => {
|
||||
const layoutType = useStoreState((state) => state.theme.layout.type)
|
||||
const layoutType = useStoreState((state) => state.theme.layout.type)
|
||||
|
||||
return (
|
||||
<div className="app-layout-blank flex flex-auto flex-col h-[100vh]">
|
||||
{layoutType === LAYOUT_TYPE_BLANK ? (
|
||||
<View />
|
||||
) : (
|
||||
<Simple>
|
||||
<View />
|
||||
</Simple>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className="app-layout-blank flex flex-auto flex-col h-[100vh]">
|
||||
{layoutType === LAYOUT_TYPE_BLANK ? (
|
||||
<View />
|
||||
) : (
|
||||
<Simple>
|
||||
<View />
|
||||
</Simple>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthLayout
|
||||
|
|
|
|||
|
|
@ -4,35 +4,41 @@ import Card from '@/components/ui/Card'
|
|||
import Logo from '@/components/template/Logo'
|
||||
import type { ReactNode, ReactElement } from 'react'
|
||||
import type { CommonProps } from '@/@types/common'
|
||||
import { HiArrowLeft } from 'react-icons/hi'
|
||||
|
||||
interface SimpleProps extends CommonProps {
|
||||
content?: ReactNode
|
||||
content?: ReactNode
|
||||
}
|
||||
|
||||
const Simple = ({ children, content, ...rest }: SimpleProps) => {
|
||||
return (
|
||||
<div className="h-full">
|
||||
<Container className="flex flex-col flex-auto items-center justify-center min-w-0 h-full">
|
||||
<Card
|
||||
className="min-w-[320px] md:min-w-[450px]"
|
||||
bodyClass="md:p-10"
|
||||
>
|
||||
<div className="text-center">
|
||||
<Logo type="streamline" imgClass="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
{content}
|
||||
{children
|
||||
? cloneElement(children as ReactElement, {
|
||||
contentClassName: 'text-center',
|
||||
...rest,
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</Card>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className="h-full">
|
||||
<Container className="flex flex-col flex-auto items-center justify-center min-w-0 h-full">
|
||||
<Card className="min-w-[320px] md:min-w-[450px]" bodyClass="md:p-10">
|
||||
<a
|
||||
href="https://sozsoft.com"
|
||||
rel="noopener noreferrer"
|
||||
className="top-0 right-0 text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
<HiArrowLeft className="text-2xl" />
|
||||
</a>
|
||||
|
||||
<div className="text-center">
|
||||
<Logo type="streamline" imgClass="mx-auto" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
{content}
|
||||
{children
|
||||
? cloneElement(children as ReactElement, {
|
||||
contentClassName: 'text-center',
|
||||
...rest,
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</Card>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Simple
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { FailedSignInResponse } from '@/@types/auth'
|
|||
import ActionLink from '@/components/shared/ActionLink'
|
||||
import Captcha from '@/components/shared/Captcha'
|
||||
import PasswordInput from '@/components/shared/PasswordInput'
|
||||
import { Avatar, Select } from '@/components/ui'
|
||||
import Alert from '@/components/ui/Alert'
|
||||
import Button from '@/components/ui/Button'
|
||||
import Checkbox from '@/components/ui/Checkbox'
|
||||
|
|
@ -16,9 +17,16 @@ 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 { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import * as Yup from 'yup'
|
||||
import { components } from 'react-select'
|
||||
import { HiArrowLeft, HiCheck } from 'react-icons/hi'
|
||||
import { SelectBoxOption } from '@/shared/types'
|
||||
import i18n from 'i18next'
|
||||
import appConfig from '@/configs/app.config'
|
||||
import { dateLocales } from '@/locales'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
type SignInFormSchema = {
|
||||
userName: string
|
||||
|
|
@ -41,6 +49,7 @@ const validationSchema = Yup.object().shape({
|
|||
})
|
||||
|
||||
const Login = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const isMultiTenant = useStoreState((a) => a.abpConfig.config?.multiTenancy.isEnabled)
|
||||
const { setTenantId } = useStoreActions((a) => a.auth)
|
||||
|
|
@ -169,6 +178,56 @@ const Login = () => {
|
|||
setSubmitting(false)
|
||||
}
|
||||
|
||||
const CustomControl = ({ children, ...props }: any) => {
|
||||
const selected = props.getValue()[0]
|
||||
return (
|
||||
<components.Control {...props} className="flex items-center justify-start gap-2 px-2">
|
||||
{selected && (
|
||||
<Avatar
|
||||
className="ltr:ml-2 rtl:mr-2"
|
||||
shape="circle"
|
||||
size={18}
|
||||
src={`/img/countries/${selected.cultureName}.png`}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</components.Control>
|
||||
)
|
||||
}
|
||||
|
||||
const CustomSingleValue = ({ data, ...props }: any) => {
|
||||
return (
|
||||
<components.SingleValue {...props}>
|
||||
<div className="flex items-center">
|
||||
<Avatar
|
||||
className="ltr:mr-2 rtl:ml-2"
|
||||
shape="circle"
|
||||
size={18}
|
||||
src={`/img/countries/${data.cultureName}.png`}
|
||||
/>
|
||||
<span>{data.label}</span>
|
||||
</div>
|
||||
</components.SingleValue>
|
||||
)
|
||||
}
|
||||
|
||||
const CustomSelectOption = ({ innerProps, label, data, isSelected }: any) => {
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-between p-2 ${
|
||||
isSelected ? 'bg-gray-100 dark:bg-gray-500' : 'hover:bg-gray-50 dark:hover:bg-gray-600'
|
||||
}`}
|
||||
{...innerProps}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Avatar shape="circle" size={20} src={data.imgPath} />
|
||||
<span className="ml-2 rtl:mr-2">{label}</span>
|
||||
</div>
|
||||
{isSelected && <HiCheck className="text-emerald-500 text-xl" />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const subDomainName = getSubdomain()
|
||||
const tenantId = useStoreState((a) => a.auth.tenantId) ?? subDomainName
|
||||
const tenantStyle: React.CSSProperties | undefined =
|
||||
|
|
@ -184,6 +243,49 @@ const Login = () => {
|
|||
}
|
||||
: undefined
|
||||
|
||||
const { config } = useStoreState((state) => state.abpConfig)
|
||||
const { setLang } = useStoreActions((actions) => actions.locale)
|
||||
|
||||
const languageList = config?.localization.languages
|
||||
const languageOptions = useMemo(() => {
|
||||
return (
|
||||
languageList?.map((lang) => ({
|
||||
label: lang.displayName,
|
||||
value: lang.cultureName,
|
||||
cultureName: lang.cultureName,
|
||||
imgPath: `/img/countries/${lang.cultureName}.png`,
|
||||
})) || []
|
||||
)
|
||||
}, [languageList])
|
||||
|
||||
const [selectedLang, setSelectedLang] = useState<SelectBoxOption | null>(() => {
|
||||
const currentCulture = config?.localization?.currentCulture?.cultureName
|
||||
return languageOptions.find((lang) => lang.cultureName === currentCulture) || null
|
||||
})
|
||||
|
||||
const onLanguageSelect = (cultureName = appConfig.locale) => {
|
||||
setLoading(true)
|
||||
|
||||
const dispatchLang = () => {
|
||||
i18n.changeLanguage(cultureName)
|
||||
setLang(cultureName)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
if (dateLocales[cultureName]) {
|
||||
dateLocales[cultureName]()
|
||||
.then(() => {
|
||||
dayjs.locale(cultureName)
|
||||
dispatchLang()
|
||||
})
|
||||
.catch(() => {
|
||||
dispatchLang()
|
||||
})
|
||||
} else {
|
||||
dispatchLang()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMultiTenant) return
|
||||
|
||||
|
|
@ -201,14 +303,14 @@ const Login = () => {
|
|||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.5, origin: 1 }}
|
||||
>
|
||||
<div className="mb-8">
|
||||
<h3 className="mb-1">{translate('::Abp.Account.WelcomeBack')}</h3>
|
||||
<div className="mb-8 relative">
|
||||
<h3 className="mb-1 pr-10">{translate('::Abp.Account.WelcomeBack')}</h3>
|
||||
<p>{translate('::Abp.Account.WelcomeBack.Message')}</p>
|
||||
</div>
|
||||
{isMultiTenant && (
|
||||
<>
|
||||
<label className="form-label mb-2" style={tenantStyle}>
|
||||
{ translate('::Sirket')}
|
||||
{translate('::Sirket')}
|
||||
</label>
|
||||
<div className="mb-4">
|
||||
<Input
|
||||
|
|
@ -221,6 +323,23 @@ const Login = () => {
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
<label className="form-label mb-2">
|
||||
{translate('::Abp.Identity.Preferences.Language')}
|
||||
</label>
|
||||
<div className="mb-4">
|
||||
<Select
|
||||
options={languageOptions}
|
||||
components={{
|
||||
Option: CustomSelectOption,
|
||||
SingleValue: CustomSingleValue,
|
||||
}}
|
||||
defaultValue={selectedLang}
|
||||
onChange={(option) => onLanguageSelect(option?.value)}
|
||||
className="mb-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
|
|
@ -284,7 +403,9 @@ const Login = () => {
|
|||
<Field className="mb-0" name="rememberMe" component={Checkbox}>
|
||||
{translate('::Abp.Account.RememberMe')}
|
||||
</Field>
|
||||
<ActionLink to={ROUTES_ENUM.account.forgotPassword}>{translate('::Abp.Account.ForgotPassword')}</ActionLink>
|
||||
<ActionLink to={ROUTES_ENUM.account.forgotPassword}>
|
||||
{translate('::Abp.Account.ForgotPassword')}
|
||||
</ActionLink>
|
||||
</div>
|
||||
{showCaptcha && (
|
||||
<Captcha
|
||||
|
|
@ -311,7 +432,9 @@ const Login = () => {
|
|||
</Button>
|
||||
<div className="mt-4 text-center">
|
||||
<span>{translate('::Abp.Account.SignUp.Message')} </span>
|
||||
<ActionLink to={ROUTES_ENUM.account.register}>{translate('::Abp.Account.Register')}</ActionLink>
|
||||
<ActionLink to={ROUTES_ENUM.account.register}>
|
||||
{translate('::Abp.Account.Register')}
|
||||
</ActionLink>
|
||||
</div>
|
||||
</FormContainer>
|
||||
</Form>
|
||||
|
|
|
|||
Loading…
Reference in a new issue