Skip to content

Commit

Permalink
Merge pull request #333 from tonkeeper/feature/swap-widget
Browse files Browse the repository at this point in the history
Swap widget Updates
  • Loading branch information
KuznetsovNikita authored Dec 19, 2024
2 parents cd5f30c + 399181c commit 1fa1450
Show file tree
Hide file tree
Showing 23 changed files with 411 additions and 170 deletions.
2 changes: 1 addition & 1 deletion apps/web-swap-widget/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tonkeeper/web-swap-widget",
"version": "3.0.0",
"version": "3.0.5",
"author": "Ton APPS UK Limited <[email protected]>",
"description": "Web swap widget for Tonkeeper",
"dependencies": {
Expand Down
37 changes: 33 additions & 4 deletions apps/web-swap-widget/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { useAccountsStateQuery, useActiveAccountQuery } from '@tonkeeper/uikit/d
import { GlobalStyle } from '@tonkeeper/uikit/dist/styles/globalStyle';
import { FC, PropsWithChildren, Suspense, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { BrowserAppSdk } from './libs/appSdk';
import { WidgetAppSdk } from './libs/appSdk';
import { useAnalytics, useAppHeight, useApplyQueryParams, useAppWidth } from './libs/hooks';
import { useGlobalPreferencesQuery } from '@tonkeeper/uikit/dist/state/global-preferences';
import { useGlobalSetup } from '@tonkeeper/uikit/dist/state/globalSetup';
Expand All @@ -36,6 +36,7 @@ import { useAccountsStorage } from '@tonkeeper/uikit/dist/hooks/useStorage';
import { AccountTonWatchOnly } from '@tonkeeper/core/dist/entries/account';
import { getTonkeeperInjectionContext } from './libs/tonkeeper-injection-context';
import { Address } from '@ton/core';
import { defaultLanguage } from './i18n';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -46,16 +47,44 @@ const queryClient = new QueryClient({
}
});

const sdk = new BrowserAppSdk();
const sdk = new WidgetAppSdk();
const TARGET_ENV = 'swap-widget-web';

const queryParams = new URLSearchParams(new URL(window.location.href).search);

const queryParamLangKey = (supportedLanguages: string[]) => {
let key = queryParams.get('lang');

if (!key) {
return undefined;
}

if (supportedLanguages.includes(key)) {
return key;
}

if (key.includes('_')) {
key = key.split('_')[0].toLowerCase();

return supportedLanguages.includes(key) ? key : undefined;
}
};

export const App: FC = () => {
const languages = (import.meta.env.VITE_APP_LOCALES ?? defaultLanguage).split(',');
const queryParamsLang = queryParamLangKey(languages);

const { t: tSimple, i18n } = useTranslation();

useEffect(() => {
if (queryParamsLang && queryParamsLang !== defaultLanguage) {
i18n.reloadResources(queryParamsLang).then(() => i18n.changeLanguage(queryParamsLang));
}
}, []);

const t = useTWithReplaces(tSimple);

const translation = useMemo(() => {
const languages = (import.meta.env.VITE_APP_LOCALES ?? 'en').split(',');
const client: I18nContext = {
t,
i18n: {
Expand Down Expand Up @@ -225,7 +254,7 @@ const Loader: FC = () => {

const Wrapper = styled.div`
box-sizing: border-box;
padding: 0 16px 34px;
padding: 0 16px 46px;
height: 100%;
`;

Expand Down
8 changes: 6 additions & 2 deletions apps/web-swap-widget/src/components/SwapWidgetFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const Row = styled.div`
${Body3Class}
`;

const RowWrap = styled(Row)`
flex-wrap: wrap;
`;

const Link = styled.a`
display: flex;
align-items: center;
Expand Down Expand Up @@ -42,15 +46,15 @@ export const SwapWidgetFooter = () => {
</Link>
&nbsp;{t('and')}&nbsp;TON
</Row>
<Row>
<RowWrap>
<Link href="https://tonkeeper.com/privacy" target="_blank">
{t('legal_privacy')}
</Link>
<Dot />
<Link href="https://tonkeeper.com/terms" target="_blank">
{t('legal_terms')}
</Link>
</Row>
</RowWrap>
</Wrapper>
);
};
Expand Down
58 changes: 43 additions & 15 deletions apps/web-swap-widget/src/components/SwapWidgetPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { styled } from 'styled-components';
import { useEncodeSwapToTonConnectParams } from '@tonkeeper/uikit/dist/state/swap/useEncodeSwap';
import { useState } from 'react';
import {
useSelectedSwap,
useSwapFromAmount,
Expand All @@ -18,6 +17,8 @@ import { NonNullableFields } from '@tonkeeper/core/dist/utils/types';
import { SwapWidgetHeader } from './SwapWidgetHeader';
import { getTonkeeperInjectionContext } from '../libs/tonkeeper-injection-context';
import { SwapWidgetFooter } from './SwapWidgetFooter';
import { SwapWidgetTxSentNotification } from './SwapWidgetTxSent';
import { useDisclosure } from '@tonkeeper/uikit/dist/hooks/useDisclosure';

const MainFormWrapper = styled.div`
height: 100%;
Expand Down Expand Up @@ -54,30 +55,56 @@ const ChangeIconStyled = styled(IconButton)`

export const SwapWidgetPage = () => {
const { isLoading, mutateAsync: encode } = useEncodeSwapToTonConnectParams({
ignoreBattery: true
forceCalculateBattery: true
});
const [hasBeenSent, setHasBeenSent] = useState<boolean>(false);
const [selectedSwap] = useSelectedSwap();
const [fromAsset, setFromAsset] = useSwapFromAsset();
const [toAsset, setToAsset] = useSwapToAsset();
const [_, setFromAmount] = useSwapFromAmount();
const { isOpen, onClose, onOpen } = useDisclosure();

const onConfirm = async () => {
const params = await encode(selectedSwap! as NonNullableFields<CalculatedSwap>);

const ctx = getTonkeeperInjectionContext()!;

ctx.sendTransaction({
source: ctx.address,
// legacy tonkeeper api, timestamp in ms
valid_until: params.valid_until * 1000,
messages: params.messages.map(m => ({
address: m.address,
amount: m.amount.toString(),
payload: m.payload
}))
}).finally(() => setHasBeenSent(false));
setHasBeenSent(true);
try {
const result = await ctx.sendTransaction({
source: ctx.address,
/**
* legacy tonkeeper api, timestamp in ms
*/
valid_until: params.valid_until * 1000,
messages: params.messages.map(m => ({
address: m.address,
amount: m.amount.toString(),
payload: m.payload
})),
messagesVariants: params.messagesVariants
? Object.fromEntries(
Object.entries(params.messagesVariants).map(([k, v]) => [
k,
v.map(m => ({
address: m.address,
amount: m.amount.toString(),
payload: m.payload
}))
])
)
: undefined
});

/**
old tonkeeper android versions return empty result instead of throwing
*/
if (!result) {
throw new Error('Operation failed');
}

onOpen();
} catch (e) {
console.error(e);
}
};

const onChangeFields = () => {
Expand All @@ -97,10 +124,11 @@ export const SwapWidgetPage = () => {
</ChangeIconStyled>
</SwapFromField>
<SwapToField separateInfo />
<SwapButton onClick={onConfirm} isEncodingProcess={isLoading || hasBeenSent} />
<SwapButton onClick={onConfirm} isEncodingProcess={isLoading} />
<Spacer />
<SwapWidgetFooter />
<SwapTokensListNotification />
<SwapWidgetTxSentNotification isOpen={isOpen} onClose={onClose} />
</MainFormWrapper>
);
};
78 changes: 78 additions & 0 deletions apps/web-swap-widget/src/components/SwapWidgetTxSent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Notification } from '@tonkeeper/uikit/dist/components/Notification';
import { FC } from 'react';
import styled, { useTheme } from 'styled-components';
import { Body2, Button, Label2 } from '@tonkeeper/uikit';
import { useTranslation } from 'react-i18next';

const NotificationStyled = styled(Notification)`
.dialog-header {
margin-bottom: 0;
}
`;

export const SwapWidgetTxSentNotification: FC<{ isOpen: boolean; onClose: () => void }> = ({
isOpen,
onClose
}) => {
return (
<NotificationStyled isOpen={isOpen} handleClose={onClose}>
{() => <SwapWidgetTxSentNotificationContent onClose={onClose} />}
</NotificationStyled>
);
};

const Wrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
`;

const CheckmarkCircleIcon = () => {
const theme = useTheme();

return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="56"
height="56"
viewBox="0 0 56 56"
fill="none"
style={{ marginBottom: '12px' }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M28 50.0001C40.1503 50.0001 50 40.1504 50 28.0001C50 15.8499 40.1503 6.00012 28 6.00012C15.8497 6.00012 6 15.8499 6 28.0001C6 40.1504 15.8497 50.0001 28 50.0001ZM38.0607 24.0608C38.6464 23.475 38.6464 22.5252 38.0607 21.9395C37.4749 21.3537 36.5251 21.3537 35.9393 21.9395L25 32.8788L21.0607 28.9395C20.4749 28.3537 19.5251 28.3537 18.9393 28.9395C18.3536 29.5252 18.3536 30.475 18.9393 31.0608L23.9393 36.0608C24.5251 36.6466 25.4749 36.6466 26.0607 36.0608L38.0607 24.0608Z"
fill={theme.accentGreen}
/>
<path
opacity="0.32"
fillRule="evenodd"
clipRule="evenodd"
d="M38.0607 21.9395C38.6464 22.5252 38.6464 23.475 38.0607 24.0608L26.0607 36.0608C25.4749 36.6466 24.5251 36.6466 23.9393 36.0608L18.9393 31.0608C18.3536 30.475 18.3536 29.5252 18.9393 28.9395C19.5251 28.3537 20.4749 28.3537 21.0607 28.9395L25 32.8788L35.9393 21.9395C36.5251 21.3537 37.4749 21.3537 38.0607 21.9395Z"
fill={theme.accentGreen}
/>
</svg>
);
};

const Body2Secondary = styled(Body2)`
color: ${p => p.theme.textSecondary};
margin-bottom: 24px;
margin-top: 4px;
`;

const SwapWidgetTxSentNotificationContent: FC<{ onClose: () => void }> = ({ onClose }) => {
const { t } = useTranslation();
return (
<Wrapper>
<CheckmarkCircleIcon />
<Label2>{t('swap_transaction_sent_title')}</Label2>
<Body2Secondary>{t('swap_transaction_sent_description')}</Body2Secondary>
<Button fullWidth onClick={onClose}>
{t('swap_transaction_sent_close_button')}
</Button>
</Wrapper>
);
};
31 changes: 16 additions & 15 deletions apps/web-swap-widget/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';

i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
debug: false,
lng: 'en', // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
// if you're using a language detector, do not define the lng option
fallbackLng: 'en',
interpolation: {
escapeValue: false, // react already safes from xss
},
});
export const defaultLanguage = 'en';

i18n.use(Backend)
.use(LanguageDetector)
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
debug: false,
lng: defaultLanguage, // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
// if you're using a language detector, do not define the lng option
fallbackLng: defaultLanguage,
interpolation: {
escapeValue: false // react already safes from xss
}
});

export default i18n;
6 changes: 5 additions & 1 deletion apps/web-swap-widget/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import ReactDOM from 'react-dom/client';
import { App } from './App';
import './i18n';
import { WidgetAppSdk } from './libs/appSdk';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
const rootElement = document.getElementById('root') as HTMLElement;
rootElement.setAttribute('data-app-version', WidgetAppSdk.version);

const root = ReactDOM.createRoot(rootElement);

root.render(<App />);
10 changes: 6 additions & 4 deletions apps/web-swap-widget/src/libs/appSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BaseApp } from '@tonkeeper/core/dist/AppSdk';
import copyToClipboard from 'copy-to-clipboard';
import packageJson from '../../package.json';
import { disableScroll, enableScroll, getScrollbarWidth } from './scroll';
import { BrowserStorage } from './storage';
import { SwapWidgetStorage } from './storage';

function iOS() {
return (
Expand All @@ -14,9 +14,11 @@ function iOS() {
);
}

export class BrowserAppSdk extends BaseApp {
export class WidgetAppSdk extends BaseApp {
static version = packageJson.version ?? 'Unknown';

constructor() {
super(new BrowserStorage());
super(new SwapWidgetStorage());
}

copyToClipboard = (value: string, notification?: string) => {
Expand All @@ -42,7 +44,7 @@ export class BrowserAppSdk extends BaseApp {
isStandalone = () =>
iOS() && ((window.navigator as unknown as { standalone: boolean }).standalone as boolean);

version = packageJson.version ?? 'Unknown';
version = WidgetAppSdk.version;

targetEnv = 'web' as const;
}
Loading

0 comments on commit 1fa1450

Please sign in to comment.