Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion apps/frontend/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
--color-primary-content: #fff;
--color-secondary: #f4f5f6;
--color-secondary-content: #58667e;
--color-accent: #1de7df;
--color-accent: #db2777;
--color-accent-content: #000;
--color-neutral: #eff2f5;
--color-neutral-content: #58667e;
Expand Down
Binary file added apps/frontend/android-chrome-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/frontend/android-chrome-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/frontend/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/frontend/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/frontend/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/frontend/favicon.ico
Binary file not shown.
Binary file removed apps/frontend/favicon.png
Binary file not shown.
6 changes: 5 additions & 1 deletion apps/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/frontend/favicon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/frontend/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/frontend/favicon-16x16.png" />
<link rel="icon" type="image/x-icon" href="/frontend/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/frontend/apple-touch-icon.png" />
<link rel="manifest" href="/frontend/site.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title translate="no">Vortex</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
Expand Down
19 changes: 19 additions & 0 deletions apps/frontend/site.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"background_color": "#f5f9fa",
"display": "standalone",
"icons": [
{
"sizes": "192x192",
"src": "/frontend/android-chrome-192x192.png",
"type": "image/png"
},
{
"sizes": "512x512",
"src": "/frontend/android-chrome-512x512.png",
"type": "image/png"
}
],
"name": "Vortex",
"short_name": "Vortex",
"theme_color": "#0f4dc0"
}
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
14 changes: 7 additions & 7 deletions apps/frontend/src/hooks/ramp/useRampNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ export const useRampNavigation = (
const shouldSkipQuoteForm = useMemo(() => searchParams.quoteId || hasAllQuoteRefreshParams(searchParams), [searchParams]);

const getCurrentComponent = useCallback(() => {
if (shouldSkipQuoteForm) {
return formComponent;
}

if (rampState?.ramp?.currentPhase === "complete") {
return successComponent;
}
Expand All @@ -41,19 +37,23 @@ export const useRampNavigation = (
return progressComponent;
}

if (shouldSkipQuoteForm) {
return formComponent;
}

if (rampMachineState.value === "Idle") {
return quoteComponent;
}

return formComponent;
}, [
shouldSkipQuoteForm,
rampState,
formComponent,
rampMachineState.value,
shouldSkipQuoteForm,
successComponent,
failureComponent,
progressComponent,
rampMachineState.value,
formComponent,
quoteComponent
]);

Expand Down
Loading