Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swap widget Updates #333

Merged
merged 24 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6673016
Merge branch 'feature/ipad' into feature/swap-widget
siandreev Dec 12, 2024
7a8296a
fix: make widget storage not permanent (except swap settings)
siandreev Dec 12, 2024
2947f53
fix: i18n query params for widget
siandreev Dec 12, 2024
6202bb1
fix: swap tx info tooltips
siandreev Dec 12, 2024
4c82d7b
fix: tokens from swap list open explorer button
siandreev Dec 12, 2024
7018782
fix: add advanced battery messages
siandreev Dec 12, 2024
e0c59c2
fix: swap widget tx successfully sent modal added
siandreev Dec 13, 2024
d475fec
fix: add app version; refactor tx send flow
siandreev Dec 13, 2024
47d3b26
fix: widget modals bottom padding
siandreev Dec 13, 2024
ac4f2df
fix: improve tx send error handling
siandreev Dec 13, 2024
6bb0b0f
fix: scroll tokens list
siandreev Dec 13, 2024
49173b9
chore: bump version
siandreev Dec 13, 2024
66704df
fix: button text
siandreev Dec 13, 2024
7920f3b
fix: button text
siandreev Dec 13, 2024
2a6888f
fix: confirm button
siandreev Dec 13, 2024
f88fb57
fix: remove tokens external links for widget
siandreev Dec 13, 2024
d566cf0
fix: skip return value check
siandreev Dec 14, 2024
3f9bfab
fix: log error; handle old android versions
siandreev Dec 16, 2024
4303229
fix: add logs
siandreev Dec 17, 2024
8d82d2c
fix: remove logs
siandreev Dec 17, 2024
7a3db17
fix: remove error toasts
siandreev Dec 17, 2024
94f8594
fix: ts error
siandreev Dec 17, 2024
8a575c5
fix: terms and policy links layout
siandreev Dec 18, 2024
399181c
Merge remote-tracking branch 'origin/main' into feature/swap-widget
KuznetsovNikita Dec 19, 2024
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
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
Loading