Skip to content

Commit

Permalink
chore: Buy component touch ups (#1775)
Browse files Browse the repository at this point in the history
Co-authored-by: Alissa Crane <[email protected]>
  • Loading branch information
abcrane123 and alissacrane-cb authored Dec 20, 2024
1 parent a154ba1 commit 19d7524
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 23 deletions.
2 changes: 2 additions & 0 deletions src/buy/components/Buy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function Buy({
maxSlippage: FALLBACK_DEFAULT_MAX_SLIPPAGE,
},
className,
disabled = false,
experimental = { useAggregator: false },
isSponsored = false,
onError,
Expand All @@ -51,6 +52,7 @@ export function Buy({
return (
<BuyProvider
config={config}
disabled={disabled}
experimental={experimental}
isSponsored={isSponsored}
onError={onError}
Expand Down
2 changes: 1 addition & 1 deletion src/buy/components/BuyAmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function BuyAmountInput() {
return (
<div
className={cn(
'flex h-full items-center border px-2 pl-4',
'flex h-12 items-center border px-2 pl-4',
background.default,
border.radius,
)}
Expand Down
3 changes: 2 additions & 1 deletion src/buy/components/BuyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useBuyContext } from './BuyProvider';
export function BuyButton() {
const {
address,
disabled,
setIsDropdownOpen,
isDropdownOpen,
from,
Expand All @@ -35,7 +36,7 @@ export function BuyButton() {
statusName === 'transactionApproved';

const isMissingRequiredField = !to?.amount || !to?.token;
const isDisabled = isLoading;
const isDisabled = isLoading || disabled;

const handleSubmit = useCallback(() => {
if (isMissingRequiredField) {
Expand Down
19 changes: 19 additions & 0 deletions src/buy/components/BuyOnrampItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,23 @@ describe('BuyOnrampItem', () => {
expect(button).toHaveClass('flex items-center gap-2 rounded-lg p-2');
expect(button).toHaveAttribute('type', 'button');
});

it('should show overlay on mouse enter', () => {
const { getByTestId, getByText, queryByText } = render(
<BuyOnrampItem
name="Apple Pay"
description="Fast and secure payments."
onClick={mockOnClick}
icon="applePay"
/>,
);

fireEvent.mouseEnter(getByTestId('ockBuyApplePayInfo'));

expect(getByText('Only on mobile and Safari')).toBeInTheDocument();

fireEvent.mouseLeave(getByTestId('ockBuyApplePayInfo'));

expect(queryByText('Only on mobile and Safari')).toBeNull();
});
});
8 changes: 7 additions & 1 deletion src/buy/components/BuyOnrampItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Tooltip } from '@/ui-react/internal/components/Tooltip';
import { useCallback } from 'react';
import { appleSvg } from '../../internal/svg/appleSvg';
import { cardSvg } from '../../internal/svg/cardSvg';
Expand Down Expand Up @@ -47,7 +48,12 @@ export function BuyOnrampItem({
{ONRAMP_ICON_MAP[icon]}
</div>
<div className="flex flex-col items-start">
<div>{name}</div>
<div className="relative flex items-center gap-1">
<div>{name}</div>
{name === 'Apple Pay' && (
<Tooltip content="Only on mobile and Safari" />
)}
</div>
<div className={cn('text-xs', color.foregroundMuted)}>
{description}
</div>
Expand Down
22 changes: 12 additions & 10 deletions src/buy/components/BuyProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -813,24 +813,26 @@ describe('BuyProvider', () => {
});
});

it('should setLifecycleStatus to error when projectId is not provided', async () => {
it('logs an error when projectId is not provided', async () => {
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
(useOnchainKit as Mock).mockReturnValue({
projectId: undefined,
config: {
paymaster: undefined,
},
});

const { result } = renderHook(() => useBuyContext(), { wrapper });
expect(result.current.lifecycleStatus).toEqual({
statusName: 'error',
statusData: expect.objectContaining({
code: 'TmBPc04',
error:
'Project ID is required, please set the projectId in the OnchainKitProvider',
message: '',
}),
renderHook(() => useBuyContext(), { wrapper });

await waitFor(() => {
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Project ID is required for this component, please set the projectId in the OnchainKitProvider',
);
});

consoleErrorSpy.mockRestore();
});

it('should handle submit correctly', async () => {
Expand Down
16 changes: 6 additions & 10 deletions src/buy/components/BuyProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function BuyProvider({
config = {
maxSlippage: FALLBACK_DEFAULT_MAX_SLIPPAGE,
},
disabled,
experimental,
isSponsored,
onError,
Expand Down Expand Up @@ -132,17 +133,11 @@ export function BuyProvider({

useEffect(() => {
if (!projectId) {
updateLifecycleStatus({
statusName: 'error',
statusData: {
code: 'TmBPc04',
error:
'Project ID is required, please set the projectId in the OnchainKitProvider',
message: '',
},
});
console.error(
'Project ID is required for this component, please set the projectId in the OnchainKitProvider',
);
}
}, [projectId, updateLifecycleStatus]);
}, [projectId]);

useEffect(() => {
// Reset inputs after status reset. `resetInputs` is dependent
Expand Down Expand Up @@ -424,6 +419,7 @@ export function BuyProvider({
const value = useValue({
address,
config,
disabled,
from,
fromETH,
fromUSDC,
Expand Down
3 changes: 3 additions & 0 deletions src/buy/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { Address, TransactionReceipt } from 'viem';
export type BuyReact = {
className?: string; // Optional className override for top div element.
config?: SwapConfig;
disabled?: boolean; // Disables Buy button
experimental?: {
useAggregator: boolean; // Whether to use a DEX aggregator. (default: true)
};
Expand All @@ -26,6 +27,7 @@ export type BuyReact = {
export type BuyContextType = {
address?: Address; // Used to check if user is connected in SwapButton
config: SwapConfig;
disabled?: boolean;
fromETH: SwapUnit;
fromUSDC: SwapUnit;
lifecycleStatus: LifecycleStatus;
Expand All @@ -48,6 +50,7 @@ export type BuyProviderReact = {
config?: {
maxSlippage: number; // Maximum acceptable slippage for a swap. (default: 10) This is as a percent, not basis points
};
disabled?: boolean;
experimental: {
useAggregator: boolean; // Whether to use a DEX aggregator. (default: true)
};
Expand Down
2 changes: 2 additions & 0 deletions src/internal/svg/appleSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export const appleSvg = (
preserveAspectRatio="xMidYMid meet"
id="Artwork"
data-testid="appleSvg"
width="24"
height="24"
>
<title>Apple Pay Onramp</title>
<path
Expand Down
35 changes: 35 additions & 0 deletions src/ui/react/internal/components/Tooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { Tooltip } from './Tooltip';

describe('Tooltip', () => {
it('renders the children', () => {
render(<Tooltip content="Test Content" />);
expect(screen.getByTestId('ockBuyApplePayInfo')).toBeInTheDocument();
});

it('shows the content on mouse enter', () => {
render(<Tooltip content="Test Content" />);
const triggerElement = screen.getByTestId('ockBuyApplePayInfo');

fireEvent.mouseEnter(triggerElement);
expect(screen.getByText('Test Content')).toBeInTheDocument();
});

it('hides the content on mouse leave', () => {
render(<Tooltip content="Test Content" />);
const triggerElement = screen.getByTestId('ockBuyApplePayInfo');

fireEvent.mouseEnter(triggerElement);
fireEvent.mouseLeave(triggerElement);

expect(screen.queryByText('Test Content')).not.toBeInTheDocument();
});

it('renders custom children if provided', () => {
const CustomChild = <div>Custom Child</div>;
render(<Tooltip content="Test Content">{CustomChild}</Tooltip>);

expect(screen.getByText('Custom Child')).toBeInTheDocument();
});
});
47 changes: 47 additions & 0 deletions src/ui/react/internal/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { infoSvg } from '@/internal/svg/infoSvg';
import { background, border, cn, text } from '@/styles/theme';
import { useCallback, useState } from 'react';

type TooltipReact = {
children?: React.ReactNode;
content: React.ReactNode;
};

export function Tooltip({ children = infoSvg, content }: TooltipReact) {
const [isOverlayVisible, setIsOverlayVisible] = useState(false);

const showOverlay = useCallback(() => {
setIsOverlayVisible(true);
}, []);

const hideOverlay = useCallback(() => {
setIsOverlayVisible(false);
}, []);

return (
<>
<div
data-testid="ockBuyApplePayInfo"
className={cn('h-2.5 w-2.5 cursor-pointer object-cover')}
onMouseEnter={showOverlay}
onMouseLeave={hideOverlay}
>
{children}
</div>
{isOverlayVisible && (
<div
className={cn(
'absolute top-0 right-0 flex translate-x-[100%] translate-y-[-100%]',
'whitespace-nowrap p-2',
border.radius,
background.inverse,
text.legal,
border.lineDefault,
)}
>
{content}
</div>
)}
</>
);
}

0 comments on commit 19d7524

Please sign in to comment.