Skip to content
Open
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,65 +1,16 @@
import { useCallback, useMemo } from 'react';
import { View } from 'react-native';
import { Image } from 'expo-image';

import DepositOption from '@/components/DepositOption/DepositOption';
import { DEPOSIT_MODAL } from '@/constants/modals';
import { getAsset } from '@/lib/assets';
import { DepositMethod } from '@/lib/types';
import useDepositBuyCryptoOptions from '@/hooks/useDepositBuyCryptoOptions';
import { getVaultDepositConfig } from '@/lib/vaults';
import { useDepositStore } from '@/store/useDepositStore';

const DepositBuyCryptoOptions = () => {
const setModal = useDepositStore(state => state.setModal);
const { buyCryptoOptions } = useDepositBuyCryptoOptions();
const depositConfig = getVaultDepositConfig();

// const handleBankDepositPress = useCallback(() => {
// setModal(DEPOSIT_MODAL.OPEN_BANK_TRANSFER_AMOUNT);
// }, [setModal]);

// const handleCreditCardPress = useCallback(() => {
// setModal(DEPOSIT_MODAL.OPEN_BUY_CRYPTO);
// }, [setModal]);

const buyCryptoOptions = useMemo(
() => [
// {
// text: 'Debit/Credit Card',
// subtitle: 'Google Pay, card or bank account',
// icon: (
// <Image
// source={getAsset('images/buy_crypto.png')}
// style={{ width: 26, height: 22 }}
// contentFit="contain"
// />
// ),
// onPress: handleCreditCardPress,
// method: 'credit_card' as DepositMethod,
// },
// {
// text: 'Bank Deposit',
// subtitle: 'Make a transfer from your bank.',
// icon: (
// <Image
// source={getAsset('images/bank_deposit.png')}
// style={{ width: 26, height: 22 }}
// contentFit="contain"
// />
// ),
// onPress: handleBankDepositPress,
// isComingSoon: false,
// method: 'bank_transfer' as DepositMethod,
// },
],
[
// handleCreditCardPress,
// handleBankDepositPress
],
);

return (
<View className="gap-y-2.5">
{/* {buyCryptoOptions
{buyCryptoOptions
.filter(option => !option.method || depositConfig.methods.includes(option.method))
.map(option => (
<DepositOption
Expand All @@ -68,9 +19,9 @@ const DepositBuyCryptoOptions = () => {
subtitle={option.subtitle}
icon={option.icon}
onPress={option.onPress}
// isComingSoon={option.isComingSoon}
chipText={option.chipText}
/>
))} */}
))}
</View>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { useState } from 'react';
import { ActivityIndicator, View } from 'react-native';
import { Info } from 'lucide-react-native';

import CopyToClipboard from '@/components/CopyToClipboard';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { DEPOSIT_MODAL } from '@/constants/modals';
import { useOnrampAutomation } from '@/hooks/useOnrampAutomation';
import type { OnrampAutomationRail } from '@/lib/types';
import { useDepositStore } from '@/store/useDepositStore';

const TAB_CONFIG: { key: OnrampAutomationRail; label: string }[] = [
{ key: 'ach', label: 'ACH' },
{ key: 'wire', label: 'Wire' },
];

const RAIL_FOOTER: Record<OnrampAutomationRail, string> = {
ach: 'ACH cutoff is 4:00 PM ET. Funds typically settle in 1–3 business days.',
wire: 'Wire cutoff is 5:45 PM ET. Funds typically settle the same business day.',
};

const Row = ({
label,
value,
withDivider = false,
}: {
label: string;
value: string;
withDivider?: boolean;
}) => (
<View>
<View className="flex-row items-center justify-between gap-4 px-4 py-4">
<Text className="text-base text-gray-400" numberOfLines={1}>
{label}
</Text>
<View className="flex-1 flex-row items-center justify-end gap-2 overflow-hidden">
<Text
className="flex-1 text-right text-base font-medium text-white"
numberOfLines={2}
ellipsizeMode="tail"
>
{value}
</Text>
{value ? <CopyToClipboard text={value} /> : null}
</View>
</View>
{withDivider && <View className="mx-4 h-[1px] bg-[#2C2C2C]" />}
</View>
);

export const VirtualAccountDetailsModal = () => {
const setModal = useDepositStore(state => state.setModal);
const { data: automation, isLoading } = useOnrampAutomation();
const [rail, setRail] = useState<OnrampAutomationRail>('ach');

if (isLoading) {
return (
<View className="flex-1 items-center justify-center py-16">
<ActivityIndicator />
</View>
);
}

if (!automation) {
return (
<View className="flex-1 items-center justify-center gap-4 py-12">
<Text className="text-base text-white">Could not load your bank details.</Text>
<Button
className="h-12 rounded-2xl px-6"
onPress={() => setModal(DEPOSIT_MODAL.OPEN_VIRTUAL_ACCOUNT_TOS)}
>
<Text className="text-base font-bold text-black">Try again</Text>
</Button>
</View>
);
}

const { depositAddress } = automation;

return (
<View className="flex-1 gap-4">
<View className="items-center gap-2">
<Text className="text-2xl font-bold text-white">Deposit USD</Text>
<Text className="text-center text-base text-gray-400">
Send a transfer from your bank — funds arrive as soUSD in your Solid balance.
</Text>
</View>

<View className="mt-2 flex-row gap-2 rounded-2xl bg-[#1C1C1C] p-1">
{TAB_CONFIG.map(tab => {
const isActive = rail === tab.key;
return (
<Button
key={tab.key}
className={`h-10 flex-1 rounded-xl ${isActive ? 'bg-[#2C2C2C]' : 'bg-transparent'}`}
onPress={() => setRail(tab.key)}
>
<Text
className={`text-base font-medium ${isActive ? 'text-white' : 'text-gray-400'}`}
>
{tab.label}
</Text>
</Button>
);
})}
</View>

<View className="overflow-hidden rounded-2xl bg-[#1C1C1C]">
<Row label="Beneficiary name" value={depositAddress.beneficiaryName} withDivider />
<Row label="Beneficiary address" value={depositAddress.beneficiaryAddress} withDivider />
<Row label="Bank name" value={depositAddress.beneficiaryBankName} withDivider />
<Row label="Bank address" value={depositAddress.beneficiaryBankAddress} withDivider />
<Row label="Account number" value={depositAddress.accountNumber} withDivider />
<Row label="Routing number" value={depositAddress.routingNumber} />
</View>

<View className="flex-row items-start gap-3 rounded-2xl bg-[#1C1C1C] px-4 py-4">
<Info size={20} color="#94F27F" />
<Text className="flex-1 text-sm leading-5 text-gray-400">{RAIL_FOOTER[rail]}</Text>
</View>

<Button
className="mt-auto h-14 rounded-2xl sm:mt-8"
style={{ backgroundColor: '#94F27F' }}
onPress={() => setModal(DEPOSIT_MODAL.CLOSE)}
>
<Text className="text-base font-bold text-black">Done</Text>
</Button>
</View>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useCallback, useEffect, useState } from 'react';
import { ActivityIndicator, Pressable, View } from 'react-native';
import { Check } from 'lucide-react-native';

import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { DEPOSIT_MODAL } from '@/constants/modals';
import {
useCreateOnrampAutomation,
useOnrampAutomation,
} from '@/hooks/useOnrampAutomation';
import { useDepositStore } from '@/store/useDepositStore';

const TOS_POINTS = [
'A persistent virtual bank account will be issued in your name for ACH and Wire deposits.',
'Incoming USD is converted to USDC by Rain and automatically deposited into the soUSD vault on your behalf.',
'You agree to Rain Payments’ Terms of Service and Privacy Policy and confirm you are the account holder.',
'Deposits are subject to ACH (cutoff 4:00 PM ET) and Wire (cutoff 5:45 PM ET) banking hours. Settlement may take 1–3 business days.',
];

export const VirtualAccountTosModal = () => {
const setModal = useDepositStore(state => state.setModal);
const { data: existingAutomation } = useOnrampAutomation();
const createMutation = useCreateOnrampAutomation();
const [agreed, setAgreed] = useState(false);

// Defensive: if an automation already exists, skip ToS straight to details.
useEffect(() => {
if (existingAutomation) {
setModal(DEPOSIT_MODAL.OPEN_VIRTUAL_ACCOUNT_DETAILS);
}
}, [existingAutomation, setModal]);

const handleAccept = useCallback(() => {
createMutation.mutate('ach', {
onSuccess: () => {
setModal(DEPOSIT_MODAL.OPEN_VIRTUAL_ACCOUNT_DETAILS);
},
});
}, [createMutation, setModal]);

return (
<View className="flex-1 gap-4">
<View className="items-center gap-2 px-2">
<Text className="text-2xl font-bold text-white">Before you continue</Text>
<Text className="text-center text-base text-gray-400">
Review the terms of the Rain virtual bank account.
</Text>
</View>

<View className="gap-3 rounded-2xl bg-[#1C1C1C] p-4">
{TOS_POINTS.map(point => (
<View key={point} className="flex-row items-start gap-3">
<View className="mt-1 h-1.5 w-1.5 rounded-full bg-[#94F27F]" />
<Text className="flex-1 text-sm leading-5 text-white">{point}</Text>
</View>
))}
</View>

<Pressable
className="flex-row items-start gap-3 rounded-2xl bg-[#1C1C1C] px-4 py-4"
onPress={() => setAgreed(prev => !prev)}
accessibilityRole="checkbox"
accessibilityState={{ checked: agreed }}
>
<View
className={`mt-0.5 h-5 w-5 items-center justify-center rounded border ${
agreed ? 'border-[#94F27F] bg-[#94F27F]' : 'border-gray-500 bg-transparent'
}`}
>
{agreed && <Check size={14} color="#000" />}
</View>
<Text className="flex-1 text-sm leading-5 text-white">
I agree to the terms above and authorize Rain to issue a virtual bank account on my behalf.
</Text>
</Pressable>

{createMutation.isError && (
<Text className="text-center text-sm text-red-400">
Something went wrong creating your bank account. Please try again.
</Text>
)}

<Button
className="mt-auto h-14 rounded-2xl sm:mt-8"
style={{
backgroundColor: agreed && !createMutation.isPending ? '#94F27F' : '#3A3A3A',
}}
disabled={!agreed || createMutation.isPending}
onPress={handleAccept}
>
{createMutation.isPending ? (
<ActivityIndicator color="#000" />
) : (
<Text className="text-base font-bold text-black">Accept and continue</Text>
)}
</Button>
</View>
);
};
8 changes: 8 additions & 0 deletions constants/modals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ export const DEPOSIT_MODAL = {
name: 'open_token_selector',
number: 17,
},
OPEN_VIRTUAL_ACCOUNT_DETAILS: {
name: 'open_virtual_account_details',
number: 18,
},
OPEN_VIRTUAL_ACCOUNT_TOS: {
name: 'open_virtual_account_tos',
number: 19,
},
};

export const SEND_MODAL = {
Expand Down
Loading
Loading