Skip to content
22 changes: 7 additions & 15 deletions apps/frontend/src/components/Avenia/AveniaField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CalendarDaysIcon } from "@heroicons/react/24/outline";
import { motion } from "motion/react";
import { FC } from "react";
import { useFormContext, useFormState } from "react-hook-form";
Expand Down Expand Up @@ -69,21 +70,12 @@ export const AveniaField: FC<AveniaFieldProps> = ({ id, label, index, validation
<label className="mb-1 block" htmlFor={id}>
{label}
</label>
<Field
className={cn("w-full p-2", errors[id] && "border border-red-800")}
id={id}
register={register(id, {
pattern: validationPattern
? {
message: validationPattern.message,
value: validationPattern.value
}
: undefined,
required: true,
validate: validationPattern?.validate
})}
{...rest}
/>
<div className="relative">
<Field className={cn("w-full p-2", errors[id] && "border border-red-800")} id={id} register={register(id)} {...rest} />
{id === ExtendedAveniaFieldOptions.BIRTHDATE && (
<CalendarDaysIcon className="absolute right-3 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-600 pointer-events-none" />
)}
</div>
{errorMessage && <span className="mt-1 text-red-800 text-sm">{errorMessage}</span>}
</motion.div>
);
Expand Down
13 changes: 9 additions & 4 deletions apps/frontend/src/components/Avenia/DocumentUpload/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,13 @@ export const DocumentUpload: React.FC<DocumentUploadProps> = ({ aveniaKycActor,
label: string,
onChange: React.ChangeEventHandler<HTMLInputElement> | undefined,
valid: boolean,
Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>,
fileName?: string
) => (
<label className="relative flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed p-6 hover:border-blue-500">
<Icon className="mb-2 h-12 w-12 text-gray-400" />
<span className="mb-1 text-gray-600">{label}</span>
<span className="text-gray-400 text-sm">{fileName || t("components.documentUpload.helperText")}</span>
<input accept=".png,.jpeg,.jpg,.pdf" className="hidden" onChange={onChange} type="file" />
{valid && <CheckCircleIcon className="absolute top-2 right-2 h-6 w-6 text-green-500" />}
</label>
Expand All @@ -190,13 +192,15 @@ export const DocumentUpload: React.FC<DocumentUploadProps> = ({ aveniaKycActor,
t("components.documentUpload.fields.rgFront"),
e => handleFileChange(e, setFront, setFrontValid),
frontValid,
DocumentTextIcon
DocumentTextIcon,
front?.name
)}
{renderField(
t("components.documentUpload.fields.rgBack"),
e => handleFileChange(e, setBack, setBackValid),
backValid,
DocumentTextIcon
DocumentTextIcon,
back?.name
)}
</>
)}
Expand All @@ -205,7 +209,8 @@ export const DocumentUpload: React.FC<DocumentUploadProps> = ({ aveniaKycActor,
t("components.documentUpload.fields.cnhDocument"),
e => handleFileChange(e, setFront, setFrontValid),
frontValid,
DocumentTextIcon
DocumentTextIcon,
front?.name
)}
</div>

Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/components/QuoteSubmitButtons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const QuoteSubmitButton: FC<QuoteSubmitButtonProps> = ({ className, disab
return (
<div className={className}>
<button className="btn-vortex-primary btn w-full" disabled={isSubmitButtonDisabled} onClick={onClick}>
{(isQuoteOutdated || pending) && <Spinner />}
{(isQuoteOutdated || pending) && !currentErrorMessage && <Spinner />}
{isMaintenanceDisabled ? buttonProps.title : buttonText}
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ const useButtonContent = ({ toToken, submitButtonDisabled }: UseButtonContentPro
]);
};

export const RampSubmitButton = ({ className }: { className?: string }) => {
export const RampSubmitButton = ({ className, hasValidationErrors }: { className?: string; hasValidationErrors?: boolean }) => {
const rampActor = useRampActor();
const { onRampConfirm } = useRampSubmission();
const stellarData = useStellarKycSelector();
Expand Down Expand Up @@ -208,6 +208,10 @@ export const RampSubmitButton = ({ className }: { className?: string }) => {
const toToken = isOnramp ? getOnChainTokenDetailsOrDefault(selectedNetwork, onChainToken) : getAnyFiatTokenDetails(fiatToken);

const submitButtonDisabled = useMemo(() => {
if (hasValidationErrors) {
return true;
}

if (
walletLocked &&
(isOfframp || quote?.from === "sepa") &&
Expand Down Expand Up @@ -242,6 +246,7 @@ export const RampSubmitButton = ({ className }: { className?: string }) => {

return false;
}, [
hasValidationErrors,
executionInput,
isQuoteExpired,
isOfframp,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Networks } from "@vortexfi/shared";
import { useFormContext } from "react-hook-form";
import { RampFormValues } from "../../../hooks/ramp/schema";
import { useVortexAccount } from "../../../hooks/useVortexAccount";
import { ConnectWalletSection } from "../../ConnectWalletSection";
import { RampSubmitButton } from "../../RampSubmitButton/RampSubmitButton";
Expand All @@ -10,12 +12,38 @@ export interface DetailsStepActionsProps {
requiresConnection: boolean;
className?: string;
forceNetwork?: Networks;
isBrazilLanding: boolean;
}

export const DetailsStepActions = ({ signingState, className, requiresConnection, forceNetwork }: DetailsStepActionsProps) => {
export const DetailsStepActions = ({
signingState,
className,
requiresConnection,
forceNetwork,
isBrazilLanding
}: DetailsStepActionsProps) => {
const { shouldDisplay: signingBoxVisible, signatureState, confirmations } = signingState;
const { isConnected } = useVortexAccount(forceNetwork);

const {
formState: { errors },
watch
} = useFormContext<RampFormValues>();
const formValues = watch();

const hasFormErrors = Object.keys(errors).length > 0;

let hasEmptyForm = false;

if (isBrazilLanding) {
const allRelevantFieldsEmpty = !formValues.taxId || !formValues.walletAddress;
hasEmptyForm = allRelevantFieldsEmpty;
} else {
hasEmptyForm = !formValues.walletAddress;
}

const hasValidationErrors = hasFormErrors || hasEmptyForm;

if (signingBoxVisible) {
return (
<div className={`flex grow text-center ${className || ""}`}>
Expand All @@ -28,7 +56,7 @@ export const DetailsStepActions = ({ signingState, className, requiresConnection
return (
<div className={className}>
{requiresConnection && <ConnectWalletSection forceNetwork={forceNetwork} />}
{displayRampSubmitButton && <RampSubmitButton className="mb-4" />}
{displayRampSubmitButton && <RampSubmitButton className="mb-4" hasValidationErrors={hasValidationErrors} />}
</div>
);
};
24 changes: 19 additions & 5 deletions apps/frontend/src/components/widget-steps/DetailsStep/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { InformationCircleIcon } from "@heroicons/react/24/outline";
import { FiatToken, Networks } from "@vortexfi/shared";
import { useSelector } from "@xstate/react";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { FormProvider } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useRampActor } from "../../../contexts/rampState";
import { RampFormValues } from "../../../hooks/ramp/schema";
import { useRampForm } from "../../../hooks/ramp/useRampForm";
import { useRampSubmission } from "../../../hooks/ramp/useRampSubmission";
import { useSigningBoxState } from "../../../hooks/useSigningBoxState";
Expand Down Expand Up @@ -32,6 +33,7 @@ export interface FormData {
taxId?: string;
moneriumWalletAddress?: string;
walletAddress?: string;
fiatToken?: FiatToken;
}

export const DetailsStep = ({ className }: DetailsStepProps) => {
Expand All @@ -58,6 +60,7 @@ export const DetailsStep = ({ className }: DetailsStepProps) => {
const walletForm = walletLockedFromState || address || undefined;

const { form } = useRampForm({
fiatToken: quote?.rampType === "BUY" ? (quote.inputCurrency as FiatToken) : (quote?.outputCurrency as FiatToken),
moneriumWalletAddress: evmAddress,
pixId,
taxId,
Expand All @@ -72,12 +75,18 @@ export const DetailsStep = ({ className }: DetailsStepProps) => {

if (isMoneriumToAssethubRamp && substrateAddress) {
form.setValue("walletAddress", substrateAddress);
} else if (walletLockedFromState) {
form.setValue("walletAddress", walletLockedFromState);
} else if (!isMoneriumToAssethubRamp && address) {
form.setValue("walletAddress", address);
} else if (walletLockedFromState) {
form.setValue("walletAddress", walletLockedFromState);
}
}, [form, evmAddress, isMoneriumRamp, address, walletLockedFromState, isMoneriumToAssethubRamp, substrateAddress]);

const fiatToken = quote?.rampType === "BUY" ? (quote.inputCurrency as FiatToken) : (quote?.outputCurrency as FiatToken);
form.setValue("fiatToken", fiatToken);
}, [form, evmAddress, isMoneriumRamp, address, walletLockedFromState, isMoneriumToAssethubRamp, substrateAddress, quote]);

const previousValues = useRef<RampFormValues>({});
const currentValues = form.watch();

const { onRampConfirm } = useRampSubmission();

Expand Down Expand Up @@ -118,7 +127,12 @@ export const DetailsStep = ({ className }: DetailsStepProps) => {
</div>
</div>
)}
<DetailsStepActions forceNetwork={forceNetwork} requiresConnection={!canSkipConnection} signingState={signingState} />
<DetailsStepActions
forceNetwork={forceNetwork}
isBrazilLanding={isBrazilLanding}
requiresConnection={!canSkipConnection}
signingState={signingState}
/>
</form>
<DetailsStepQuoteSummary quote={quote} />
</FormProvider>
Expand Down
13 changes: 10 additions & 3 deletions apps/frontend/src/hooks/brla/useKYCForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,27 @@ const createKycFormSchema = (t: (key: string) => string) =>

[ExtendedAveniaFieldOptions.BIRTHDATE]: yup
.date()
.transform((value, originalValue) => {
.transform((value: Date | undefined, originalValue: any) => {
return originalValue === "" ? undefined : value;
})
.required(t("components.brlaExtendedForm.validation.birthdate.required"))
.max(new Date(), t("components.brlaExtendedForm.validation.birthdate.future"))
.min(new Date(1900, 0, 1), t("components.brlaExtendedForm.validation.birthdate.tooOld")),
.min(new Date(1900, 0, 1), t("components.brlaExtendedForm.validation.birthdate.tooOld"))
.test("is-18-or-older", t("components.brlaExtendedForm.validation.birthdate.tooYoung"), value => {
if (!value) return true;
const birthDate = new Date(value);
const ageDate = new Date(birthDate);
ageDate.setFullYear(ageDate.getFullYear() + 18);
return ageDate <= new Date();
}),

[ExtendedAveniaFieldOptions.COMPANY_NAME]: yup
.string()
.min(3, t("components.brlaExtendedForm.validation.companyName.minLength")),

[ExtendedAveniaFieldOptions.START_DATE]: yup
.date()
.transform((value, originalValue) => {
.transform((value: Date | undefined, originalValue: any) => {
return originalValue === "" ? undefined : value;
})
.max(new Date(), t("components.brlaExtendedForm.validation.startDate.future"))
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/hooks/ramp/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type RampFormValues = {
pixId?: string;
walletAddress?: string;
moneriumWalletAddress?: string;
fiatToken?: FiatToken;
};

export const PHONE_REGEX = /^\+[1-9][0-9]\d{1,14}$/;
Expand Down
Loading