Tenantlı uygulama için ForgotPassword, ResetPassword
This commit is contained in:
parent
f9c5910813
commit
d161e0f4b9
17 changed files with 538 additions and 416 deletions
|
|
@ -138,11 +138,15 @@ User Detail: {string.Format(userDetailUrl, user.Id)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
[Captcha]
|
[Captcha]
|
||||||
public async Task SendAccountConfirmationCodeAsync(SendAccountConfirmationCodeInputDto input)
|
public async Task<bool> SendAccountConfirmationCodeAsync(SendAccountConfirmationCodeInputDto input)
|
||||||
{
|
{
|
||||||
var user = await UserManager.FindByEmailAsync(input.EmailAddress);
|
var user = await UserManager.FindByEmailAsync(input.EmailAddress);
|
||||||
if (user != null)
|
if (user == null)
|
||||||
await SendConfirmationCodeAsync(user);
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await SendConfirmationCodeAsync(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> VerifyAccountConfirmationCodeAsync(VerifyAccountConfirmationCodeInputDto input)
|
public async Task<bool> VerifyAccountConfirmationCodeAsync(VerifyAccountConfirmationCodeInputDto input)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Flurl.Http;
|
using Flurl.Http;
|
||||||
using Sozsoft.Platform.Localization;
|
using Sozsoft.Platform.Localization;
|
||||||
|
|
@ -23,6 +24,11 @@ public class TurnstileCaptchaManager : PlatformDomainService, ICaptchaManager
|
||||||
|
|
||||||
public async Task<bool> VerifyCaptchaAsync(string CaptchaResponse, bool throwOnFailure = false)
|
public async Task<bool> VerifyCaptchaAsync(string CaptchaResponse, bool throwOnFailure = false)
|
||||||
{
|
{
|
||||||
|
if (CaptchaResponse.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var endPoint = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpAccount.Captcha.EndPoint);
|
var endPoint = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpAccount.Captcha.EndPoint);
|
||||||
var privateKey = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpAccount.Captcha.SecretKey);
|
var privateKey = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpAccount.Captcha.SecretKey);
|
||||||
if (endPoint.IsNullOrWhiteSpace() || privateKey.IsNullOrWhiteSpace())
|
if (endPoint.IsNullOrWhiteSpace() || privateKey.IsNullOrWhiteSpace())
|
||||||
|
|
@ -46,18 +52,24 @@ public class TurnstileCaptchaManager : PlatformDomainService, ICaptchaManager
|
||||||
|
|
||||||
if (response != null && response.StatusCode == 200)
|
if (response != null && response.StatusCode == 200)
|
||||||
{
|
{
|
||||||
var result = await response.GetJsonAsync<dynamic>();
|
var result = await response.GetJsonAsync<TurnstileCaptchaVerifyResponse>();
|
||||||
if (!(bool)result.success)
|
if (result?.Success != true)
|
||||||
{
|
{
|
||||||
if (throwOnFailure)
|
if (throwOnFailure)
|
||||||
{
|
{
|
||||||
throw new UserFriendlyException(Localizer[PlatformConsts.AbpIdentity.User.CaptchaWrongCode]);
|
throw new UserFriendlyException(Localizer[PlatformConsts.AbpIdentity.User.CaptchaWrongCode]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (bool)result.success;
|
|
||||||
|
return result?.Success == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
private class TurnstileCaptchaVerifyResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,8 +187,8 @@ public class PlatformHttpApiHostModule : AbpModule
|
||||||
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>());
|
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>());
|
||||||
|
|
||||||
options.Applications[PlatformConsts.React].RootUrl = configuration["App:ClientUrl"];
|
options.Applications[PlatformConsts.React].RootUrl = configuration["App:ClientUrl"];
|
||||||
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.EmailConfirmation] = "account/confirm";
|
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.EmailConfirmation] = "confirm";
|
||||||
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.PasswordReset] = "account/reset-password";
|
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.PasswordReset] = "reset-password";
|
||||||
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.UserDetail] = "account/{0}";
|
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.UserDetail] = "account/{0}";
|
||||||
|
|
||||||
//options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
|
//options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
|
||||||
|
|
|
||||||
|
|
@ -4,7 +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 '@/proxy/common'
|
import type { CommonProps } from '@/proxy/common'
|
||||||
import { FaArrowLeft, FaCheck } from 'react-icons/fa';
|
import { FaArrowLeft, FaCheck } from 'react-icons/fa'
|
||||||
import { Avatar, Select } from '@/components/ui'
|
import { Avatar, Select } from '@/components/ui'
|
||||||
import { useStoreActions, useStoreState } from '@/store'
|
import { useStoreActions, useStoreState } from '@/store'
|
||||||
import appConfig from '@/proxy/configs/app.config'
|
import appConfig from '@/proxy/configs/app.config'
|
||||||
|
|
@ -90,7 +90,7 @@ 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 className="min-w-[320px] md:min-w-[450px]" bodyClass="md:p-5">
|
<Card className="w-full min-w-[320px] max-w-[360px] md:min-w-[450px]" bodyClass="md:p-5">
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
{!hasSubdomain() && (
|
{!hasSubdomain() && (
|
||||||
<a
|
<a
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const Logo = (props: LogoProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('logo', 'my-1', className)}
|
className={classNames('logo', 'my-2', className)}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
...{ width: logoWidth },
|
...{ width: logoWidth },
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState, forwardRef } from 'react'
|
import { useState, forwardRef } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import useTimeout from '../hooks/useTimeout'
|
import useTimeout from '../hooks/useTimeout'
|
||||||
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from 'react-icons/fa';
|
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from 'react-icons/fa'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import CloseButton from '../CloseButton'
|
import CloseButton from '../CloseButton'
|
||||||
import StatusIcon from '../StatusIcon'
|
import StatusIcon from '../StatusIcon'
|
||||||
|
|
@ -9,173 +9,153 @@ import type { TypeAttributes, CommonProps } from '../@types/common'
|
||||||
import type { ReactNode, MouseEvent } from 'react'
|
import type { ReactNode, MouseEvent } from 'react'
|
||||||
|
|
||||||
export interface AlertProps extends CommonProps {
|
export interface AlertProps extends CommonProps {
|
||||||
closable?: boolean
|
closable?: boolean
|
||||||
customClose?: ReactNode | string
|
customClose?: ReactNode | string
|
||||||
customIcon?: ReactNode | string
|
customIcon?: ReactNode | string
|
||||||
duration?: number
|
duration?: number
|
||||||
title?: ReactNode | string
|
title?: ReactNode | string
|
||||||
onClose?: (e?: MouseEvent<HTMLDivElement>) => void
|
onClose?: (e?: MouseEvent<HTMLDivElement>) => void
|
||||||
rounded?: boolean
|
rounded?: boolean
|
||||||
showIcon?: boolean
|
showIcon?: boolean
|
||||||
triggerByToast?: boolean
|
triggerByToast?: boolean
|
||||||
type?: TypeAttributes.Status
|
type?: TypeAttributes.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TYPE = 'warning'
|
const DEFAULT_TYPE = 'warning'
|
||||||
|
|
||||||
const TYPE_MAP = {
|
const TYPE_MAP = {
|
||||||
success: {
|
success: {
|
||||||
backgroundColor: 'bg-emerald-50 dark:bg-emerald-500',
|
backgroundColor: 'bg-emerald-50 dark:bg-emerald-500',
|
||||||
titleColor: 'text-emerald-700 dark:text-emerald-50',
|
titleColor: 'text-emerald-700 dark:text-emerald-50',
|
||||||
textColor: 'text-emerald-500 dark:text-emerald-50',
|
textColor: 'text-emerald-500 dark:text-emerald-50',
|
||||||
iconColor: 'text-emerald-400 dark:text-emerald-50',
|
iconColor: 'text-emerald-400 dark:text-emerald-50',
|
||||||
icon: <FaCheckCircle />,
|
icon: <FaCheckCircle />,
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
backgroundColor: 'bg-blue-50 dark:bg-blue-500',
|
backgroundColor: 'bg-blue-50 dark:bg-blue-500',
|
||||||
titleColor: 'text-blue-700 dark:text-blue-100',
|
titleColor: 'text-blue-700 dark:text-blue-100',
|
||||||
textColor: 'text-blue-500 dark:text-blue-100',
|
textColor: 'text-blue-500 dark:text-blue-100',
|
||||||
iconColor: 'text-blue-400 dark:text-blue-100',
|
iconColor: 'text-blue-400 dark:text-blue-100',
|
||||||
icon: <FaInfoCircle />,
|
icon: <FaInfoCircle />,
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
backgroundColor: 'bg-yellow-50 dark:bg-yellow-500',
|
backgroundColor: 'bg-yellow-50 dark:bg-yellow-500',
|
||||||
titleColor: 'text-yellow-700 dark:text-yellow-50',
|
titleColor: 'text-yellow-700 dark:text-yellow-50',
|
||||||
textColor: 'text-yellow-500 dark:text-yellow-50',
|
textColor: 'text-yellow-500 dark:text-yellow-50',
|
||||||
iconColor: 'text-yellow-400 dark:text-yellow-50',
|
iconColor: 'text-yellow-400 dark:text-yellow-50',
|
||||||
icon: <FaExclamation />,
|
icon: <FaExclamation />,
|
||||||
},
|
},
|
||||||
danger: {
|
danger: {
|
||||||
backgroundColor: 'bg-red-50 dark:bg-red-500',
|
backgroundColor: 'bg-red-50 dark:bg-red-500',
|
||||||
titleColor: 'text-red-700 dark:text-red-100',
|
titleColor: 'text-red-700 dark:text-red-100',
|
||||||
textColor: 'text-red-500 dark:text-red-100',
|
textColor: 'text-red-500 dark:text-red-100',
|
||||||
iconColor: 'text-red-400 dark:text-red-100',
|
iconColor: 'text-red-400 dark:text-red-100',
|
||||||
icon: <FaTimesCircle />,
|
icon: <FaTimesCircle />,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const TYPE_ARRAY: TypeAttributes.Status[] = [
|
const TYPE_ARRAY: TypeAttributes.Status[] = ['success', 'danger', 'info', 'warning']
|
||||||
'success',
|
|
||||||
'danger',
|
|
||||||
'info',
|
|
||||||
'warning',
|
|
||||||
]
|
|
||||||
|
|
||||||
const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
|
const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
closable = false,
|
closable = false,
|
||||||
customClose,
|
customClose,
|
||||||
customIcon,
|
customIcon,
|
||||||
duration = 3000,
|
duration = 3000,
|
||||||
title = null,
|
title = null,
|
||||||
onClose,
|
onClose,
|
||||||
rounded = true,
|
rounded = true,
|
||||||
showIcon = false,
|
showIcon = false,
|
||||||
triggerByToast = false,
|
triggerByToast = false,
|
||||||
...rest
|
...rest
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const getType = () => {
|
const getType = () => {
|
||||||
const { type = DEFAULT_TYPE } = props
|
const { type = DEFAULT_TYPE } = props
|
||||||
if (TYPE_ARRAY.includes(type)) {
|
if (TYPE_ARRAY.includes(type)) {
|
||||||
return type
|
return type
|
||||||
}
|
|
||||||
return DEFAULT_TYPE
|
|
||||||
}
|
}
|
||||||
|
return DEFAULT_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
const type = getType()
|
const type = getType()
|
||||||
const typeMap = TYPE_MAP[type]
|
const typeMap = TYPE_MAP[type]
|
||||||
|
|
||||||
const [display, setDisplay] = useState('show')
|
const [display, setDisplay] = useState('show')
|
||||||
|
|
||||||
const { clear } = useTimeout(
|
const { clear } = useTimeout(onClose as () => void, duration, (duration as number) > 0)
|
||||||
onClose as () => void,
|
|
||||||
duration,
|
|
||||||
(duration as number) > 0
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleClose = (e: MouseEvent<HTMLDivElement>) => {
|
const handleClose = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
setDisplay('hiding')
|
setDisplay('hiding')
|
||||||
onClose?.(e)
|
onClose?.(e)
|
||||||
clear()
|
clear()
|
||||||
if (!triggerByToast) {
|
if (!triggerByToast) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setDisplay('hide')
|
setDisplay('hide')
|
||||||
}, 400)
|
}, 400)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderClose = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="cursor-pointer"
|
|
||||||
role="presentation"
|
|
||||||
onClick={(e) => handleClose(e)}
|
|
||||||
>
|
|
||||||
{customClose || <CloseButton defaultStyle={false} />}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const alertDefaultClass = 'p-2 relative flex'
|
|
||||||
|
|
||||||
const alertClass = classNames(
|
|
||||||
'alert',
|
|
||||||
alertDefaultClass,
|
|
||||||
typeMap.backgroundColor,
|
|
||||||
typeMap.textColor,
|
|
||||||
!title ? 'font-semibold' : '',
|
|
||||||
closable ? 'justify-between' : '',
|
|
||||||
closable && !title ? 'items-center' : '',
|
|
||||||
rounded && 'rounded-lg',
|
|
||||||
className
|
|
||||||
)
|
|
||||||
|
|
||||||
if (display === 'hide') {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderClose = () => {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<div className="cursor-pointer" role="presentation" onClick={(e) => handleClose(e)}>
|
||||||
ref={ref}
|
{customClose || <CloseButton defaultStyle={false} />}
|
||||||
className={alertClass}
|
</div>
|
||||||
initial={{ opacity: 1 }}
|
|
||||||
animate={display === 'hiding' ? 'exit' : 'animate'}
|
|
||||||
transition={{ duration: 0.25, type: 'tween' }}
|
|
||||||
variants={{
|
|
||||||
animate: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
exit: {
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<div className={`flex ${title ? '' : 'items-center'}`}>
|
|
||||||
{showIcon && (
|
|
||||||
<StatusIcon
|
|
||||||
iconColor={typeMap.iconColor}
|
|
||||||
custom={customIcon}
|
|
||||||
type={type}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className={showIcon ? 'ltr:ml-2 rtl:mr-2' : ''}>
|
|
||||||
{title ? (
|
|
||||||
<div
|
|
||||||
className={`font-semibold mb-1 ${typeMap.titleColor}`}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{closable ? renderClose() : null}
|
|
||||||
</motion.div>
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const alertDefaultClass = 'p-2 relative flex'
|
||||||
|
|
||||||
|
const alertClass = classNames(
|
||||||
|
'alert',
|
||||||
|
alertDefaultClass,
|
||||||
|
typeMap.backgroundColor,
|
||||||
|
typeMap.textColor,
|
||||||
|
!title ? 'font-semibold' : '',
|
||||||
|
closable ? 'justify-between' : '',
|
||||||
|
closable && !title ? 'items-center' : '',
|
||||||
|
rounded && 'rounded-lg',
|
||||||
|
className,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (display === 'hide') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
ref={ref}
|
||||||
|
className={alertClass}
|
||||||
|
initial={{ opacity: 1 }}
|
||||||
|
animate={display === 'hiding' ? 'exit' : 'animate'}
|
||||||
|
transition={{ duration: 0.25, type: 'tween' }}
|
||||||
|
variants={{
|
||||||
|
animate: {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<div className={`flex min-w-0 ${title ? '' : 'items-center'}`}>
|
||||||
|
{showIcon && <StatusIcon iconColor={typeMap.iconColor} custom={customIcon} type={type} />}
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'min-w-0 flex-1 whitespace-normal break-words leading-6',
|
||||||
|
showIcon ? 'ltr:ml-2 rtl:mr-2' : '',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{title ? <div className={`font-semibold mb-1 ${typeMap.titleColor}`}>{title}</div> : null}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{closable ? renderClose() : null}
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
Alert.displayName = 'Alert'
|
Alert.displayName = 'Alert'
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,42 @@ const AccessDenied = React.lazy(() => import('@/views/AccessDenied'))
|
||||||
const NotFound = React.lazy(() => import('@/views/NotFound'))
|
const NotFound = React.lazy(() => import('@/views/NotFound'))
|
||||||
const DatabaseSetup = React.lazy(() => import('@/views/setup/DatabaseSetup'))
|
const DatabaseSetup = React.lazy(() => import('@/views/setup/DatabaseSetup'))
|
||||||
|
|
||||||
|
const RootRedirect = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const searchParams = new URLSearchParams(location.search)
|
||||||
|
const isPasswordResetLink = searchParams.has('userId') && searchParams.has('resetToken')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Navigate
|
||||||
|
to={
|
||||||
|
isPasswordResetLink
|
||||||
|
? `${ROUTES_ENUM.authenticated.resetPassword}${location.search}`
|
||||||
|
: hasSubdomain()
|
||||||
|
? ROUTES_ENUM.authenticated.login
|
||||||
|
: ROUTES_ENUM.public.home
|
||||||
|
}
|
||||||
|
replace
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const LegacyPasswordResetRedirect = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
return <Navigate to={`${ROUTES_ENUM.authenticated.resetPassword}${location.search}`} replace />
|
||||||
|
}
|
||||||
|
|
||||||
|
const LegacyEmailConfirmationRedirect = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
return <Navigate to={location.pathname.replace(/^\/account/, '')} replace />
|
||||||
|
}
|
||||||
|
|
||||||
export const DynamicRouter: React.FC = () => {
|
export const DynamicRouter: React.FC = () => {
|
||||||
const { routes, loading, error } = useDynamicRoutes()
|
const { routes, loading, error } = useDynamicRoutes()
|
||||||
const { registeredComponents, renderComponent, isComponentRegistered } = useComponents()
|
const { registeredComponents, renderComponent, isComponentRegistered } = useComponents()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const dynamicRoutes = React.useMemo(() => mapDynamicRoutes(routes), [routes])
|
const dynamicRoutes = React.useMemo(() => mapDynamicRoutes(routes), [routes])
|
||||||
|
|
||||||
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı
|
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı
|
||||||
|
|
@ -33,7 +64,11 @@ export const DynamicRouter: React.FC = () => {
|
||||||
{dynamicRoutes
|
{dynamicRoutes
|
||||||
.filter((r) => r.routeType === 'protected')
|
.filter((r) => r.routeType === 'protected')
|
||||||
.map((route) => {
|
.map((route) => {
|
||||||
const Component = route.getComponent(registeredComponents, renderComponent, isComponentRegistered)
|
const Component = route.getComponent(
|
||||||
|
registeredComponents,
|
||||||
|
renderComponent,
|
||||||
|
isComponentRegistered,
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
key={route.key}
|
key={route.key}
|
||||||
|
|
@ -85,7 +120,11 @@ export const DynamicRouter: React.FC = () => {
|
||||||
hasSubdomain() ? r.routeType === 'authenticated' : r.routeType !== 'protected',
|
hasSubdomain() ? r.routeType === 'authenticated' : r.routeType !== 'protected',
|
||||||
)
|
)
|
||||||
.map((route) => {
|
.map((route) => {
|
||||||
const Component = route.getComponent(registeredComponents, renderComponent, isComponentRegistered)
|
const Component = route.getComponent(
|
||||||
|
registeredComponents,
|
||||||
|
renderComponent,
|
||||||
|
isComponentRegistered,
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
key={route.key}
|
key={route.key}
|
||||||
|
|
@ -100,15 +139,9 @@ export const DynamicRouter: React.FC = () => {
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* root redirect */}
|
{/* root redirect */}
|
||||||
<Route
|
<Route path="/" element={<RootRedirect />} />
|
||||||
path="/"
|
<Route path="/account/confirm/:userId/:token" element={<LegacyEmailConfirmationRedirect />} />
|
||||||
element={
|
<Route path="/account/reset-password" element={<LegacyPasswordResetRedirect />} />
|
||||||
<Navigate
|
|
||||||
to={hasSubdomain() ? ROUTES_ENUM.authenticated.login : ROUTES_ENUM.public.home}
|
|
||||||
replace
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* public access denied (statik) */}
|
{/* public access denied (statik) */}
|
||||||
<Route
|
<Route
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ export const resetPassword = (userId: string, resetToken: string, password: stri
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const sendAccountConfirmationCode = (data: any) => {
|
export const sendAccountConfirmationCode = (data: any) =>
|
||||||
apiService.fetchData({
|
apiService.fetchData<boolean>({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'api/app/platform-account/send-account-confirmation-code',
|
url: 'api/app/platform-account/send-account-confirmation-code',
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -61,7 +61,6 @@ export const sendAccountConfirmationCode = (data: any) => {
|
||||||
captchaResponse: data.captchaResponse,
|
captchaResponse: data.captchaResponse,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
export const verifyAccountConfirmationCode = (userId: string, token: string) =>
|
export const verifyAccountConfirmationCode = (userId: string, token: string) =>
|
||||||
apiService.fetchData({
|
apiService.fetchData({
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import {
|
import {
|
||||||
sendAccountConfirmationCode,
|
sendAccountConfirmationCode,
|
||||||
verifyAccountConfirmationCode,
|
verifyAccountConfirmationCode,
|
||||||
|
|
@ -12,15 +13,28 @@ function useAccount() {
|
||||||
|
|
||||||
const sendConfirmationCode = async (values: any) => {
|
const sendConfirmationCode = async (values: any) => {
|
||||||
try {
|
try {
|
||||||
await sendAccountConfirmationCode(values)
|
const result = await sendAccountConfirmationCode(values)
|
||||||
|
if (result.data !== true) {
|
||||||
|
throw new Error('This email is already confirmed or no account was found.')
|
||||||
|
}
|
||||||
|
|
||||||
setError('')
|
setError('')
|
||||||
setMessage('Verification code has been sent to your e-mail address.')
|
setMessage('Verification code has been sent to your e-mail address.')
|
||||||
|
return {
|
||||||
|
status: 'success',
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
const err =
|
||||||
|
error?.response?.data?.error?.message ||
|
||||||
|
error?.response?.data?.message ||
|
||||||
|
error?.message ||
|
||||||
|
error.toString()
|
||||||
|
|
||||||
setMessage('')
|
setMessage('')
|
||||||
setError(error?.response?.data?.message || error.toString())
|
setError(err)
|
||||||
return {
|
return {
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
message: error?.response?.data?.message || error.toString(),
|
message: err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +52,7 @@ function useAccount() {
|
||||||
setError('')
|
setError('')
|
||||||
setMessage('Your account is confirmed')
|
setMessage('Your account is confirmed')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate('/account/login')
|
navigate(ROUTES_ENUM.authenticated.login)
|
||||||
}, 3000)
|
}, 3000)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid token')
|
throw new Error('Invalid token')
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,11 @@ const Views = (props: ViewsProps) => {
|
||||||
<ErrorBoundary fallbackRender={fallbackRender}>
|
<ErrorBoundary fallbackRender={fallbackRender}>
|
||||||
<Suspense fallback={<Loading loading={true} />}>
|
<Suspense fallback={<Loading loading={true} />}>
|
||||||
{!!warning?.length && (
|
{!!warning?.length && (
|
||||||
<Alert showIcon className="mb-4" type="warning">
|
<Alert showIcon className="mb-4 text-sm text-left" type="warning">
|
||||||
{warning.map((w, i) => (
|
{warning.map((w, i) => (
|
||||||
<div key={i}>{w}</div>
|
<div key={i} className="whitespace-normal break-words">
|
||||||
|
{w}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ import { sendExtendLoginRequest } from '@/services/account.service'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
|
import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
|
||||||
|
import { TurnstileInstance } from '@marsidev/react-turnstile'
|
||||||
import { Field, Form, Formik } from 'formik'
|
import { Field, Form, Formik } from 'formik'
|
||||||
import { useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
|
@ -35,21 +36,23 @@ const ExtendLogin = () => {
|
||||||
|
|
||||||
const [message, setMessage] = useTimeOutMessage(10000)
|
const [message, setMessage] = useTimeOutMessage(10000)
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const captchaRef = useRef<TurnstileInstance>(null)
|
||||||
|
|
||||||
const onSendMail = async (
|
const onSendMail = async (
|
||||||
values: ExtendLoginFormSchema,
|
values: ExtendLoginFormSchema,
|
||||||
setSubmitting: (isSubmitting: boolean) => void,
|
setSubmitting: (isSubmitting: boolean) => void,
|
||||||
|
setFieldValue: (field: string, value: string) => void,
|
||||||
) => {
|
) => {
|
||||||
const { email, captchaResponse } = values
|
const { email, captchaResponse } = values
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
try {
|
try {
|
||||||
const resp = await sendExtendLoginRequest({ email, captchaResponse })
|
await sendExtendLoginRequest({ email, captchaResponse })
|
||||||
if (resp.data) {
|
setEmailSent(true)
|
||||||
setSubmitting(false)
|
|
||||||
setEmailSent(true)
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setMessage(error?.response?.data || error.toString())
|
setMessage(error?.response?.data || error.toString())
|
||||||
|
} finally {
|
||||||
|
setFieldValue('captchaResponse', '')
|
||||||
|
captchaRef.current?.reset()
|
||||||
setSubmitting(false)
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,11 +66,13 @@ const ExtendLogin = () => {
|
||||||
></Helmet>
|
></Helmet>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-1">{translate('::Abp.Account.ExtendLogin.Title')}</h3>
|
<h3 className="mb-1">{translate('::Abp.Account.ExtendLogin.Title')}</h3>
|
||||||
<p>{translate('::Abp.Account.ExtendLogin.Description')}</p>
|
<Alert showIcon className="mt-4 mb-4" type="success">
|
||||||
|
{translate('::Abp.Account.ExtendLogin.Description')}
|
||||||
|
</Alert>
|
||||||
<div className="mt-4 text-center">
|
<div className="mt-4 text-center">
|
||||||
<span>{translate('::Abp.Account.Backto')} </span>
|
<span>{translate('::Abp.Account.Backto')} </span>
|
||||||
<ActionLink to={signInUrl}>{translate('::Abp.Account.SignIn')}</ActionLink>
|
<ActionLink to={signInUrl}>{translate('::Abp.Account.SignIn')}</ActionLink>
|
||||||
</div>{' '}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -92,9 +97,9 @@ const ExtendLogin = () => {
|
||||||
captchaResponse: '',
|
captchaResponse: '',
|
||||||
}}
|
}}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={(values, { setSubmitting }) => {
|
onSubmit={(values, { setSubmitting, setFieldValue }) => {
|
||||||
if (!disableSubmit) {
|
if (!disableSubmit) {
|
||||||
onSendMail(values, setSubmitting)
|
onSendMail(values, setSubmitting, setFieldValue)
|
||||||
} else {
|
} else {
|
||||||
setSubmitting(false)
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +120,7 @@ const ExtendLogin = () => {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
<Captcha
|
<Captcha
|
||||||
|
ref={captchaRef}
|
||||||
onError={() => setFieldValue('captchaResponse', '')}
|
onError={() => setFieldValue('captchaResponse', '')}
|
||||||
onExpire={() => setFieldValue('captchaResponse', '')}
|
onExpire={() => setFieldValue('captchaResponse', '')}
|
||||||
onSuccess={(token: string) => setFieldValue('captchaResponse', token)}
|
onSuccess={(token: string) => setFieldValue('captchaResponse', token)}
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,11 @@ import { sendPasswordResetCode } from '@/services/account.service'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
|
import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
|
||||||
|
import { TurnstileInstance } from '@marsidev/react-turnstile'
|
||||||
import type { AxiosError } from 'axios'
|
import type { AxiosError } from 'axios'
|
||||||
import { Field, Form, Formik } from 'formik'
|
import { Field, Form, Formik } from 'formik'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import { useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
|
@ -37,23 +38,25 @@ const ForgotPassword = () => {
|
||||||
|
|
||||||
const [message, setMessage] = useTimeOutMessage()
|
const [message, setMessage] = useTimeOutMessage()
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const captchaRef = useRef<TurnstileInstance>(null)
|
||||||
|
|
||||||
const onSendMail = async (
|
const onSendMail = async (
|
||||||
values: ForgotPasswordFormSchema,
|
values: ForgotPasswordFormSchema,
|
||||||
setSubmitting: (isSubmitting: boolean) => void,
|
setSubmitting: (isSubmitting: boolean) => void,
|
||||||
|
setFieldValue: (field: string, value: string) => void,
|
||||||
) => {
|
) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
try {
|
try {
|
||||||
const resp = await sendPasswordResetCode(values)
|
await sendPasswordResetCode(values)
|
||||||
if (resp.data) {
|
setEmailSent(true)
|
||||||
setSubmitting(false)
|
|
||||||
setEmailSent(true)
|
|
||||||
}
|
|
||||||
} catch (errors) {
|
} catch (errors) {
|
||||||
setMessage(
|
setMessage(
|
||||||
(errors as AxiosError<{ message: string }>)?.response?.data?.message ||
|
(errors as AxiosError<{ message: string }>)?.response?.data?.message ||
|
||||||
(errors as Error).toString(),
|
(errors as Error).toString(),
|
||||||
)
|
)
|
||||||
|
} finally {
|
||||||
|
setFieldValue('captchaResponse', '')
|
||||||
|
captchaRef.current?.reset()
|
||||||
setSubmitting(false)
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +77,6 @@ const ForgotPassword = () => {
|
||||||
{emailSent ? (
|
{emailSent ? (
|
||||||
<>
|
<>
|
||||||
<h3 className="mb-1">{translate('::Abp.Account.ForgotPassword.Checkyouremail')}</h3>
|
<h3 className="mb-1">{translate('::Abp.Account.ForgotPassword.Checkyouremail')}</h3>
|
||||||
<p>{translate('::Abp.Account.ForgotPassword.Checkyouremail.Message')}</p>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
@ -88,51 +90,64 @@ const ForgotPassword = () => {
|
||||||
{message}
|
{message}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<TenantSelector />
|
{emailSent ? (
|
||||||
<Formik
|
<>
|
||||||
initialValues={{
|
<Alert showIcon className="mb-4" type="success">
|
||||||
email: userName,
|
{translate('::Abp.Account.ForgotPassword.Checkyouremail.Message')}
|
||||||
captchaResponse: '',
|
</Alert>
|
||||||
}}
|
<div className="mt-4 text-center">
|
||||||
validationSchema={validationSchema}
|
<span>{translate('::Abp.Account.Backto')} </span>
|
||||||
onSubmit={(values, { setSubmitting }) => {
|
<ActionLink to={signInUrl}>{translate('::Abp.Account.SignIn')}</ActionLink>
|
||||||
if (!disableSubmit) {
|
</div>
|
||||||
onSendMail(values, setSubmitting)
|
</>
|
||||||
} else {
|
) : (
|
||||||
setSubmitting(false)
|
<>
|
||||||
}
|
<TenantSelector />
|
||||||
}}
|
<Formik
|
||||||
>
|
initialValues={{
|
||||||
{({ touched, errors, isSubmitting, setFieldValue }) => (
|
email: userName,
|
||||||
<Form>
|
captchaResponse: '',
|
||||||
<FormContainer>
|
}}
|
||||||
<div className={emailSent ? 'hidden' : ''}>
|
validationSchema={validationSchema}
|
||||||
<FormItem invalid={errors.email && touched.email} errorMessage={errors.email}>
|
onSubmit={(values, { setSubmitting, setFieldValue }) => {
|
||||||
<Field
|
if (!disableSubmit) {
|
||||||
type="email"
|
onSendMail(values, setSubmitting, setFieldValue)
|
||||||
autoComplete="off"
|
} else {
|
||||||
name="email"
|
setSubmitting(false)
|
||||||
placeholder={translate('::Abp.Account.EmailAddress')}
|
}
|
||||||
component={Input}
|
}}
|
||||||
|
>
|
||||||
|
{({ touched, errors, isSubmitting, setFieldValue }) => (
|
||||||
|
<Form>
|
||||||
|
<FormContainer>
|
||||||
|
<FormItem invalid={errors.email && touched.email} errorMessage={errors.email}>
|
||||||
|
<Field
|
||||||
|
type="email"
|
||||||
|
autoComplete="off"
|
||||||
|
name="email"
|
||||||
|
placeholder={translate('::Abp.Account.EmailAddress')}
|
||||||
|
component={Input}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<Captcha
|
||||||
|
ref={captchaRef}
|
||||||
|
onError={() => setFieldValue('captchaResponse', '')}
|
||||||
|
onExpire={() => setFieldValue('captchaResponse', '')}
|
||||||
|
onSuccess={(token: string) => setFieldValue('captchaResponse', token)}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
<Button block loading={isSubmitting} variant="solid" type="submit">
|
||||||
</div>
|
Send Email
|
||||||
<Captcha
|
</Button>
|
||||||
onError={() => setFieldValue('captchaResponse', '')}
|
<div className="mt-4 text-center">
|
||||||
onExpire={() => setFieldValue('captchaResponse', '')}
|
<span>{translate('::Abp.Account.Backto')} </span>
|
||||||
onSuccess={(token: string) => setFieldValue('captchaResponse', token)}
|
<ActionLink to={signInUrl}>{translate('::Abp.Account.SignIn')}</ActionLink>
|
||||||
/>
|
</div>
|
||||||
<Button block loading={isSubmitting} variant="solid" type="submit">
|
</FormContainer>
|
||||||
{emailSent ? 'Resend Email' : 'Send Email'}
|
</Form>
|
||||||
</Button>
|
)}
|
||||||
<div className="mt-4 text-center">
|
</Formik>
|
||||||
<span>{translate('::Abp.Account.Backto')} </span>
|
</>
|
||||||
<ActionLink to={signInUrl}>{translate('::Abp.Account.SignIn')}</ActionLink>
|
)}
|
||||||
</div>
|
|
||||||
</FormContainer>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,11 @@ const Login = () => {
|
||||||
findUiVersion()
|
findUiVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCaptcha) {
|
||||||
|
setFieldValue('captchaResponse', '')
|
||||||
|
captchaRef.current?.reset()
|
||||||
|
}
|
||||||
|
|
||||||
setSubmitting(false)
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import useAuth from '@/utils/hooks/useAuth'
|
import useAuth from '@/utils/hooks/useAuth'
|
||||||
import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
|
import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
|
||||||
import Captcha from '@/components/shared/Captcha'
|
import Captcha from '@/components/shared/Captcha'
|
||||||
|
import { TurnstileInstance } from '@marsidev/react-turnstile'
|
||||||
import { Field, Form, Formik } from 'formik'
|
import { Field, Form, Formik } from 'formik'
|
||||||
import { useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
|
|
@ -30,6 +31,7 @@ const validationSchema = Yup.object().shape({
|
||||||
confirmPassword: Yup.string().oneOf([Yup.ref('password')], 'Your passwords do not match'),
|
confirmPassword: Yup.string().oneOf([Yup.ref('password')], 'Your passwords do not match'),
|
||||||
name: Yup.string().required(),
|
name: Yup.string().required(),
|
||||||
surname: Yup.string().required(),
|
surname: Yup.string().required(),
|
||||||
|
captchaResponse: Yup.string().required(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const Register = () => {
|
const Register = () => {
|
||||||
|
|
@ -41,24 +43,30 @@ const Register = () => {
|
||||||
|
|
||||||
const [message, setMessage] = useState('')
|
const [message, setMessage] = useState('')
|
||||||
const [error, setError] = useTimeOutMessage(10000)
|
const [error, setError] = useTimeOutMessage(10000)
|
||||||
|
const captchaRef = useRef<TurnstileInstance>(null)
|
||||||
|
|
||||||
const onSignUp = async (
|
const onSignUp = async (
|
||||||
values: SignUpFormSchema,
|
values: SignUpFormSchema,
|
||||||
setSubmitting: (isSubmitting: boolean) => void,
|
setSubmitting: (isSubmitting: boolean) => void,
|
||||||
|
setFieldValue: (field: string, value: string) => void,
|
||||||
) => {
|
) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
const result = await signUp(values)
|
try {
|
||||||
|
const result = await signUp(values)
|
||||||
|
|
||||||
if (result?.status === 'failed') {
|
if (result?.status === 'failed' || result?.status === 'error') {
|
||||||
setError(result.message)
|
setError(result.message)
|
||||||
setMessage('')
|
setMessage('')
|
||||||
} else {
|
} else {
|
||||||
setMessage(translate('::Abp.Account.Register.ResultMessage'))
|
setMessage(translate('::Abp.Account.Register.ResultMessage'))
|
||||||
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
||||||
setError('')
|
setError('')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setFieldValue('captchaResponse', '')
|
||||||
|
captchaRef.current?.reset()
|
||||||
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitting(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -84,107 +92,117 @@ const Register = () => {
|
||||||
{error}
|
{error}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<TenantSelector />
|
{message ? (
|
||||||
<Formik
|
<div className="mt-4 text-center">
|
||||||
initialValues={{
|
<span>{translate('::Abp.Account.Register.AlreadyHaveAnAccount')} </span>
|
||||||
email: '',
|
<ActionLink to={signInUrl}>{translate('::Abp.Account.SignIn')}</ActionLink>
|
||||||
password: '',
|
</div>
|
||||||
confirmPassword: '',
|
) : (
|
||||||
name: '',
|
<>
|
||||||
surname: '',
|
<TenantSelector />
|
||||||
captchaResponse: '',
|
<Formik
|
||||||
}}
|
initialValues={{
|
||||||
validationSchema={validationSchema}
|
email: '',
|
||||||
onSubmit={(values, { setSubmitting }) => {
|
password: '',
|
||||||
if (!disableSubmit) {
|
confirmPassword: '',
|
||||||
onSignUp(values, setSubmitting)
|
name: '',
|
||||||
} else {
|
surname: '',
|
||||||
setSubmitting(false)
|
captchaResponse: '',
|
||||||
}
|
}}
|
||||||
}}
|
validationSchema={validationSchema}
|
||||||
>
|
onSubmit={(values, { setSubmitting, setFieldValue }) => {
|
||||||
{({ touched, errors, isSubmitting, setFieldValue }) => (
|
if (!disableSubmit) {
|
||||||
<Form>
|
onSignUp(values, setSubmitting, setFieldValue)
|
||||||
<FormContainer>
|
} else {
|
||||||
<FormItem
|
setSubmitting(false)
|
||||||
label={translate('::Abp.Account.EmailAddress')}
|
}
|
||||||
invalid={errors.email && touched.email}
|
}}
|
||||||
errorMessage={errors.email}
|
>
|
||||||
>
|
{({ touched, errors, isSubmitting, setFieldValue }) => (
|
||||||
<Field
|
<Form>
|
||||||
type="email"
|
<FormContainer>
|
||||||
autoComplete="off"
|
<FormItem
|
||||||
name="email"
|
label={translate('::Abp.Account.EmailAddress')}
|
||||||
placeholder={translate('::Abp.Account.EmailAddress')}
|
invalid={errors.email && touched.email}
|
||||||
component={Input}
|
errorMessage={errors.email}
|
||||||
/>
|
>
|
||||||
</FormItem>
|
<Field
|
||||||
<FormItem
|
type="email"
|
||||||
label={translate('::Abp.Identity.Password')}
|
autoComplete="off"
|
||||||
invalid={errors.password && touched.password}
|
name="email"
|
||||||
errorMessage={errors.password}
|
placeholder={translate('::Abp.Account.EmailAddress')}
|
||||||
>
|
component={Input}
|
||||||
<Field
|
/>
|
||||||
autoComplete="off"
|
</FormItem>
|
||||||
name="password"
|
<FormItem
|
||||||
placeholder={translate('::Abp.Identity.Password')}
|
label={translate('::Abp.Identity.Password')}
|
||||||
component={PasswordInput}
|
invalid={errors.password && touched.password}
|
||||||
/>
|
errorMessage={errors.password}
|
||||||
</FormItem>
|
>
|
||||||
<FormItem
|
<Field
|
||||||
label={translate('::Abp.Account.ConfirmPassword')}
|
autoComplete="off"
|
||||||
invalid={errors.confirmPassword && touched.confirmPassword}
|
name="password"
|
||||||
errorMessage={errors.confirmPassword}
|
placeholder={translate('::Abp.Identity.Password')}
|
||||||
>
|
component={PasswordInput}
|
||||||
<Field
|
/>
|
||||||
autoComplete="off"
|
</FormItem>
|
||||||
name="confirmPassword"
|
<FormItem
|
||||||
placeholder={translate('::Abp.Account.ConfirmPassword')}
|
label={translate('::Abp.Account.ConfirmPassword')}
|
||||||
component={PasswordInput}
|
invalid={errors.confirmPassword && touched.confirmPassword}
|
||||||
/>
|
errorMessage={errors.confirmPassword}
|
||||||
</FormItem>
|
>
|
||||||
<FormItem
|
<Field
|
||||||
label={translate('::Abp.Identity.User.UserInformation.Name')}
|
autoComplete="off"
|
||||||
invalid={errors.name && touched.name}
|
name="confirmPassword"
|
||||||
errorMessage={errors.name}
|
placeholder={translate('::Abp.Account.ConfirmPassword')}
|
||||||
>
|
component={PasswordInput}
|
||||||
<Field
|
/>
|
||||||
type="name"
|
</FormItem>
|
||||||
autoComplete="off"
|
<FormItem
|
||||||
name="name"
|
label={translate('::Abp.Identity.User.UserInformation.Name')}
|
||||||
placeholder={translate('::Abp.Identity.User.UserInformation.Name')}
|
invalid={errors.name && touched.name}
|
||||||
component={Input}
|
errorMessage={errors.name}
|
||||||
/>
|
>
|
||||||
</FormItem>
|
<Field
|
||||||
<FormItem
|
type="name"
|
||||||
label={translate('::Abp.Identity.User.UserInformation.Surname')}
|
autoComplete="off"
|
||||||
invalid={errors.surname && touched.surname}
|
name="name"
|
||||||
errorMessage={errors.surname}
|
placeholder={translate('::Abp.Identity.User.UserInformation.Name')}
|
||||||
>
|
component={Input}
|
||||||
<Field
|
/>
|
||||||
type="surname"
|
</FormItem>
|
||||||
autoComplete="off"
|
<FormItem
|
||||||
name="surname"
|
label={translate('::Abp.Identity.User.UserInformation.Surname')}
|
||||||
placeholder={translate('::Abp.Identity.User.UserInformation.Surname')}
|
invalid={errors.surname && touched.surname}
|
||||||
component={Input}
|
errorMessage={errors.surname}
|
||||||
/>
|
>
|
||||||
</FormItem>
|
<Field
|
||||||
<Captcha
|
type="surname"
|
||||||
onError={() => setFieldValue('captchaResponse', '')}
|
autoComplete="off"
|
||||||
onExpire={() => setFieldValue('captchaResponse', '')}
|
name="surname"
|
||||||
onSuccess={(token) => setFieldValue('captchaResponse', token)}
|
placeholder={translate('::Abp.Identity.User.UserInformation.Surname')}
|
||||||
/>
|
component={Input}
|
||||||
<Button block loading={isSubmitting} variant="solid" type="submit">
|
/>
|
||||||
{isSubmitting ? 'Creating Account...' : 'Sign Up'}
|
</FormItem>
|
||||||
</Button>
|
<Captcha
|
||||||
<div className="mt-4 text-center">
|
ref={captchaRef}
|
||||||
<span>{translate('::Abp.Account.Register.AlreadyHaveAnAccount')} </span>
|
onError={() => setFieldValue('captchaResponse', '')}
|
||||||
<ActionLink to={signInUrl}>{translate('::Abp.Account.SignIn')}</ActionLink>
|
onExpire={() => setFieldValue('captchaResponse', '')}
|
||||||
</div>
|
onSuccess={(token) => setFieldValue('captchaResponse', token)}
|
||||||
</FormContainer>
|
/>
|
||||||
</Form>
|
<Button block loading={isSubmitting} variant="solid" type="submit">
|
||||||
)}
|
{isSubmitting ? 'Creating Account...' : 'Sign Up'}
|
||||||
</Formik>
|
</Button>
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<span>{translate('::Abp.Account.Register.AlreadyHaveAnAccount')} </span>
|
||||||
|
<ActionLink to={signInUrl}>{translate('::Abp.Account.SignIn')}</ActionLink>
|
||||||
|
</div>
|
||||||
|
</FormContainer>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -59,16 +59,14 @@ const ResetPassword = () => {
|
||||||
const { password } = values
|
const { password } = values
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
try {
|
try {
|
||||||
const resp = await resetPassword(userId, resetToken, password)
|
await resetPassword(userId, resetToken, password)
|
||||||
if (resp.data) {
|
setResetComplete(true)
|
||||||
setSubmitting(false)
|
|
||||||
setResetComplete(true)
|
|
||||||
}
|
|
||||||
} catch (errors) {
|
} catch (errors) {
|
||||||
setMessage(
|
setMessage(
|
||||||
(errors as AxiosError<{ message: string }>)?.response?.data?.message ||
|
(errors as AxiosError<{ message: string }>)?.response?.data?.message ||
|
||||||
(errors as Error).toString(),
|
(errors as Error).toString(),
|
||||||
)
|
)
|
||||||
|
} finally {
|
||||||
setSubmitting(false)
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ import { Field, Form, Formik } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { ActionLink, TenantSelector } from '@/components/shared'
|
import { ActionLink, TenantSelector } from '@/components/shared'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { store } from '@/store'
|
import { store, useStoreActions } from '@/store'
|
||||||
import Captcha from '@/components/shared/Captcha'
|
import Captcha from '@/components/shared/Captcha'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { APP_NAME } from '@/constants/app.constant'
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
|
import { TurnstileInstance } from '@marsidev/react-turnstile'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
type FormSchema = {
|
type FormSchema = {
|
||||||
email: string
|
email: string
|
||||||
|
|
@ -25,13 +27,32 @@ const SendConfirmationCode = () => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const { message, error, sendConfirmationCode } = useAccount()
|
const { message, error, sendConfirmationCode } = useAccount()
|
||||||
|
const { setWarning } = useStoreActions((actions) => actions.base.messages)
|
||||||
|
const captchaRef = useRef<TurnstileInstance>(null)
|
||||||
|
const [captchaKey, setCaptchaKey] = useState(0)
|
||||||
|
|
||||||
const onSubmit = async (values: FormSchema, setSubmitting: (isSubmitting: boolean) => void) => {
|
useEffect(() => {
|
||||||
|
setWarning('')
|
||||||
|
}, [setWarning])
|
||||||
|
|
||||||
|
const onSubmit = async (
|
||||||
|
values: FormSchema,
|
||||||
|
setSubmitting: (isSubmitting: boolean) => void,
|
||||||
|
setFieldValue: (field: string, value: string) => void,
|
||||||
|
) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
|
||||||
await sendConfirmationCode(values)
|
try {
|
||||||
|
const result = await sendConfirmationCode(values)
|
||||||
setSubmitting(false)
|
if (result?.status !== 'failed') {
|
||||||
|
setWarning('')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setFieldValue('captchaResponse', '')
|
||||||
|
captchaRef.current?.reset()
|
||||||
|
setCaptchaKey((key) => key + 1)
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -42,10 +63,12 @@ const SendConfirmationCode = () => {
|
||||||
defaultTitle={APP_NAME}
|
defaultTitle={APP_NAME}
|
||||||
></Helmet>
|
></Helmet>
|
||||||
|
|
||||||
<div className="mb-8">
|
{!message && (
|
||||||
<h3 className="mb-1">{translate('::Abp.Account.SendConfirmationCode')}</h3>
|
<div className="mb-8">
|
||||||
<p>{translate('::Abp.Account.SendConfirmationCode.Message')}</p>
|
<h3 className="mb-1">{translate('::Abp.Account.SendConfirmationCode')}</h3>
|
||||||
</div>
|
<p>{translate('::Abp.Account.SendConfirmationCode.Message')}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
{message && (
|
{message && (
|
||||||
<Alert showIcon className="mb-4" type="success">
|
<Alert showIcon className="mb-4" type="success">
|
||||||
|
|
@ -57,51 +80,64 @@ const SendConfirmationCode = () => {
|
||||||
{error}
|
{error}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<TenantSelector />
|
{message ? (
|
||||||
<Formik
|
<div className="mt-4 text-center">
|
||||||
initialValues={{
|
<span>{translate('::Abp.Account.Backto')} </span>
|
||||||
email: userName,
|
<ActionLink to={ROUTES_ENUM.authenticated.login}>
|
||||||
captchaResponse: '',
|
{translate('::Abp.Account.SignIn')}
|
||||||
}}
|
</ActionLink>
|
||||||
validationSchema={validationSchema}
|
</div>
|
||||||
onSubmit={(values, { setSubmitting }) => {
|
) : (
|
||||||
onSubmit(values, setSubmitting)
|
<>
|
||||||
}}
|
<TenantSelector />
|
||||||
>
|
<Formik
|
||||||
{({ touched, errors, isSubmitting, setFieldValue }) => (
|
initialValues={{
|
||||||
<Form>
|
email: userName,
|
||||||
<FormContainer>
|
captchaResponse: '',
|
||||||
<FormItem
|
}}
|
||||||
label={translate('::Abp.Account.EmailAddress')}
|
validationSchema={validationSchema}
|
||||||
invalid={errors.email && touched.email}
|
onSubmit={(values, { setSubmitting, setFieldValue }) => {
|
||||||
errorMessage={errors.email}
|
onSubmit(values, setSubmitting, setFieldValue)
|
||||||
>
|
}}
|
||||||
<Field
|
>
|
||||||
type="email"
|
{({ touched, errors, isSubmitting, setFieldValue }) => (
|
||||||
autoComplete="off"
|
<Form>
|
||||||
name="email"
|
<FormContainer>
|
||||||
placeholder={translate('::Abp.Account.EmailAddress')}
|
<FormItem
|
||||||
component={Input}
|
label={translate('::Abp.Account.EmailAddress')}
|
||||||
/>
|
invalid={errors.email && touched.email}
|
||||||
</FormItem>
|
errorMessage={errors.email}
|
||||||
<Captcha
|
>
|
||||||
onError={() => setFieldValue('captchaResponse', '')}
|
<Field
|
||||||
onExpire={() => setFieldValue('captchaResponse', '')}
|
type="email"
|
||||||
onSuccess={(token: string) => setFieldValue('captchaResponse', token)}
|
autoComplete="off"
|
||||||
/>
|
name="email"
|
||||||
<Button block loading={isSubmitting} variant="solid" type="submit">
|
placeholder={translate('::Abp.Account.EmailAddress')}
|
||||||
{isSubmitting ? 'Sending code...' : 'Send Code'}
|
component={Input}
|
||||||
</Button>
|
/>
|
||||||
<div className="mt-4 text-center">
|
</FormItem>
|
||||||
<span>{translate('::Abp.Account.Backto')} </span>
|
<Captcha
|
||||||
<ActionLink to={ROUTES_ENUM.authenticated.login}>
|
key={captchaKey}
|
||||||
{translate('::Abp.Account.SignIn')}
|
ref={captchaRef}
|
||||||
</ActionLink>
|
onError={() => setFieldValue('captchaResponse', '')}
|
||||||
</div>
|
onExpire={() => setFieldValue('captchaResponse', '')}
|
||||||
</FormContainer>
|
onSuccess={(token: string) => setFieldValue('captchaResponse', token)}
|
||||||
</Form>
|
/>
|
||||||
)}
|
<Button block loading={isSubmitting} variant="solid" type="submit">
|
||||||
</Formik>
|
{isSubmitting ? 'Sending code...' : 'Send Code'}
|
||||||
|
</Button>
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<span>{translate('::Abp.Account.Backto')} </span>
|
||||||
|
<ActionLink to={ROUTES_ENUM.authenticated.login}>
|
||||||
|
{translate('::Abp.Account.SignIn')}
|
||||||
|
</ActionLink>
|
||||||
|
</div>
|
||||||
|
</FormContainer>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ const Logo = (props: LogoProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('logo', 'my-1', className)}
|
className={classNames('logo', 'my-2', className)}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
...{ width: logoWidth },
|
...{ width: logoWidth },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue