Initial deployment setup
Deploy staging / deploy (push) Failing after 6s

This commit is contained in:
root committed 2026-08-31 16:45:24 +09:00
commit 33453ecc55
3475 files changed
+850363

No files matched your search

@@ -0,0 +1,16 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export * from './ui';
@@ -0,0 +1,20 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { z } from 'zod';
export const requestResetPasswordWithEmailSchema = z.object({
email: z.email(),
});
@@ -0,0 +1,43 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { z } from 'zod';
export const resetPasswordWithEmailSchema = z
.object({
password: z.string().min(8),
confirmPassword: z.string().min(8),
code: z.string(),
email: z.email(),
})
.refine(({ password, confirmPassword }) => password === confirmPassword, {
message: 'must equal Password',
path: ['confirmPassword'],
})
.refine(({ password }) => /[A-Za-z]/.test(password), {
message: 'must contain at least one letter.',
path: ['password'],
})
.refine(
({ password }) => /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]/.test(password),
{
message: 'must contain at least one special character.',
path: ['password'],
},
)
.refine(({ password }) => !/(.)\1/.test(password), {
message: 'must not contain consecutive identical characters',
path: ['password'],
});
@@ -0,0 +1,17 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export { default as RequestResetPasswordWithEmail } from './request-reset-password-with-email.ui';
export { default as ResetPasswordWithEmailForm } from './reset-password-with-email-form.ui';
@@ -0,0 +1,85 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { useRouter } from 'next/router';
import { zodResolver } from '@hookform/resolvers/zod';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { Button, toast } from '@ufb/react';
import { Path, TextInput, useOAIMutation } from '@/shared';
import { requestResetPasswordWithEmailSchema } from '../request-reset-password-with-email.schema';
type FormType = z.infer<typeof requestResetPasswordWithEmailSchema>;
interface IProps {}
const RequestResetPasswordWithEmail: React.FC<IProps> = () => {
const { t } = useTranslation();
const router = useRouter();
const { register, handleSubmit, formState } = useForm<FormType>({
resolver: zodResolver(requestResetPasswordWithEmailSchema),
});
const { mutate, isPending } = useOAIMutation({
method: 'post',
path: '/api/admin/users/password/reset/code',
queryOptions: {
async onSuccess() {
await router.push(Path.SIGN_IN);
toast.success(t('v2.toast.success'));
},
},
});
return (
<form onSubmit={handleSubmit((data) => mutate(data))}>
<div className="mb-12 flex flex-col gap-4">
<TextInput
type="email"
label="Email"
placeholder={t('v2.placeholder.text')}
error={formState.errors.email?.message}
{...register('email')}
/>
</div>
<div className="flex flex-col gap-2">
<Button
size="medium"
type="submit"
loading={isPending}
disabled={!formState.isValid}
>
{t('v2.auth.reset-password.button.send-email')}
</Button>
<Button
size="medium"
variant="outline"
type="button"
onClick={router.back}
>
{t('button.back')}
</Button>
</div>
</form>
);
};
export default RequestResetPasswordWithEmail;
@@ -0,0 +1,95 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { useRouter } from 'next/router';
import { zodResolver } from '@hookform/resolvers/zod';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { Button, toast } from '@ufb/react';
import { Path, TextInput, useOAIMutation } from '@/shared';
import { resetPasswordWithEmailSchema } from '../reset-password-with-email.schema';
type FormType = z.infer<typeof resetPasswordWithEmailSchema>;
interface IProps {
code: string;
email: string;
}
const ResetPasswordWithEmailForm: React.FC<IProps> = ({ code, email }) => {
const { t } = useTranslation();
const router = useRouter();
const { handleSubmit, register, formState } = useForm<FormType>({
resolver: zodResolver(resetPasswordWithEmailSchema),
defaultValues: { code, email },
});
const { mutate, isPending } = useOAIMutation({
method: 'post',
path: '/api/admin/users/password/reset',
queryOptions: {
async onSuccess() {
toast.success(t('v2.toast.success'));
await router.push(Path.SIGN_IN);
},
},
});
const onSubmit = ({ password }: FormType) =>
mutate({ code, email, password });
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-10 flex flex-col gap-4">
<TextInput
type="email"
label="Email"
placeholder={t('v2.placeholder.text')}
{...register('email')}
disabled
/>
<TextInput
type="password"
label="Password"
placeholder={t('v2.placeholder.text')}
error={formState.errors.password?.message}
{...register('password')}
/>
<TextInput
type="password"
label="Confirm Password"
placeholder={t('v2.placeholder.text')}
error={formState.errors.confirmPassword?.message}
{...register('confirmPassword')}
/>
</div>
<Button
type="submit"
loading={isPending}
disabled={!formState.isDirty}
size="medium"
>
{t('v2.button.confirm')}
</Button>
</form>
);
};
export default ResetPasswordWithEmailForm;
@@ -0,0 +1,17 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export * from './ui';
export * from './lib';
@@ -0,0 +1,16 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export { useOAuthCallback } from './use-oauth-callback';
@@ -0,0 +1,71 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { AxiosError } from 'axios';
import { toast } from '@ufb/react';
import type { IFetchError } from '@/shared';
import { useUserStore } from '@/entities/user';
export const useOAuthCallback = () => {
const router = useRouter();
const { signInWithOAuth } = useUserStore();
const [status, setStatus] = useState<'loading' | 'error'>('loading');
const processedCodeRef = useRef<string | null>(null);
const code = router.query.code as string | undefined;
const error = router.query.error as string | undefined;
const errorDescription = router.query.error_description as string | undefined;
useEffect(() => {
if (error) {
toast.error(
errorDescription ?? 'An error occurred during OAuth2.0 login. Please contact the administrator.',
);
setStatus('error');
return;
}
// Next.js renders once before the query string is available. Do not leave
// the callback in an ambiguous state while the router is hydrating.
if (!router.isReady || !code || processedCodeRef.current === code) return;
// OAuth authorization codes are single-use. React StrictMode can run
// effects twice during local development, so exchange each code once.
processedCodeRef.current = code;
void signInWithOAuth({ code }).catch((error) => {
if (error instanceof AxiosError && error.response) {
const message = error.response.data as IFetchError;
toast.error(
message.message ??
message.axiosError?.error ??
'An error occurred during OAuth2.0 login. Please contact the administrator.',
);
} else {
toast.error(
'An error occurred during OAuth2.0 login. Please contact the administrator.',
);
}
setStatus('error');
});
}, [code, error, errorDescription, router.isReady, signInWithOAuth]);
return { status };
};
@@ -0,0 +1,16 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export { default as SignInWithOAuthButton } from './sign-in-with-oauth-button.ui';
@@ -0,0 +1,141 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { useEffect } from 'react';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { Button } from '@ufb/react';
import { useOAIQuery } from '@/shared';
import { useTenantStore } from '@/entities/tenant';
interface IProps {}
const OAUTH_CALLBACK_URL_STORAGE_KEY = 'ufb.oauth.callback-url';
const OAUTH_FORCE_LOGIN_STORAGE_KEY = 'ufb.oauth.force-login';
const SignInWithOAuthButton: React.FC<IProps> = () => {
const { t } = useTranslation();
const router = useRouter();
const { tenant } = useTenantStore();
const callback_url = (router.query.callback_url ?? '') as string;
const force_login =
typeof window !== 'undefined' ?
(router.query.force_login ??
window.sessionStorage.getItem(OAUTH_FORCE_LOGIN_STORAGE_KEY) ??
'')
: '';
const { data } = useOAIQuery({
path: '/api/admin/auth/signIn/oauth/loginURL',
queryOptions: { enabled: tenant?.useOAuth ?? false },
variables: {
callback_url,
force_login: force_login ? 'true' : undefined,
},
});
useEffect(() => {
if (!force_login || !data?.url || typeof window === 'undefined') return;
if (callback_url) {
window.sessionStorage.setItem(
OAUTH_CALLBACK_URL_STORAGE_KEY,
callback_url,
);
} else {
window.sessionStorage.removeItem(OAUTH_CALLBACK_URL_STORAGE_KEY);
}
window.sessionStorage.setItem(OAUTH_FORCE_LOGIN_STORAGE_KEY, 'true');
window.location.assign(data.url);
}, [callback_url, data?.url, force_login]);
if (tenant?.oauthConfig?.loginButtonType === 'GOOGLE') {
return (
<Button
variant="outline"
size="medium"
disabled={!data?.url}
onClick={() => {
if (typeof window !== 'undefined') {
if (callback_url) {
window.sessionStorage.setItem(
OAUTH_CALLBACK_URL_STORAGE_KEY,
callback_url,
);
} else {
window.sessionStorage.removeItem(OAUTH_CALLBACK_URL_STORAGE_KEY);
}
if (force_login) {
window.sessionStorage.setItem(
OAUTH_FORCE_LOGIN_STORAGE_KEY,
'true',
);
} else {
window.sessionStorage.removeItem(OAUTH_FORCE_LOGIN_STORAGE_KEY);
}
window.location.assign(data?.url ?? '');
}
}}
>
<Image
src="/assets/images/google.svg"
alt="Google"
width={24}
height={24}
/>
Google {t('button.sign-in')}
</Button>
);
}
return (
<Button
variant="outline"
size="medium"
disabled={!data?.url}
onClick={() => {
if (typeof window !== 'undefined') {
if (callback_url) {
window.sessionStorage.setItem(
OAUTH_CALLBACK_URL_STORAGE_KEY,
callback_url,
);
} else {
window.sessionStorage.removeItem(OAUTH_CALLBACK_URL_STORAGE_KEY);
}
if (force_login) {
window.sessionStorage.setItem(
OAUTH_FORCE_LOGIN_STORAGE_KEY,
'true',
);
} else {
window.sessionStorage.removeItem(OAUTH_FORCE_LOGIN_STORAGE_KEY);
}
window.location.assign(data?.url ?? '');
}
}}
>
{tenant?.oauthConfig?.loginButtonName ??
`OAuth 2.0 ${t('button.sign-in')}`}
</Button>
);
};
export default SignInWithOAuthButton;
@@ -0,0 +1,17 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export * from './ui';
export type { SignUpWithEmailType } from './sign-up-with-email.type';
@@ -0,0 +1,45 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { z } from 'zod';
//
export const signUpWithEmailSchema = z
.object({
email: z.email(),
emailState: z.enum(['NOT_VERIFIED', 'VERIFING', 'EXPIRED', 'VERIFIED']),
code: z.string().length(6),
password: z.string().min(8),
confirmPassword: z.string().min(8),
})
.refine((schema) => schema.password === schema.confirmPassword, {
message: 'must equal Password',
path: ['confirmPassword'],
})
.refine(({ password }) => /[A-Za-z]/.test(password), {
message: 'must contain at least one letter.',
path: ['password'],
})
.refine(
({ password }) => /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]/.test(password),
{
message: 'must contain at least one special character.',
path: ['password'],
},
)
.refine(({ password }) => !/(.)\1/.test(password), {
message: 'must not contain consecutive identical characters',
path: ['password'],
});
@@ -0,0 +1,20 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import type { z } from 'zod';
import type { signUpWithEmailSchema } from './sign-up-with-email.schema';
export type SignUpWithEmailType = z.infer<typeof signUpWithEmailSchema>;
@@ -0,0 +1,16 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
export { default as SignUpWithEmailForm } from './sign-up-with-email-form.ui';
@@ -0,0 +1,230 @@
/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { useState } from 'react';
import { useRouter } from 'next/router';
import { zodResolver } from '@hookform/resolvers/zod';
import dayjs from 'dayjs';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useInterval } from 'react-use';
import { Button } from '@ufb/react';
import { TextInput, useOAIMutation } from '@/shared';
import { signUpWithEmailSchema } from '../sign-up-with-email.schema';
import type { SignUpWithEmailType } from '../sign-up-with-email.type';
interface IProps {
onSubmit: (data: SignUpWithEmailType) => void;
loading?: boolean;
submitText: string;
initialValues?: SignUpWithEmailType | null;
skipEmailVerification?: boolean;
}
const SignUpWithEmailForm: React.FC<IProps> = (props) => {
const {
onSubmit,
loading,
submitText,
initialValues,
skipEmailVerification,
} = props;
const { t } = useTranslation();
const router = useRouter();
const {
handleSubmit,
register,
formState,
getValues,
setValue,
watch,
setError,
clearErrors,
} = useForm<SignUpWithEmailType>({
resolver: zodResolver(signUpWithEmailSchema),
defaultValues:
initialValues ?
{ ...initialValues }
: {
emailState: skipEmailVerification ? 'VERIFIED' : 'NOT_VERIFIED',
code: skipEmailVerification ? '000000' : '',
},
});
const [expiredTime, setExpiredTime] = useState<string>();
const [leftTime, setLeftTime] = useState('');
const { mutate: fetchCode, status: fetchCodeStatus } = useOAIMutation({
method: 'post',
path: '/api/admin/auth/email/code',
queryOptions: {
onSuccess(data) {
setValue('emailState', 'VERIFING');
setExpiredTime(data?.expiredAt);
clearErrors('email');
},
onError(error) {
const { message } = error;
setError('email', { message });
},
},
});
const { mutate: verifyCode, status: verifyCodeStatus } = useOAIMutation({
method: 'post',
path: '/api/admin/auth/email/code/verify',
queryOptions: {
onSuccess() {
setValue('emailState', 'VERIFIED');
clearErrors('code');
},
onError(error) {
const { message } = error;
setError('code', { message });
},
},
});
useInterval(
() => {
const seconds = dayjs(expiredTime).diff(dayjs(), 'seconds');
if (seconds < 0) {
setLeftTime(`00:00`);
setValue('emailState', 'EXPIRED');
} else {
const m = Math.floor(seconds / 60)
.toString()
.padStart(2, '0');
const s = Math.floor(seconds % 60)
.toString()
.padStart(2, '0');
setLeftTime(`${m}:${s}`);
}
},
watch('emailState') === 'VERIFING' ? 1000 : null,
);
return (
<form onSubmit={handleSubmit((data) => onSubmit(data))}>
<div className="mb-12 flex flex-col gap-4">
<div className="flex gap-2">
<TextInput
type="email"
{...register('email')}
label="Email"
placeholder={t('v2.placeholder.text')}
disabled={
!skipEmailVerification && watch('emailState') !== 'NOT_VERIFIED'
}
error={formState.errors.email?.message}
infoCaption={
!skipEmailVerification && watch('emailState') !== 'NOT_VERIFIED' ?
t('v2.text.request-email-auth')
: undefined
}
rightButton={
skipEmailVerification ? undefined : (
<Button
type="button"
onClick={() => fetchCode({ email: getValues('email') })}
loading={fetchCodeStatus === 'pending'}
disabled={watch('emailState') === 'VERIFIED'}
className="min-w-[96px]"
>
{t('v2.auth.sign-up.button.request-auth-code')}
</Button>
)
}
/>
</div>
{!skipEmailVerification && watch('emailState') !== 'NOT_VERIFIED' && (
<div className="flex gap-2">
<TextInput
type="text"
label="Authentication code"
{...register('code')}
disabled={watch('emailState') === 'VERIFIED'}
placeholder={t('v2.placeholder.text')}
error={formState.errors.code?.message}
right={
(watch('emailState') === 'VERIFING' ||
watch('emailState') === 'EXPIRED') && (
<p className="font-16-regular">{leftTime}</p>
)
}
rightButton={
<Button
loading={verifyCodeStatus === 'pending'}
disabled={
watch('emailState') === 'VERIFIED' ||
watch('code').length !== 6
}
onClick={() => {
const code = getValues('code');
if (!code) return;
verifyCode({ code, email: getValues('email') });
}}
className="min-w-[96px]"
>
{t('v2.auth.sign-up.button.verify-auth-code')}
</Button>
}
successCaption={
watch('emailState') === 'VERIFIED' ? 'Authenticated.' : (
undefined
)
}
/>
</div>
)}
<TextInput
type="password"
label="Password"
placeholder={t('v2.placeholder.text')}
error={formState.errors.password?.message}
{...register('password')}
/>
<TextInput
type="password"
label="Confirm Password"
placeholder={t('v2.placeholder.text')}
error={formState.errors.confirmPassword?.message}
{...register('confirmPassword')}
/>
</div>
<div className="flex flex-col gap-4">
<Button
size="medium"
type="submit"
loading={loading}
disabled={!formState.isDirty || watch('emailState') !== 'VERIFIED'}
>
{submitText}
</Button>
<Button size="medium" variant="outline" onClick={router.back}>
{t('button.back')}
</Button>
</div>
</form>
);
};
export default SignUpWithEmailForm;