Skip to content

Commit

Permalink
Compliance mock. Form errors design. Send dialog design (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
avkos authored Jan 13, 2025
1 parent 1ce115b commit 0f4e226
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';

import { ComplianceEngineText } from './ComplianceEngineText';

const meta = {
title: 'FormErrorText',
component: ComplianceEngineText,
tags: ['autodocs'],
} satisfies Meta<typeof ComplianceEngineText>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
result: true,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function ComplianceEngineText({ result }: { result: boolean }) {
return (
<div className="flex">
{result ? (
<p className="bg-teal-600/10 rounded-lg py-1 px-3 my-2 text-xs text-success">
Approved by Circle Compliance Engine ✓
</p>
) : (
<p className="bg-teal-600/10 rounded-lg py-1 px-3 my-2 text-xs text-error denied">
Denied by Circle Compliance Engine ✘
</p>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ComplianceEngineText';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export function FormErrorText({ value }: { value?: unknown }) {
return (
<p className="text-sm text-destructive" style={{ minHeight: 20, paddingLeft: 13 }}>
<p className="text-sm min-h-6 mt-1 mb-2 text-error">
{typeof value === 'object'
? JSON.stringify(value)
: typeof value === 'string'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import { WalletTokenBalance } from '~/lib/types';
export type TokenSelectProps = Omit<SelectProps, 'children'> & {
placeholder?: string;
balances: WalletTokenBalance[];
className?: string;
};

/** A dropdown select menu to choose a token */
export function TokenSelect({ ...props }: TokenSelectProps) {
export function TokenSelect({ className, ...props }: TokenSelectProps) {
const { placeholder = 'Select Token', balances = [], ...other } = props;
return (
<Select {...other}>
<SelectTrigger className="w-full">
<SelectTrigger className={`w-full ${className}`}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,29 @@ import { useState } from 'react';
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import z from 'zod';

import { ComplianceEngineText } from '~/components/ComplianceEngineText';
import { FormErrorText } from '~/components/FormErrorText';
import { TokenSelect } from '~/components/TokenSelect';
import { Button } from '~/components/ui/button';
import { Input } from '~/components/ui/input';
import { Textarea } from '~/components/ui/textarea';
import { WalletDetails } from '~/components/WalletDetails';
import { FeeLevel } from '~/lib/constants';
import { CircleError } from '~/lib/responses';
import { Transaction, Wallet, WalletTokenBalance } from '~/lib/types';
import { isAddress, isNumber } from '~/lib/utils';

export interface ScreenAddressResult {
result?: boolean;
}

export interface WalletSendProps {
/** The wallet */
wallet: Wallet;
balances: WalletTokenBalance[];
onSendTransaction: (data: CreateTransactionInput) => Promise<Transaction | CircleError>;
onGetTransaction: (data: GetTransactionInput) => Promise<{ transaction: Transaction }>;
onScreenAddress?: (address: string) => Promise<ScreenAddressResult>;
onConfirmed?: (data: Transaction) => Promise<void>;
}

Expand Down Expand Up @@ -55,7 +62,10 @@ export function WalletSend({
onSendTransaction,
onGetTransaction,
onConfirmed,
onScreenAddress,
}: WalletSendProps) {
const [screeningAddressResult, setScreeningAddressResult] =
useState<ScreenAddressResult>({});
const [requestError, setRequestError] = useState<string>('');
const [transactionData, setTransactionData] = useState({} as Transaction);
const {
Expand Down Expand Up @@ -104,34 +114,66 @@ export function WalletSend({
}, 1000);
}
};
const onChangeAddress = (e: React.ChangeEvent<HTMLInputElement>) => {
const address = e.target.value;
if (typeof onScreenAddress === 'function') {
if (isAddress(address)) {
onScreenAddress(address)
.then((res: ScreenAddressResult) => {
setScreeningAddressResult(res);
})
.catch(console.error);
} else {
setScreeningAddressResult({});
}
}
};

return (
<div className="items-center w-full">
<WalletDetails wallet={wallet} />
<h1 className="text-xl text-black mt-8">Send Transaction</h1>
<p className="text-base text-gray-600">
Send transaction to any blockchain address
</p>
{/* eslint-disable-next-line @typescript-eslint/no-misused-promises */}
<form className="w-full" onSubmit={handleSubmit(onSubmit)}>
<div className="w-ful mt-6">
<form className="w-full mt-6" onSubmit={handleSubmit(onSubmit)}>
<div className="w-ful">
<Input
placeholder="Recipient Address"
className="col-span-3"
className={`col-span-3 ${screeningAddressResult.result === undefined && errors.destinationAddress?.message ? 'border border-error' : ''}`}
{...register('destinationAddress')}
onChange={onChangeAddress}
/>
<FormErrorText value={errors.destinationAddress?.message} />
{screeningAddressResult.result !== undefined ? (
<ComplianceEngineText result={screeningAddressResult.result} />
) : (
<FormErrorText value={errors.destinationAddress?.message} />
)}
</div>
<div className="mt-6">
<div className="w-full">
<Controller
name="tokenId"
control={control}
render={({ field }) => (
<TokenSelect balances={balances} onValueChange={field.onChange} />
<TokenSelect
balances={balances}
onValueChange={field.onChange}
className={`${errors.tokenId?.message ? 'border border-error' : ''}`}
/>
)}
/>
<FormErrorText value={errors.tokenId?.message} />
</div>
<div className="mt-6">
<Input placeholder="Amount" className="col-span-3" {...register('amount')} />
<div className="w-full">
<Input
placeholder="Amount"
className={`col-span-3 ${errors.amount?.message ? 'border border-error' : ''}`}
{...register('amount')}
/>
<FormErrorText value={errors.amount?.message} />
</div>
<div className="mt-6">
<div className="w-full">
<Textarea
placeholder="Note(optional)"
className="col-span-3 min-h-[100px]"
Expand All @@ -140,15 +182,15 @@ export function WalletSend({
</div>
<Button
type="submit"
className="mt-6 w-full"
className="w-full mt-6"
disabled={isTransactionPending(transactionData)}
>
{isTransactionPending(transactionData) && (
<LoaderCircle className="animate-spin" />
)}
Send
</Button>
<FormErrorText value={requestError} />
{requestError && <FormErrorText value={requestError} />}
</form>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/circle-demo-webapp/app/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
'min-w-[428px] fixed left-[50%] top-[50%] z-50 grid translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className,
)}
{...props}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ActionFunction } from '@remix-run/node';

import { assertCircleErrorResponse, errorResponse } from '~/lib/server.responses';

export const action: ActionFunction = () => {
try {
return Response.json({
result: 'APPROVED',
id: {},
address: '0x1bf9ad0cc2ad298c69a2995aa806ee832788218c',
chain: 'MATIC-AMOY',
details: [
{
id: 'c4d1da72-111e-4d52-bdbf-2e74a2d803d5',
vendor: 'VENDOR',
response: {},
createDate: '2023-01-01T12:04:05Z',
},
],
alertId: {},
});
} catch (e: unknown) {
assertCircleErrorResponse(e);

return errorResponse(e.response.data.error.message);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface WalletSendDialogProps {
onSendTransaction: WalletSendProps['onSendTransaction'];
onGetTransaction: WalletSendProps['onGetTransaction'];
onConfirmed?: WalletSendProps['onConfirmed'];
onScreenAddress?: WalletSendProps['onScreenAddress'];
}

export function WalletSendDialog(props: WalletSendDialogProps) {
Expand Down
6 changes: 6 additions & 0 deletions packages/circle-demo-webapp/app/routes/wallet.$id/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TransactionTableRow } from '~/components/TransactionTableRow';
import { Card } from '~/components/ui/card';
import { WalletBalance } from '~/components/WalletBalance';
import { WalletDetails } from '~/components/WalletDetails';
import { ScreenAddressResult } from '~/components/WalletSend';
import { useTransactions } from '~/hooks/useTransactions';
import { sdk } from '~/lib/sdk';
import { Transaction, Wallet, WalletTokenBalance } from '~/lib/types';
Expand Down Expand Up @@ -84,6 +85,11 @@ export default function WalletBalancePage() {
callFetch<{ transaction: Transaction }>('/api/getTransaction', data)
}
onConfirmed={() => refetchTransactions()}
onScreenAddress={(address: string) =>
callFetch<ScreenAddressResult>('/api/complianceScreenAddress', {
address,
})
}
/>
</div>
</WalletDetails>
Expand Down
1 change: 1 addition & 0 deletions packages/circle-demo-webapp/app/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
* {
@apply border-border;
}

body {
@apply bg-background text-foreground;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/circle-demo-webapp/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export default {
sm: 'calc(var(--radius) - 4px)',
},
colors: {
error: '#D66262',
success: '#039855',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
Expand Down

0 comments on commit 0f4e226

Please sign in to comment.