|
|
@@ -80,13 +80,37 @@ function InputRow(
|
|
|
function FormGroup(props: FormHTMLAttributes<any>) {
|
|
|
const { className, children, ...reset } = props
|
|
|
return (
|
|
|
- <form className={cn('flex flex-col justify-center items-center gap-4 w-full', className)}
|
|
|
+ <form className={cn('relative flex flex-col justify-center items-center gap-4 w-full', className)}
|
|
|
{...reset}>
|
|
|
{children}
|
|
|
</form>
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+function useCountDown() {
|
|
|
+ const [countDownNum, setCountDownNum] = useState<number>(0)
|
|
|
+ const startCountDown = () => {
|
|
|
+ setCountDownNum(60)
|
|
|
+ const interval = setInterval(() => {
|
|
|
+ setCountDownNum((num) => {
|
|
|
+ if (num <= 1) {
|
|
|
+ clearInterval(interval)
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ return num - 1
|
|
|
+ })
|
|
|
+ }, 1000)
|
|
|
+ }
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ return () => {
|
|
|
+ setCountDownNum(0)
|
|
|
+ }
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ return { countDownNum, startCountDown, setCountDownNum }
|
|
|
+}
|
|
|
+
|
|
|
export function LoginForm() {
|
|
|
const { setErrors, setCurrentTab, onSessionCallback } = useAuthFormState()
|
|
|
const [loading, setLoading] = useState<boolean>(false)
|
|
|
@@ -237,8 +261,6 @@ export function SignupForm() {
|
|
|
|
|
|
try {
|
|
|
setLoading(true)
|
|
|
- await new Promise(resolve => { setTimeout(resolve, 500) })
|
|
|
-
|
|
|
const ret = await Auth.signUp({
|
|
|
username: data.username as string,
|
|
|
password: data.password as string,
|
|
|
@@ -251,7 +273,14 @@ export function SignupForm() {
|
|
|
|
|
|
if (ret.isSignUpComplete) {
|
|
|
// TODO: auto sign in
|
|
|
- console.log(ret)
|
|
|
+ if (ret.nextStep?.signUpStep === 'COMPLETE_AUTO_SIGN_IN') {
|
|
|
+ const { nextStep } = await Auth.autoSignIn()
|
|
|
+ if (nextStep.signInStep === 'DONE') {
|
|
|
+ // signed in
|
|
|
+ setCurrentTab('login')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
setCurrentTab('login')
|
|
|
return
|
|
|
} else {
|
|
|
@@ -320,27 +349,106 @@ export function SignupForm() {
|
|
|
|
|
|
export function ResetPasswordForm() {
|
|
|
const [isSentCode, setIsSentCode] = useState<boolean>(false)
|
|
|
- const { setCurrentTab } = useAuthFormState()
|
|
|
+ const [sentUsername, setSentUsername] = useState<string>('')
|
|
|
+ const { setCurrentTab, setErrors } = useAuthFormState()
|
|
|
+ const { countDownNum, startCountDown } = useCountDown()
|
|
|
+ const [loading, setLoading] = useState<boolean>(false)
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ setErrors({})
|
|
|
+ }, [isSentCode])
|
|
|
|
|
|
return (
|
|
|
<FormGroup
|
|
|
autoComplete={'off'}
|
|
|
- onSubmit={(e) => {
|
|
|
+ onSubmit={async (e) => {
|
|
|
+ setErrors(null)
|
|
|
e.preventDefault()
|
|
|
|
|
|
// get submit form input data
|
|
|
const formData = new FormData(e.target as HTMLFormElement)
|
|
|
const data = Object.fromEntries(formData.entries())
|
|
|
- console.log(data)
|
|
|
|
|
|
- setIsSentCode(true)
|
|
|
+ if (!isSentCode) {
|
|
|
+ try {
|
|
|
+ setLoading(true)
|
|
|
+
|
|
|
+ const username = (data.email as string)?.trim()
|
|
|
+ // send reset code
|
|
|
+ const ret = await Auth.resetPassword({ username })
|
|
|
+ console.debug('[Auth] reset pw code sent: ', ret)
|
|
|
+ setSentUsername(username)
|
|
|
+ startCountDown()
|
|
|
+ setIsSentCode(true)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error sending reset code:', error)
|
|
|
+ setErrors({ email: { message: (error as Error).message, title: t('Bad Response.') } })
|
|
|
+ } finally {
|
|
|
+ setLoading(false)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // confirm reset password
|
|
|
+ if ((data.password as string)?.length < 8) {
|
|
|
+ setErrors({
|
|
|
+ password: {
|
|
|
+ message: t('Password must be at least 8 characters.'),
|
|
|
+ title: t('Invalid Password')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return
|
|
|
+ } else if (data.password !== data.confirm_password) {
|
|
|
+ setErrors({
|
|
|
+ confirm_password: {
|
|
|
+ message: t('Passwords do not match.'),
|
|
|
+ title: t('Invalid Password')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ setLoading(true)
|
|
|
+ const ret = await Auth.confirmResetPassword({
|
|
|
+ username: sentUsername,
|
|
|
+ newPassword: data.password as string,
|
|
|
+ confirmationCode: data.code as string
|
|
|
+ })
|
|
|
+
|
|
|
+ console.debug('[Auth] confirm reset pw: ', ret)
|
|
|
+ setCurrentTab('login')
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error confirming reset password:', error)
|
|
|
+ setErrors({ 'confirm_password': { message: (error as Error).message, title: t('Bad Response.') } })
|
|
|
+ } finally {
|
|
|
+ setLoading(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}}>
|
|
|
{isSentCode ? (
|
|
|
<>
|
|
|
+ <div className={'w-full opacity-60 flex justify-end relative h-0 z-[2]'}>
|
|
|
+ {countDownNum > 0 ? (
|
|
|
+ <span className={'text-sm opacity-50 select-none absolute top-3 right-0'}>
|
|
|
+ {countDownNum}s
|
|
|
+ </span>
|
|
|
+ ) : (<a onClick={async () => {
|
|
|
+ startCountDown()
|
|
|
+ try {
|
|
|
+ const ret = await Auth.resetPassword({ username: sentUsername })
|
|
|
+ console.debug('[Auth] reset pw code re-sent: ', ret)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error resending reset code:', error)
|
|
|
+ setErrors({ email: { message: (error as Error).message, title: t('Bad Response.') } })
|
|
|
+ } finally {}
|
|
|
+ }} className={'text-sm opacity-70 hover:opacity-90 underline absolute top-3 right-0 select-none'}>
|
|
|
+ {t('Resend code')}
|
|
|
+ </a>)}
|
|
|
+ </div>
|
|
|
<InputRow id="code" type="text" name="code" required={true}
|
|
|
placeholder={'123456'}
|
|
|
autoComplete={'off'}
|
|
|
label={t('Enter the code sent to your email')}/>
|
|
|
+
|
|
|
<InputRow id="password" type="password" name="password" required={true}
|
|
|
placeholder={t('New Password')}
|
|
|
label={t('New Password')}/>
|
|
|
@@ -352,13 +460,16 @@ export function ResetPasswordForm() {
|
|
|
<div className={'w-full'}>
|
|
|
<Button type="submit"
|
|
|
className={'w-full'}
|
|
|
- variant={'secondary'}
|
|
|
- >{t('Reset password')}</Button>
|
|
|
+ disabled={loading}
|
|
|
+ >
|
|
|
+ {loading && <Loader2Icon className="animate-spin mr-1" size={16}/>}
|
|
|
+ {t('Reset password')}
|
|
|
+ </Button>
|
|
|
|
|
|
<p className={'pt-4 text-center'}>
|
|
|
- <a onClick={() => setIsSentCode(false)}
|
|
|
+ <a onClick={() => setCurrentTab('login')}
|
|
|
className={'text-sm opacity-50 hover:opacity-80 underline'}>
|
|
|
- {t('Resend code')}
|
|
|
+ {t('Back to login')}
|
|
|
</a>
|
|
|
</p>
|
|
|
</div>
|
|
|
@@ -372,8 +483,11 @@ export function ResetPasswordForm() {
|
|
|
<div className={'w-full'}>
|
|
|
<Button type="submit"
|
|
|
className={'w-full'}
|
|
|
- variant={'secondary'}
|
|
|
- >{t('Send code')}</Button>
|
|
|
+ disabled={loading}
|
|
|
+ >
|
|
|
+ {loading && <Loader2Icon className="animate-spin mr-1" size={16}/>}
|
|
|
+ {t('Send code')}
|
|
|
+ </Button>
|
|
|
|
|
|
<p className={'pt-3 text-center'}>
|
|
|
<a onClick={() => setCurrentTab('login')}
|
|
|
@@ -395,30 +509,13 @@ export function ConfirmWithCodeForm(
|
|
|
const [loading, setLoading] = useState<boolean>(false)
|
|
|
const isFromSignIn = props.user?.hasOwnProperty('isSignedIn')
|
|
|
const signUpCodeDeliveryDetails = props.user?.nextStep?.codeDeliveryDetails
|
|
|
- const [countDownNum, setCountDownNum] = useState<number>(0)
|
|
|
- const startCountDown = () => {
|
|
|
- setCountDownNum(60)
|
|
|
- const interval = setInterval(() => {
|
|
|
- setCountDownNum((num) => {
|
|
|
- if (num <= 1) {
|
|
|
- clearInterval(interval)
|
|
|
- return 0
|
|
|
- }
|
|
|
- return num - 1
|
|
|
- })
|
|
|
- }, 1000)
|
|
|
- }
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- return () => {
|
|
|
- setCountDownNum(0)
|
|
|
- }
|
|
|
- }, [])
|
|
|
+ const { countDownNum, startCountDown, setCountDownNum } = useCountDown()
|
|
|
|
|
|
return (
|
|
|
<FormGroup
|
|
|
autoComplete={'off'}
|
|
|
onSubmit={async (e) => {
|
|
|
+ setErrors(null)
|
|
|
e.preventDefault()
|
|
|
|
|
|
// get submit form input data
|
|
|
@@ -433,13 +530,22 @@ export function ConfirmWithCodeForm(
|
|
|
confirmationCode: data.code as string,
|
|
|
})
|
|
|
|
|
|
- console.log('===>>', ret)
|
|
|
+ if (ret.nextStep?.signUpStep === 'COMPLETE_AUTO_SIGN_IN') {
|
|
|
+ const { nextStep } = await Auth.autoSignIn()
|
|
|
+ if (nextStep.signInStep === 'DONE') {
|
|
|
+ // signed in
|
|
|
+ setCurrentTab('login')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ setCurrentTab('login')
|
|
|
} else {
|
|
|
const ret = await Auth.confirmSignIn({
|
|
|
challengeResponse: data.code as string,
|
|
|
})
|
|
|
|
|
|
- console.log('===>>', ret)
|
|
|
+ console.log('===>> confirmSignIn: ', ret)
|
|
|
}
|
|
|
} catch (e) {
|
|
|
setErrors({ code: { message: (e as Error).message, title: t('Bad Response.') } })
|