Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moved captcha to the last signup step #4911

Merged
merged 2 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import HCaptcha from '@hcaptcha/react-hcaptcha'
import debouncePromise from 'awesome-debounce-promise'
import { FC, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Controller, useForm } from 'react-hook-form'
Expand All @@ -9,7 +8,6 @@ import { TextButton } from '@/components/_buttons/Button'
import { FormField } from '@/components/_inputs/FormField'
import { Input } from '@/components/_inputs/Input'
import { ImageCropModal, ImageCropModalImperativeHandle } from '@/components/_overlays/ImageCropModal'
import { atlasConfig } from '@/config'
import { MEMBERSHIP_NAME_PATTERN } from '@/config/regex'
import { MemberFormData } from '@/hooks/useCreateMember'
import { useUniqueMemberHandle } from '@/hooks/useUniqueMemberHandle'
Expand All @@ -28,7 +26,6 @@ export const SignUpMembershipStep: FC<SignInModalMembershipStepProps> = ({
setPrimaryButtonProps,
onSubmit,
hasNavigatedBack,
dialogContentRef,
avatar,
handle,
}) => {
Expand All @@ -53,12 +50,9 @@ export const SignUpMembershipStep: FC<SignInModalMembershipStepProps> = ({
shallow
)
const handleInputRef = useRef<HTMLInputElement | null>(null)
const captchaInputRef = useRef<HTMLDivElement | null>(null)
const avatarDialogRef = useRef<ImageCropModalImperativeHandle>(null)

const [isHandleValidating, setIsHandleValidating] = useState(false)
// used to scroll the form to the bottom upon first handle field focus - this is done to let the user see Captcha form field
const hasDoneInitialScroll = useRef(false)

const { checkIfMemberIsAvailable } = useUniqueMemberHandle()

Expand Down Expand Up @@ -109,11 +103,7 @@ export const SignUpMembershipStep: FC<SignInModalMembershipStepProps> = ({
if (errors.handle) {
handleInputRef.current?.scrollIntoView({ behavior: 'smooth' })
}

if (errors.captchaToken) {
captchaInputRef.current?.scrollIntoView({ behavior: 'smooth' })
}
}, [errors.captchaToken, errors.handle])
}, [errors.handle])

return (
<AuthenticationModalStepTemplate
Expand Down Expand Up @@ -179,39 +169,8 @@ export const SignUpMembershipStep: FC<SignInModalMembershipStepProps> = ({
error={!!errors.handle}
processing={isHandleValidating || isSubmitting}
autoComplete="off"
onClick={() => {
if (hasDoneInitialScroll.current || !dialogContentRef?.current) return
hasDoneInitialScroll.current = true
dialogContentRef.current.scrollTo({ top: dialogContentRef.current.scrollHeight, behavior: 'smooth' })
}}
/>
</FormField>
{atlasConfig.features.members.hcaptchaSiteKey && (
<Controller
control={control}
name="captchaToken"
render={({ field: { onChange }, fieldState: { error } }) => (
<FormField error={error?.message} ref={captchaInputRef}>
<HCaptcha
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sitekey={atlasConfig.features.members.hcaptchaSiteKey!}
theme="dark"
languageOverride="en"
onVerify={(token) => {
onChange(token)
trigger('captchaToken')
}}
/>
</FormField>
)}
rules={{
required: {
value: !!atlasConfig.features.members.hcaptchaSiteKey,
message: "Verify that you're not a robot.",
},
}}
/>
)}
</StyledForm>
}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import HCaptcha from '@hcaptcha/react-hcaptcha'
import { zodResolver } from '@hookform/resolvers/zod'
import { FC, RefObject, useCallback, useEffect, useRef } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { Controller, FormProvider, useForm } from 'react-hook-form'

import { AuthenticationModalStepTemplate } from '@/components/_auth/AuthenticationModalStepTemplate'
import { PasswordCriterias } from '@/components/_auth/PasswordCriterias'
import { FormField } from '@/components/_inputs/FormField'
import { Input } from '@/components/_inputs/Input'
import { atlasConfig } from '@/config'
import { AccountFormData } from '@/hooks/useCreateMember'
import { useHidePasswordInInput } from '@/hooks/useHidePasswordInInput'
import { passwordAndRepeatPasswordSchema } from '@/utils/formValidationOptions'
Expand All @@ -16,6 +18,7 @@ import { SignUpStepsCommonProps } from '../SignUpSteps.types'
type PasswordStepForm = {
password: string
confirmPassword: string
captchaToken?: string
}

type SignUpPasswordStepProps = {
Expand All @@ -34,6 +37,7 @@ export const SignUpPasswordStep: FC<SignUpPasswordStepProps> = ({
}) => {
const form = useForm<PasswordStepForm>({
shouldFocusError: true,
reValidateMode: 'onSubmit',
defaultValues: {
password,
confirmPassword: password,
Expand All @@ -44,11 +48,17 @@ export const SignUpPasswordStep: FC<SignUpPasswordStepProps> = ({
handleSubmit,
register,
formState: { errors },
control,
trigger,
} = form
const [hidePasswordProps] = useHidePasswordInInput()
const [hideConfirmPasswordProps] = useHidePasswordInInput()

const captchaRef = useRef<HCaptcha | null>(null)
const captchaInputRef = useRef<HTMLDivElement | null>(null)

const handleGoToNextStep = useCallback(() => {
captchaRef.current?.resetCaptcha()
handleSubmit((data) => {
onPasswordSubmit(data.password)
})()
Expand All @@ -61,7 +71,13 @@ export const SignUpPasswordStep: FC<SignUpPasswordStepProps> = ({
})
}, [handleGoToNextStep, setPrimaryButtonProps])

// used to scroll the form to the bottom upon first handle field focus - this is done to let the user see password requirements
useEffect(() => {
if (errors.captchaToken) {
captchaInputRef.current?.scrollIntoView({ behavior: 'smooth' })
}
}, [errors.captchaToken])

// used to scroll the form to the bottom upon first handle field focus - this is done to let the user see password requirements & captcha
const hasDoneInitialScroll = useRef(false)

return (
Expand Down Expand Up @@ -96,6 +112,27 @@ export const SignUpPasswordStep: FC<SignUpPasswordStepProps> = ({
/>
</FormField>
<PasswordCriterias />
{atlasConfig.features.members.hcaptchaSiteKey && (
<Controller
control={control}
name="captchaToken"
render={({ field: { onChange }, fieldState: { error } }) => (
<FormField error={error?.message} ref={captchaInputRef}>
<HCaptcha
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sitekey={atlasConfig.features.members.hcaptchaSiteKey!}
theme="dark"
languageOverride="en"
ref={captchaRef}
onVerify={(token) => {
onChange(token)
trigger('captchaToken')
}}
/>
</FormField>
)}
/>
)}
</StyledSignUpForm>
</AuthenticationModalStepTemplate>
</FormProvider>
Expand Down
7 changes: 7 additions & 0 deletions packages/atlas/src/utils/formValidationOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { isValid } from 'date-fns'
import { RegisterOptions, Validate } from 'react-hook-form'
import { z } from 'zod'

import { atlasConfig } from '@/config'

type TextValidationArgs = {
name: string
maxLength: number
Expand Down Expand Up @@ -67,6 +69,7 @@ export const passwordAndRepeatPasswordSchema = z
.min(9, { message: 'Password has to meet requirements.' })
.max(64, { message: 'Password has to meet requirements.' }),
confirmPassword: z.string(),
captchaToken: z.string().optional(),
})
.refine(
(data) => {
Expand All @@ -85,3 +88,7 @@ export const passwordAndRepeatPasswordSchema = z
message: 'Password has to meet requirements.',
}
)
.refine((data) => (atlasConfig.features.members.hcaptchaSiteKey ? !!data.captchaToken : true), {
path: ['captchaToken'],
message: "Verify that you're not a robot.",
})