diff --git a/package.json b/package.json
index 6ee7f72..87a3e00 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"*.css"
],
"dependencies": {
- "@hot-wallet/sdk": "^1.0.11"
+ "@hot-wallet/sdk": "^1.0.11",
+ "qrcode.react": "^4.2.0"
}
}
diff --git a/src/assets/Icons.tsx b/src/assets/Icons.tsx
index 5276c99..b64e4df 100644
--- a/src/assets/Icons.tsx
+++ b/src/assets/Icons.tsx
@@ -215,30 +215,57 @@ export const LogOut = ({ fill = '#666666' }: { fill?: string }) => (
);
export const Copy = ({ fill = '#4D4D4D' }: { fill?: string }) => (
+);
+export const LargeCopy = ({ fill = '#0C1083' }: { fill?: string }) => (
+
);
+
export const History = ({ fill = '#0C1083' }: { fill?: string }) => (
);
+
+export const ArrowDropDown = ({ fill = '#0C1083' }: { fill?: string }) => (
+
+);
+
export const RedAlert = () => (
);
+export const SwapIcon = ({ fill = '#0C1083' }: { fill?: string }) => (
+
+);
+export const ReceiveIcon = ({ fill = '#0C1083' }: { fill?: string }) => (
+
+);
+export const BalancesIcon = ({ fill = '#0C1083' }: { fill?: string }) => (
+
+);
+
+export const TokenIcon = ({ fill = '#0C1083' }: { fill?: string }) => (
+
+);
diff --git a/src/assets/bluxLogo.tsx b/src/assets/bluxLogo.tsx
index 6d00b72..c219006 100644
--- a/src/assets/bluxLogo.tsx
+++ b/src/assets/bluxLogo.tsx
@@ -71,3 +71,28 @@ const BluxLogo = ({ fill = 'black' }: { fill?: string }) => (
);
export default BluxLogo;
+
+export const SmallBlux = ({
+ fill = 'black',
+ background = 'transparent',
+}: {
+ fill?: string;
+ background?: string;
+}) => (
+
+);
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
index 697cdca..b90eae4 100644
--- a/src/components/Button/index.tsx
+++ b/src/components/Button/index.tsx
@@ -2,9 +2,10 @@ import React from 'react';
import { useProvider } from '../../context/provider';
import getContrastColor from '../../utils/getContrastColor';
+import hexToRgba from '../../utils/hexToRgba';
type ButtonSize = 'small' | 'medium' | 'large';
-type ButtonVariant = 'outline' | 'text' | 'fill';
+type ButtonVariant = 'outline' | 'text' | 'fill' | 'tonal';
type ButtonState = 'enabled' | 'disabled' | 'selected';
interface ButtonProps {
@@ -68,6 +69,11 @@ const Button = ({
color: appearance.accent,
backgroundColor: 'transparent',
});
+ } else if (variant === 'tonal') {
+ Object.assign(baseStyle, {
+ color: appearance.accent,
+ backgroundColor: hexToRgba(appearance.accent, 0.1),
+ });
}
if (state === 'selected') {
diff --git a/src/components/CardItem/index.tsx b/src/components/CardItem/index.tsx
index 3797bfd..328c1a9 100644
--- a/src/components/CardItem/index.tsx
+++ b/src/components/CardItem/index.tsx
@@ -6,6 +6,7 @@ import { useLang } from '../../hooks/useLang';
type CardItemProps = {
variant?: 'social' | 'default' | 'input';
+ size?: 'small' | 'medium';
startIcon: React.ReactNode;
endArrow?: boolean;
isRecent?: boolean;
@@ -19,6 +20,7 @@ type CardItemProps = {
const CardItem = ({
variant = 'default',
+ size = 'medium',
startIcon,
endArrow,
isRecent,
@@ -72,8 +74,12 @@ const CardItem = ({
return (
{startIcon}
-
+
{variant === 'input' ? (
<>
>
) : (
- {label}
+
+ {label}
+
)}
{isRecent && (
@@ -157,7 +173,7 @@ const CardItem = ({
)}
- {endArrow && (
+ {endArrow && size === 'medium' && (
diff --git a/src/components/QRCode/index.tsx b/src/components/QRCode/index.tsx
new file mode 100644
index 0000000..b387725
--- /dev/null
+++ b/src/components/QRCode/index.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { QRCodeCanvas } from 'qrcode.react';
+
+interface QRCodeCanvasProps {
+ value: string;
+ title?: string;
+ size?: number;
+ bgColor?: string;
+ fgColor?: string;
+ level?: 'L' | 'M' | 'Q' | 'H';
+}
+
+const QRCode = ({
+ value,
+ title = '',
+ size = 184,
+ bgColor = '#ffffff',
+ fgColor = '#00020f',
+ level = 'Q',
+ ...rest
+}: QRCodeCanvasProps) => {
+ return (
+
+
+
+ );
+};
+
+export default QRCode;
diff --git a/src/components/TabBox/index.tsx b/src/components/TabBox/index.tsx
new file mode 100644
index 0000000..b2f82f4
--- /dev/null
+++ b/src/components/TabBox/index.tsx
@@ -0,0 +1,59 @@
+import React, { useState } from 'react';
+import hexToRgba from '../../utils/hexToRgba';
+import { useProvider } from '../../context/provider';
+
+type Tab = {
+ label: string;
+ icon: React.JSX.Element;
+ content: React.ReactNode;
+};
+
+type TabsProps = {
+ tabs: Tab[];
+};
+const TabBox = ({ tabs }: TabsProps) => {
+ const context = useProvider();
+ const { appearance } = context.value.config;
+ const [activeTab, setActiveTab] = useState(0);
+
+ return (
+ <>
+
+ {tabs.map((tab, index) => {
+ const isActive = activeTab === index;
+
+ return (
+
setActiveTab(index)}
+ role="tab"
+ aria-label={tab.label}
+ aria-selected={activeTab === index}
+ tabIndex={activeTab === index ? 0 : -1}
+ className="bluxcc:flex bluxcc:h-20 bluxcc:max-w-[96px] bluxcc:cursor-pointer bluxcc:flex-col bluxcc:items-center bluxcc:justify-center bluxcc:gap-2 bluxcc:px-7 bluxcc:py-4 bluxcc:text-sm bluxcc:font-medium bluxcc:transition-all bluxcc:duration-300"
+ style={{
+ background: isActive
+ ? hexToRgba(appearance.accent, 0.1)
+ : appearance.background,
+ color: isActive ? appearance.accent : appearance.textColor,
+ borderRadius: appearance.borderRadius,
+ }}
+ >
+
{tab.icon}
+
{tab.label}
+
+ );
+ })}
+
+
+
+ {tabs[activeTab]?.content}
+
+ >
+ );
+};
+export default TabBox;
diff --git a/src/constants/index.ts b/src/constants/index.ts
index 89d0f09..0df8497 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -9,7 +9,7 @@ export const defaultLightTheme: IAppearance = {
accent: '#0c1083',
borderWidth: '1px',
bgField: '#ffffff',
- borderRadius: '24px',
+ borderRadius: '16px',
textColor: '#000000',
background: '#ffffff',
includeBorders: true,
@@ -23,7 +23,7 @@ export const defaultDarkTheme: IAppearance = {
accent: '#ffffff',
borderWidth: '1px',
bgField: '#1a1a1a',
- borderRadius: '24px',
+ borderRadius: '16px',
textColor: '#ffffff',
background: '#000000',
includeBorders: true,
diff --git a/src/constants/locales.ts b/src/constants/locales.ts
index b3c6dfe..4983dd6 100644
--- a/src/constants/locales.ts
+++ b/src/constants/locales.ts
@@ -38,6 +38,10 @@ const translations: Translations = {
en: 'Receive',
es: 'Recibir',
},
+ balances: {
+ en: 'Balances',
+ es: 'Saldos',
+ },
wrongNetwork: {
en: 'Wrong Network',
es: 'Red incorrecta',
diff --git a/src/containers/BluxModal/content.tsx b/src/containers/BluxModal/content.tsx
index 0d98a1b..e6a0ee9 100644
--- a/src/containers/BluxModal/content.tsx
+++ b/src/containers/BluxModal/content.tsx
@@ -1,5 +1,8 @@
-import { Routes } from '../../types';
+import React from 'react';
+
+import Swap from '../Pages/Swap';
import Send from '../Pages/Send';
+import Receive from '../Pages/Receive';
import Profile from '../Pages/Profile';
import Waiting from '../Pages/Waiting';
import Activity from '../Pages/Activity';
@@ -8,9 +11,11 @@ import OnBoarding from '../Pages/OnBoarding';
import ConfirmCode from '../Pages/ConfirmCode';
import WrongNetwork from '../Pages/WrongNetwork';
import SignTransaction from '../Pages/SignTransaction';
+
+import { Routes } from '../../types';
import { LanguageKey } from '../../constants/locales';
-import React from 'react';
import { translate } from '../../utils/translate';
+import Balances from '../Pages/Balances';
type RouteContent = {
title: string;
@@ -53,6 +58,18 @@ export const getModalContent = (
title: '',
Component:
,
},
+ [Routes.RECEIVE]: {
+ title: 'Receive address',
+ Component:
,
+ },
+ [Routes.SWAP]: {
+ title: 'Swap',
+ Component:
,
+ },
+ [Routes.BALANCES]: {
+ title: 'Balances',
+ Component:
,
+ },
[Routes.WRONG_NETWORK]: {
isSticky: true,
title: translate('wrongNetwork', lang),
diff --git a/src/containers/BluxModal/index.tsx b/src/containers/BluxModal/index.tsx
index 95af3a0..600b31b 100644
--- a/src/containers/BluxModal/index.tsx
+++ b/src/containers/BluxModal/index.tsx
@@ -21,7 +21,10 @@ export default function BluxModal({ isOpen, closeModal }: BluxModalProps) {
(route === Routes.ONBOARDING && value.showAllWallets) ||
route === Routes.ACTIVITY ||
route === Routes.SEND ||
- route === Routes.OTP;
+ route === Routes.OTP ||
+ route === Routes.BALANCES ||
+ route === Routes.RECEIVE ||
+ route === Routes.SWAP;
let modalIcon: 'back' | 'info' | undefined;
@@ -39,7 +42,13 @@ export default function BluxModal({ isOpen, closeModal }: BluxModalProps) {
setRoute(Routes.ONBOARDING);
} else if (value.showAllWallets) {
setValue((prev) => ({ ...prev, showAllWallets: false }));
- } else if (route === Routes.SEND || route === Routes.ACTIVITY) {
+ } else if (
+ route === Routes.SEND ||
+ route === Routes.ACTIVITY ||
+ route === Routes.BALANCES ||
+ route === Routes.RECEIVE ||
+ route === Routes.SWAP
+ ) {
setRoute(Routes.PROFILE);
}
};
diff --git a/src/containers/Pages/Balances/Assets/index.tsx b/src/containers/Pages/Balances/Assets/index.tsx
new file mode 100644
index 0000000..f62c562
--- /dev/null
+++ b/src/containers/Pages/Balances/Assets/index.tsx
@@ -0,0 +1,68 @@
+import React, { useState } from 'react';
+import humanizeAmount from '../../../../utils/humanizeAmount';
+import { IAsset } from '../../../../types';
+import { useLang } from '../../../../hooks/useLang';
+import { useProvider } from '../../../../context/provider';
+
+type AssetsProps = {
+ assets: IAsset[];
+};
+
+const Assets = ({ assets }: AssetsProps) => {
+ const [hoveredIndex, setHoveredIndex] = useState
(null);
+ const t = useLang();
+ const context = useProvider();
+ const { appearance } = context.value.config;
+
+ return (
+
+ {assets.map((asset, index) => (
+
setHoveredIndex(index)}
+ onMouseLeave={() => setHoveredIndex(null)}
+ className="bluxcc:flex bluxcc:cursor-pointer bluxcc:items-center bluxcc:justify-between bluxcc:px-4 bluxcc:py-3"
+ style={{
+ background:
+ hoveredIndex === index ? appearance.bgField : 'transparent',
+ color: appearance.textColor,
+ borderBottomStyle: 'dashed',
+ borderBottomWidth:
+ index < assets.length - 1
+ ? appearance.includeBorders
+ ? appearance.borderWidth
+ : '1px'
+ : '0px',
+ borderBottomColor: appearance.borderColor,
+ transition: 'all 0.2s ease-in-out',
+ }}
+ >
+
+
{asset.logo}
+
+
+ {asset.assetCode}
+
+ {asset.assetCode}
+
+
+
+
+ {humanizeAmount(asset.balance)}
+
+
+ ))}
+
+ {assets.length === 0 && (
+
+ {t('noAssetsFound')}
+
+ )}
+
+ );
+};
+
+export default Assets;
diff --git a/src/containers/Pages/Balances/index.tsx b/src/containers/Pages/Balances/index.tsx
new file mode 100644
index 0000000..d85f3dd
--- /dev/null
+++ b/src/containers/Pages/Balances/index.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { TokenIcon } from '../../../assets/Icons';
+import TabBox from '../../../components/TabBox';
+import Assets from './Assets';
+import { IAsset } from '../../../types';
+
+const Balances = () => {
+ const mockAssets: IAsset[] = [
+ {
+ assetCode: 'XLM',
+ assetIssuer: 'Stellar Foundation',
+ assetType: 'native',
+ balance: '1000.1234',
+ logo: '🌟',
+ },
+ {
+ assetCode: 'USDC',
+ assetIssuer: 'Centre Consortium',
+ assetType: 'credit_alphanum4',
+ balance: '500.5',
+ logo: '💵',
+ },
+ ];
+
+ const tabsContent = [
+ {
+ label: 'Assets',
+ icon: ,
+ content: ,
+ },
+ {
+ label: 'Tokens',
+ icon: ,
+
+ content: 'token',
+ },
+
+ {
+ label: 'NFTs',
+ icon: ,
+
+ content: 'nfts',
+ },
+ ];
+
+ return ;
+};
+
+export default Balances;
diff --git a/src/containers/Pages/Profile/index.tsx b/src/containers/Pages/Profile/index.tsx
index ed61ebf..ec784fd 100644
--- a/src/containers/Pages/Profile/index.tsx
+++ b/src/containers/Pages/Profile/index.tsx
@@ -3,15 +3,24 @@ import React, { useState } from 'react';
import { Routes } from '../../../types';
import copyText from '../../../utils/copyText';
import { useLang } from '../../../hooks/useLang';
-import { useBlux } from '../../../hooks/useBlux';
+// import { useBlux } from '../../../hooks/useBlux';
import CardItem from '../../../components/CardItem';
import { useProvider } from '../../../context/provider';
import shortenAddress from '../../../utils/shortenAddress';
import humanizeAmount from '../../../utils/humanizeAmount';
-import { Copy, History, LogOut, Send } from '../../../assets/Icons';
+import {
+ BalancesIcon,
+ Copy,
+ History,
+ // LogOut,
+ ReceiveIcon,
+ Send,
+ SwapIcon,
+} from '../../../assets/Icons';
+import hexToRgba from '../../../utils/hexToRgba';
const Profile = () => {
- const { logout } = useBlux();
+ // const { logout } = useBlux();
const t = useLang();
const context = useProvider();
const [copied, setCopied] = useState(false);
@@ -19,9 +28,9 @@ const Profile = () => {
const appearance = context.value.config.appearance;
const address = context.value.user.wallet?.address as string;
- const handleLogout = () => {
- logout();
- };
+ // const handleLogout = () => {
+ // logout();
+ // };
const handleCopyAddress = () => {
copyText(address)
@@ -30,7 +39,7 @@ const Profile = () => {
setTimeout(() => {
setCopied(false);
- }, 1000);
+ }, 2000);
})
.catch(() => {});
};
@@ -43,40 +52,68 @@ const Profile = () => {
return (
-
-
- {copied ? (
- t('copied')
- ) : (
-
- {address ? shortenAddress(address, 5) : ''}
-
-
- )}
-
-
- {balance ? `${humanizeAmount(balance)} XLM` : t('loading')}
-
+
+
+
+ {balance ? `${humanizeAmount(balance)} XLM` : t('loading')}
+
+
+ {copied ? (
+ t('copied')
+ ) : (
+
+ {address ? shortenAddress(address, 5) : ''}
+
+
+ )}
+
+
-
+
}
onClick={() => {
context.setRoute(Routes.SEND);
}}
/>
+ }
+ onClick={() => {
+ context.setRoute(Routes.RECEIVE);
+ }}
+ />
+ }
+ onClick={() => {
+ context.setRoute(Routes.SWAP);
+ }}
+ />
+
+
+ }
+ onClick={() => {
+ context.setRoute(Routes.BALANCES);
+ }}
+ />
{
/>
-
);
};
diff --git a/src/containers/Pages/Receive/index.tsx b/src/containers/Pages/Receive/index.tsx
new file mode 100644
index 0000000..84152b2
--- /dev/null
+++ b/src/containers/Pages/Receive/index.tsx
@@ -0,0 +1,104 @@
+import React from 'react';
+
+import Button from '../../../components/Button';
+import { useProvider } from '../../../context/provider';
+import { LargeCopy } from '../../../assets/Icons';
+import QRCode from '../../../components/QRCode';
+import { SmallBlux } from '../../../assets/bluxLogo';
+import hexToRgba from '../../../utils/hexToRgba';
+
+const Receive = () => {
+ const context = useProvider();
+ const appearance = context.value.config.appearance;
+ const address = context.value.user.wallet?.address as string;
+
+ return (
+
+
+
+
+
+ {/* divider */}
+
+
+
}
+ >
+ Copy address
+
+
+ );
+};
+
+export default Receive;
diff --git a/src/containers/Pages/Swap/AssetBox/index.tsx b/src/containers/Pages/Swap/AssetBox/index.tsx
new file mode 100644
index 0000000..385b773
--- /dev/null
+++ b/src/containers/Pages/Swap/AssetBox/index.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import getContrastColor from '../../../../utils/getContrastColor';
+import { StellarLogo } from '../../../../assets/logos';
+import { ArrowDropDown } from '../../../../assets/Icons';
+import { useProvider } from '../../../../context/provider';
+
+const AssetBox = ({ handleOpenAssets }: { handleOpenAssets: () => void }) => {
+ const context = useProvider();
+ const appearance = context.value.config.appearance;
+
+ return (
+
+ );
+};
+
+export default AssetBox;
diff --git a/src/containers/Pages/Swap/index.tsx b/src/containers/Pages/Swap/index.tsx
new file mode 100644
index 0000000..698f283
--- /dev/null
+++ b/src/containers/Pages/Swap/index.tsx
@@ -0,0 +1,202 @@
+import React, { useState } from 'react';
+import { useProvider } from '../../../context/provider';
+import Button from '../../../components/Button';
+import hexToRgba from '../../../utils/hexToRgba';
+import { ArrowDropUp, SwapIcon } from '../../../assets/Icons';
+
+import { useLang } from '../../../hooks/useLang';
+import { IAsset } from '../../../types';
+import SelectAssets from '../SelectAsset';
+import AssetBox from './AssetBox';
+
+const mockAssets: IAsset[] = [
+ {
+ assetCode: 'XLM',
+ assetIssuer: 'Stellar Foundation',
+ assetType: 'native',
+ balance: '1000.1234',
+ logo: '🌟',
+ },
+ {
+ assetCode: 'USDC',
+ assetIssuer: 'Centre Consortium',
+ assetType: 'credit_alphanum4',
+ balance: '500.5',
+ logo: '💵',
+ },
+];
+const Swap = () => {
+ const [showSelectAssetPage, setShowSelectAssetPage] = useState(false);
+ const [selectedAsset, setSelectedAsset] = useState
(mockAssets[0]);
+
+ const context = useProvider();
+ const appearance = context.value.config.appearance;
+ const t = useLang();
+
+ const handleOpenAssets = () => {
+ setShowSelectAssetPage(true);
+ };
+ if (showSelectAssetPage) {
+ return (
+
+ );
+ }
+ return (
+
+
+
+
+ From
+
+
+ 345.00{' '}
+
+ {t('max')}
+
+
+
+
+
+ ≈ $23.74 USD
+
+ {/* Swap Icon */}
+
+ {/* To Input */}
+
+
+
+ To
+
+
+
+
+
+ {/* Price Impact */}
+
+
Price Impact
+
+ %0.2
+
+
+
+
+ The estimated effect of your swap on the market price.{' '}
+
+ learn more
+
+
+
+ {/* divider */}
+
+
+ {/* Swap Button */}
+
+
+ );
+};
+
+export default Swap;
diff --git a/src/types/index.ts b/src/types/index.ts
index c3e802e..c70c563 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -173,6 +173,9 @@ export enum Routes {
SEND = 'SEND', // User sign transaction view
ACTIVITY = 'ACTIVITY', // User sign transaction view
OTP = 'OTP', // User Login with Phone ot email
+ RECEIVE = 'RECEIVE', // View for receive page
+ BALANCES = 'BALANCES', // View for balances
+ SWAP = 'SWAP', // View for swap assets
}
/**