Login ekranında dil seçimi

This commit is contained in:
Sedat ÖZTÜRK 2025-05-30 11:33:33 +03:00
parent d8000208d2
commit 2e62cfa950
3 changed files with 172 additions and 42 deletions

View file

@ -4,6 +4,7 @@ import Simple from './Simple'
import View from '@/views' import View from '@/views'
import { useStoreState } from '@/store' import { useStoreState } from '@/store'
import { LAYOUT_TYPE_BLANK } from '@/constants/theme.constant' import { LAYOUT_TYPE_BLANK } from '@/constants/theme.constant'
import { HiArrowLeft } from 'react-icons/hi'
const AuthLayout = () => { const AuthLayout = () => {
const layoutType = useStoreState((state) => state.theme.layout.type) const layoutType = useStoreState((state) => state.theme.layout.type)

View file

@ -4,6 +4,7 @@ import Card from '@/components/ui/Card'
import Logo from '@/components/template/Logo' import Logo from '@/components/template/Logo'
import type { ReactNode, ReactElement } from 'react' import type { ReactNode, ReactElement } from 'react'
import type { CommonProps } from '@/@types/common' import type { CommonProps } from '@/@types/common'
import { HiArrowLeft } from 'react-icons/hi'
interface SimpleProps extends CommonProps { interface SimpleProps extends CommonProps {
content?: ReactNode content?: ReactNode
@ -13,10 +14,15 @@ const Simple = ({ children, content, ...rest }: SimpleProps) => {
return ( return (
<div className="h-full"> <div className="h-full">
<Container className="flex flex-col flex-auto items-center justify-center min-w-0 h-full"> <Container className="flex flex-col flex-auto items-center justify-center min-w-0 h-full">
<Card <Card className="min-w-[320px] md:min-w-[450px]" bodyClass="md:p-10">
className="min-w-[320px] md:min-w-[450px]" <a
bodyClass="md:p-10" 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"> <div className="text-center">
<Logo type="streamline" imgClass="mx-auto" /> <Logo type="streamline" imgClass="mx-auto" />
</div> </div>

View file

@ -2,6 +2,7 @@ import { FailedSignInResponse } from '@/@types/auth'
import ActionLink from '@/components/shared/ActionLink' import ActionLink from '@/components/shared/ActionLink'
import Captcha from '@/components/shared/Captcha' import Captcha from '@/components/shared/Captcha'
import PasswordInput from '@/components/shared/PasswordInput' import PasswordInput from '@/components/shared/PasswordInput'
import { Avatar, Select } from '@/components/ui'
import Alert from '@/components/ui/Alert' import Alert from '@/components/ui/Alert'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
import Checkbox from '@/components/ui/Checkbox' import Checkbox from '@/components/ui/Checkbox'
@ -16,9 +17,16 @@ import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
import { TurnstileInstance } from '@marsidev/react-turnstile' import { TurnstileInstance } from '@marsidev/react-turnstile'
import { Field, Form, Formik } from 'formik' import { Field, Form, Formik } from 'formik'
import { motion } from 'framer-motion' 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 { useNavigate } from 'react-router-dom'
import * as Yup from 'yup' 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 = { type SignInFormSchema = {
userName: string userName: string
@ -41,6 +49,7 @@ const validationSchema = Yup.object().shape({
}) })
const Login = () => { const Login = () => {
const [loading, setLoading] = useState(false)
const navigate = useNavigate() const navigate = useNavigate()
const isMultiTenant = useStoreState((a) => a.abpConfig.config?.multiTenancy.isEnabled) const isMultiTenant = useStoreState((a) => a.abpConfig.config?.multiTenancy.isEnabled)
const { setTenantId } = useStoreActions((a) => a.auth) const { setTenantId } = useStoreActions((a) => a.auth)
@ -169,6 +178,56 @@ const Login = () => {
setSubmitting(false) 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 subDomainName = getSubdomain()
const tenantId = useStoreState((a) => a.auth.tenantId) ?? subDomainName const tenantId = useStoreState((a) => a.auth.tenantId) ?? subDomainName
const tenantStyle: React.CSSProperties | undefined = const tenantStyle: React.CSSProperties | undefined =
@ -184,6 +243,49 @@ const Login = () => {
} }
: undefined : 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(() => { useEffect(() => {
if (!isMultiTenant) return if (!isMultiTenant) return
@ -201,14 +303,14 @@ const Login = () => {
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, origin: 1 }} transition={{ duration: 0.5, origin: 1 }}
> >
<div className="mb-8"> <div className="mb-8 relative">
<h3 className="mb-1">{translate('::Abp.Account.WelcomeBack')}</h3> <h3 className="mb-1 pr-10">{translate('::Abp.Account.WelcomeBack')}</h3>
<p>{translate('::Abp.Account.WelcomeBack.Message')}</p> <p>{translate('::Abp.Account.WelcomeBack.Message')}</p>
</div> </div>
{isMultiTenant && ( {isMultiTenant && (
<> <>
<label className="form-label mb-2" style={tenantStyle}> <label className="form-label mb-2" style={tenantStyle}>
{ translate('::Sirket')} {translate('::Sirket')}
</label> </label>
<div className="mb-4"> <div className="mb-4">
<Input <Input
@ -221,6 +323,23 @@ const Login = () => {
</div> </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> <div>
<Formik <Formik
initialValues={{ initialValues={{
@ -284,7 +403,9 @@ const Login = () => {
<Field className="mb-0" name="rememberMe" component={Checkbox}> <Field className="mb-0" name="rememberMe" component={Checkbox}>
{translate('::Abp.Account.RememberMe')} {translate('::Abp.Account.RememberMe')}
</Field> </Field>
<ActionLink to={ROUTES_ENUM.account.forgotPassword}>{translate('::Abp.Account.ForgotPassword')}</ActionLink> <ActionLink to={ROUTES_ENUM.account.forgotPassword}>
{translate('::Abp.Account.ForgotPassword')}
</ActionLink>
</div> </div>
{showCaptcha && ( {showCaptcha && (
<Captcha <Captcha
@ -311,7 +432,9 @@ const Login = () => {
</Button> </Button>
<div className="mt-4 text-center"> <div className="mt-4 text-center">
<span>{translate('::Abp.Account.SignUp.Message')} </span> <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> </div>
</FormContainer> </FormContainer>
</Form> </Form>