From b51afbdae2773197534bae2f35c7cbb3b6a170f8 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 29 Feb 2024 15:26:28 +0100 Subject: [PATCH 01/27] fix: pro subscription state integrated to the banner, columns choose modal and aside --- .../uikit/src/components/Notification.tsx | 2 +- .../src/components/aside/SubscriptionInfo.tsx | 66 +++++++-- .../components/dashboard/CategoriesModal.tsx | 135 +++++++++--------- .../components/dashboard/DashboardTable.tsx | 6 +- .../dashboard/columns/DashboardCell.tsx | 2 +- .../uikit/src/components/pro/ProBanner.tsx | 44 +++++- .../src/components/pro/ProNotification.tsx | 14 ++ .../src/components/settings/ProSettings.tsx | 26 ++-- packages/uikit/src/hooks/useClickOutside.ts | 15 +- packages/uikit/src/hooks/useDateTimeFormat.ts | 12 +- packages/uikit/src/hooks/useDisclosure.ts | 10 ++ .../dashboard/dashboard-column.ts | 0 .../dashboard/useDashboardColumns.ts | 6 +- .../dashboard/useDashboardData.ts | 2 +- packages/uikit/src/state/pro.ts | 6 +- 15 files changed, 233 insertions(+), 113 deletions(-) create mode 100644 packages/uikit/src/components/pro/ProNotification.tsx create mode 100644 packages/uikit/src/hooks/useDisclosure.ts rename packages/uikit/src/{hooks => state}/dashboard/dashboard-column.ts (100%) rename packages/uikit/src/{hooks => state}/dashboard/useDashboardColumns.ts (96%) rename packages/uikit/src/{hooks => state}/dashboard/useDashboardData.ts (97%) diff --git a/packages/uikit/src/components/Notification.tsx b/packages/uikit/src/components/Notification.tsx index bbe314be0..14d95927e 100644 --- a/packages/uikit/src/components/Notification.tsx +++ b/packages/uikit/src/components/Notification.tsx @@ -483,7 +483,7 @@ export const Notification: FC<{ } }, [isFullWidth, handleClose]); - const containerRef = useClickOutside(onClickOutside); + const containerRef = useClickOutside(onClickOutside, nodeRef.current); return ( diff --git a/packages/uikit/src/components/aside/SubscriptionInfo.tsx b/packages/uikit/src/components/aside/SubscriptionInfo.tsx index acda93495..11ca90aea 100644 --- a/packages/uikit/src/components/aside/SubscriptionInfo.tsx +++ b/packages/uikit/src/components/aside/SubscriptionInfo.tsx @@ -2,6 +2,13 @@ import { FC } from 'react'; import styled from 'styled-components'; import { Body3 } from '../Text'; import { useDateTimeFormat } from '../../hooks/useDateTimeFormat'; +import { useProState } from '../../state/pro'; +import { Link } from 'react-router-dom'; +import { AppRoute, SettingsRoute } from '../../libs/routes'; + +const LinkStyled = styled(Link)` + text-decoration: none; +`; const InfoStyled = styled(Body3)` display: block; @@ -9,28 +16,59 @@ const InfoStyled = styled(Body3)` color: ${p => p.theme.textSecondary}; `; -const isTrialActive = false; // TODO -const isPurchaseActive = true; -const expirationDate = new Date(Date.now() + 100000000); - export const SubscriptionInfo: FC<{ className?: string }> = ({ className }) => { const formatDate = useDateTimeFormat(); + const { data } = useProState(); + + if (!data) { + return null; + } + + const { + subscription: { is_trial, valid, next_charge } + } = data; + + const linkToSettings = AppRoute.settings + SettingsRoute.pro; - if (isTrialActive) { + if (is_trial) { return ( - - Pro Trial is active.
It expires on{' '} - {formatDate(expirationDate, { day: 'numeric', month: 'short', year: 'numeric' })} - + + + Pro Trial is active. + {next_charge && ( + <> + 
It expires on{' '} + {formatDate(next_charge, { + day: 'numeric', + month: 'short', + year: 'numeric', + inputUnit: 'seconds' + })} + + )} + + ); } - if (isPurchaseActive) { + if (valid) { return ( - - Pro Subscription is active.
It expires on{' '} - {formatDate(expirationDate, { day: 'numeric', month: 'short', year: 'numeric' })} - + + + Pro Subscription is active. + {next_charge && ( + <> + It expires on{' '} + {formatDate(next_charge, { + day: 'numeric', + month: 'short', + year: 'numeric', + inputUnit: 'seconds' + })} + + )} + + ); } diff --git a/packages/uikit/src/components/dashboard/CategoriesModal.tsx b/packages/uikit/src/components/dashboard/CategoriesModal.tsx index 4b69c0102..9999bbf9e 100644 --- a/packages/uikit/src/components/dashboard/CategoriesModal.tsx +++ b/packages/uikit/src/components/dashboard/CategoriesModal.tsx @@ -10,10 +10,13 @@ import { DashboardColumnsForm, useDashboardColumnsAsForm, useDashboardColumnsForm -} from '../../hooks/dashboard/useDashboardColumns'; +} from '../../state/dashboard/useDashboardColumns'; import { Button } from '../fields/Button'; -import { DashboardColumn } from '../../hooks/dashboard/dashboard-column'; +import { DashboardColumn } from '../../state/dashboard/dashboard-column'; import { Badge } from '../shared'; +import { useProState } from '../../state/pro'; +import { ProNotification } from '../pro/ProNotification'; +import { useDisclosure } from '../../hooks/useDisclosure'; export const CategoriesModal: FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, @@ -78,11 +81,6 @@ export const CategoriesModal: FC<{ isOpen: boolean; onClose: () => void }> = ({ ); }; -const activateSubscription = () => { - alert('activate subscription'); -}; /* TODO */ -const isSubscriptionActive = Math.random() > 0.5; /* TODO */ - const CategoriesModalContent: FC<{ categories: DashboardColumn[]; categoriesForm: DashboardColumnsForm; @@ -90,6 +88,10 @@ const CategoriesModalContent: FC<{ data: DashboardColumnsForm | ((data: DashboardColumnsForm) => DashboardColumnsForm) ) => void; }> = ({ categories, categoriesForm, setCategoriesForm }) => { + const { data } = useProState(); + const isProEnabled = data?.subscription.valid; + const { isOpen, onClose, onOpen } = useDisclosure(); + const handleDrop: OnDragEndResponder = useCallback(droppedItem => { const destination = droppedItem.destination; if (!destination) return; @@ -108,66 +110,65 @@ const CategoriesModalContent: FC<{ ); }; return ( - - - {provided => ( - - {categoriesForm.map(({ id, isEnabled }, index) => ( - - {(p, snapshotDrag) => { - let transform = p.draggableProps.style?.transform; - - if (snapshotDrag.isDragging && transform) { - transform = transform.replace(/\(.+\,/, '(0,'); - } - - const style = { - ...p.draggableProps.style, - transform - }; - - const category = categories.find(c => c.id === id); - const isDisabled = category?.onlyPro && !isSubscriptionActive; - - return ( - - - - isDisabled && activateSubscription() - } - > - - - - {category?.name} - {category?.onlyPro && PRO} - - !isDisabled && - onCheckboxChange(id, value) - } - /> - - - - ); - }} - - ))} - {provided.placeholder} - - )} - - + <> + + + {provided => ( + + {categoriesForm.map(({ id, isEnabled }, index) => ( + + {(p, snapshotDrag) => { + let transform = p.draggableProps.style?.transform; + + if (snapshotDrag.isDragging && transform) { + transform = transform.replace(/\(.+\,/, '(0,'); + } + + const style = { + ...p.draggableProps.style, + transform + }; + + const category = categories.find(c => c.id === id); + const isDisabled = category?.onlyPro && !isProEnabled; + + return ( + + + isDisabled && onOpen()}> + + + + {category?.name} + {category?.onlyPro && PRO} + + !isDisabled && + onCheckboxChange(id, value) + } + /> + + + + ); + }} + + ))} + {provided.placeholder} + + )} + + + + ); }; diff --git a/packages/uikit/src/components/dashboard/DashboardTable.tsx b/packages/uikit/src/components/dashboard/DashboardTable.tsx index 3f2266e16..198a7f1f2 100644 --- a/packages/uikit/src/components/dashboard/DashboardTable.tsx +++ b/packages/uikit/src/components/dashboard/DashboardTable.tsx @@ -1,9 +1,9 @@ import styled, { css } from 'styled-components'; import { FC, useEffect, useRef, useState } from 'react'; import { Body2 } from '../Text'; -import { useDashboardColumnsAsForm } from '../../hooks/dashboard/useDashboardColumns'; -import { useDashboardData } from '../../hooks/dashboard/useDashboardData'; -import { DashboardCellAddress, DashboardColumnType } from '../../hooks/dashboard/dashboard-column'; +import { useDashboardColumnsAsForm } from '../../state/dashboard/useDashboardColumns'; +import { useDashboardData } from '../../state/dashboard/useDashboardData'; +import { DashboardCellAddress, DashboardColumnType } from '../../state/dashboard/dashboard-column'; import { DashboardCell } from './columns/DashboardCell'; const TableStyled = styled.table` diff --git a/packages/uikit/src/components/dashboard/columns/DashboardCell.tsx b/packages/uikit/src/components/dashboard/columns/DashboardCell.tsx index e289df648..01932c8fb 100644 --- a/packages/uikit/src/components/dashboard/columns/DashboardCell.tsx +++ b/packages/uikit/src/components/dashboard/columns/DashboardCell.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { DashboardCell as DashboardCellProps } from '../../../hooks/dashboard/dashboard-column'; +import { DashboardCell as DashboardCellProps } from '../../../state/dashboard/dashboard-column'; import { StringCell } from './StringCell'; import { AddressCell } from './AddressCell'; import { Network } from '@tonkeeper/core/dist/entries/network'; diff --git a/packages/uikit/src/components/pro/ProBanner.tsx b/packages/uikit/src/components/pro/ProBanner.tsx index e76d05700..4ec3417f0 100644 --- a/packages/uikit/src/components/pro/ProBanner.tsx +++ b/packages/uikit/src/components/pro/ProBanner.tsx @@ -2,6 +2,10 @@ import { FC } from 'react'; import styled from 'styled-components'; import { Body2, Label2 } from '../Text'; import { Button } from '../fields/Button'; +import { useProState } from '../../state/pro'; +import { useDateTimeFormat } from '../../hooks/useDateTimeFormat'; +import { ProNotification } from './ProNotification'; +import { useDisclosure } from '../../hooks/useDisclosure'; const ProBannerStyled = styled.div` background: ${p => p.theme.backgroundContent}; @@ -25,6 +29,22 @@ const ButtonsContainerStyled = styled.div` `; export const ProBanner: FC<{ className?: string }> = ({ className }) => { + const formatDate = useDateTimeFormat(); + const { data } = useProState(); + const { isOpen, onOpen, onClose } = useDisclosure(); + + if (!data) { + return null; + } + + const { + subscription: { is_trial, used_trial, valid, next_charge } + } = data; + + if (valid) { + return null; + } + return ( @@ -32,13 +52,29 @@ export const ProBanner: FC<{ className?: string }> = ({ className }) => { Access advanced features and tools to boost your work. - - + ) + )} + + + ); }; diff --git a/packages/uikit/src/components/pro/ProNotification.tsx b/packages/uikit/src/components/pro/ProNotification.tsx new file mode 100644 index 000000000..061ee314d --- /dev/null +++ b/packages/uikit/src/components/pro/ProNotification.tsx @@ -0,0 +1,14 @@ +import { FC } from 'react'; +import { Notification } from '../Notification'; +import { ProSettingsContent } from '../settings/ProSettings'; + +export const ProNotification: FC<{ isOpen: boolean; onClose: () => void }> = ({ + isOpen, + onClose +}) => { + return ( + + {() => } + + ); +}; diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index 9b9db00ca..527a64672 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -8,7 +8,7 @@ import { useFormatCoinValue } from '../../hooks/balance'; import { useTranslation } from '../../hooks/translation'; import { useAccountState } from '../../state/account'; import { - buyProServiceMutation, + useBuyProServiceMutation, useProLogout, useProPlans, useProState, @@ -162,7 +162,7 @@ const BuyProService: FC<{ data: ProState }> = ({ data }) => { const [plans, promoCode] = useProPlans(promo); - const { mutate, isLoading } = buyProServiceMutation(); + const { mutate, isLoading } = useBuyProServiceMutation(); useEffect(() => { if (plans && plans[0] && selectedPlan == null) { @@ -231,20 +231,28 @@ const ProContent: FC<{ data: ProState }> = ({ data }) => { return ; }; -export const ProSettings = () => { +export const ProSettingsContent: FC = () => { const { t } = useTranslation(); const { data } = useProState(); + return ( + <> + + + {t('tonkeeper_pro')} + {t('tonkeeper_pro_description')} + + {data ? : undefined} + + ); +}; + +export const ProSettings: FC = () => { return ( <> - - - {t('tonkeeper_pro')} - {t('tonkeeper_pro_description')} - - {data ? : undefined} + ); diff --git a/packages/uikit/src/hooks/useClickOutside.ts b/packages/uikit/src/hooks/useClickOutside.ts index 38b91abb9..be55084b7 100644 --- a/packages/uikit/src/hooks/useClickOutside.ts +++ b/packages/uikit/src/hooks/useClickOutside.ts @@ -1,19 +1,24 @@ import { useEffect, useRef } from 'react'; -export function useClickOutside(callback: () => void) { +export function useClickOutside( + callback: () => void, + areaElement?: A | null +) { const ref = useRef(null); useEffect(() => { - function handleClickOutside(event: MouseEvent) { + function handleClickOutside(event: Event) { if (ref.current && !ref.current.contains(event.target as Node)) { callback(); } } - document.addEventListener('mousedown', handleClickOutside); + const element = areaElement || document; + + element.addEventListener('mousedown', handleClickOutside); return () => { - document.removeEventListener('mousedown', handleClickOutside); + element.removeEventListener('mousedown', handleClickOutside); }; - }, [callback]); + }, [callback, areaElement]); return ref; } diff --git a/packages/uikit/src/hooks/useDateTimeFormat.ts b/packages/uikit/src/hooks/useDateTimeFormat.ts index a1a1dad3f..26fc089f8 100644 --- a/packages/uikit/src/hooks/useDateTimeFormat.ts +++ b/packages/uikit/src/hooks/useDateTimeFormat.ts @@ -3,6 +3,14 @@ import { useTranslation } from './translation'; export function useDateTimeFormat() { const { i18n } = useTranslation(); - return (date: Date | string | number, options?: Parameters[1]) => - Intl.DateTimeFormat(intlLocale(i18n.language), options).format(new Date(date)); + return ( + date: Date | string | number, + options?: Parameters[1] & { inputUnit?: 'seconds' | 'ms' } + ) => { + if (options?.inputUnit === 'seconds' && typeof date !== 'object') { + date = new Date(Number(date) * 1000); + } + + return Intl.DateTimeFormat(intlLocale(i18n.language), options).format(new Date(date)); + }; } diff --git a/packages/uikit/src/hooks/useDisclosure.ts b/packages/uikit/src/hooks/useDisclosure.ts new file mode 100644 index 000000000..9e83e6071 --- /dev/null +++ b/packages/uikit/src/hooks/useDisclosure.ts @@ -0,0 +1,10 @@ +import { useCallback, useState } from 'react'; + +export function useDisclosure(value = false) { + const [isOpen, setIsOpen] = useState(value); + const onClose = useCallback(() => setIsOpen(false), []); + const onOpen = useCallback(() => setIsOpen(true), []); + const onToggle = useCallback(() => setIsOpen(v => !v), []); + + return { isOpen, onClose, onOpen, onToggle }; +} diff --git a/packages/uikit/src/hooks/dashboard/dashboard-column.ts b/packages/uikit/src/state/dashboard/dashboard-column.ts similarity index 100% rename from packages/uikit/src/hooks/dashboard/dashboard-column.ts rename to packages/uikit/src/state/dashboard/dashboard-column.ts diff --git a/packages/uikit/src/hooks/dashboard/useDashboardColumns.ts b/packages/uikit/src/state/dashboard/useDashboardColumns.ts similarity index 96% rename from packages/uikit/src/hooks/dashboard/useDashboardColumns.ts rename to packages/uikit/src/state/dashboard/useDashboardColumns.ts index 27e1b5b1b..240cf6f4c 100644 --- a/packages/uikit/src/hooks/dashboard/useDashboardColumns.ts +++ b/packages/uikit/src/state/dashboard/useDashboardColumns.ts @@ -1,8 +1,8 @@ -import { useAppSdk } from '../appSdk'; +import { useAppSdk } from '../../hooks/appSdk'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { QueryKey } from '../../libs/queryKey'; import { DashboardColumn, isSupportedColumnType } from './dashboard-column'; -import { useTranslation } from '../translation'; +import { useTranslation } from '../../hooks/translation'; export type DashboardColumnsForm = { id: string; isEnabled: boolean }[]; @@ -26,7 +26,7 @@ export function useDashboardColumnsForm() { .map(c => ({ id: c.id, isEnabled: - stored.find(item => item.id === c.id)?.isEnabled || c.defaultIsChecked + stored.find(item => item.id === c.id)?.isEnabled ?? c.defaultIsChecked })); } return columns.map(c => ({ id: c.id, isEnabled: c.defaultIsChecked })); diff --git a/packages/uikit/src/hooks/dashboard/useDashboardData.ts b/packages/uikit/src/state/dashboard/useDashboardData.ts similarity index 97% rename from packages/uikit/src/hooks/dashboard/useDashboardData.ts rename to packages/uikit/src/state/dashboard/useDashboardData.ts index c46dbb360..fef864274 100644 --- a/packages/uikit/src/hooks/dashboard/useDashboardData.ts +++ b/packages/uikit/src/state/dashboard/useDashboardData.ts @@ -4,7 +4,7 @@ import { DashboardCell } from './dashboard-column'; import { useDashboardColumnsAsForm } from './useDashboardColumns'; import BigNumber from 'bignumber.js'; import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; -import { useAppContext } from '../appContext'; +import { useAppContext } from '../../hooks/appContext'; export function useDashboardData() { const { data: columns } = useDashboardColumnsAsForm(); diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index 1947fc44a..d37c5df8c 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -59,8 +59,8 @@ export const useProPlans = (promoCode?: string) => { const promo = useQuery( [QueryKey.pro, 'promo', wallet.lang, promoCode], - () => getProServiceTiers(wallet.lang, promoCode != '' ? promoCode : undefined), - { enabled: promoCode != '' } + () => getProServiceTiers(wallet.lang, promoCode !== '' ? promoCode : undefined), + { enabled: promoCode !== '' } ); return useMemo<[ProServiceTier[] | undefined, string | undefined]>(() => { @@ -72,7 +72,7 @@ export const useProPlans = (promoCode?: string) => { }, [all.data, promo.data]); }; -export const buyProServiceMutation = () => { +export const useBuyProServiceMutation = () => { const sdk = useAppSdk(); const { api } = useAppContext(); const client = useQueryClient(); From 568c0c734807e2ae8ab39cb19f2903cc6a35c082 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 1 Mar 2024 18:45:19 +0100 Subject: [PATCH 02/27] fix: tg oauth integrated --- apps/desktop/src/app/App.tsx | 6 +- apps/desktop/src/electron/mainWindow.ts | 32 +- apps/desktop/src/index.html | 579 +++++++++++++++++- apps/desktop/webpack.plugins.ts | 3 +- packages/core/src/service/proService.ts | 10 +- packages/core/src/service/telegram-oauth.ts | 69 +++ .../uikit/src/components/pro/ProBanner.tsx | 5 +- packages/uikit/src/hooks/appContext.ts | 5 + .../src/state/dashboard/useDashboardData.ts | 17 +- packages/uikit/src/state/pro.ts | 13 +- 10 files changed, 709 insertions(+), 30 deletions(-) create mode 100644 packages/core/src/service/telegram-oauth.ts diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 5801bb614..4207fd32d 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -104,6 +104,7 @@ const langs = 'en,zh_CN,ru,it,tr'; const listOfAuth: AuthState['kind'][] = ['keychain']; declare const REACT_APP_TONCONSOLE_API: string; +declare const REACT_APP_TG_BOT_ID: string; export const App = () => { const { t, i18n } = useTranslation(); @@ -233,7 +234,10 @@ export const Loader: FC = () => { standalone: false, extension: false, proFeatures: true, - ios: false + ios: false, + env: { + tgAuthBotId: REACT_APP_TG_BOT_ID + } }; return ( diff --git a/apps/desktop/src/electron/mainWindow.ts b/apps/desktop/src/electron/mainWindow.ts index 4441c6c6a..f8408d330 100644 --- a/apps/desktop/src/electron/mainWindow.ts +++ b/apps/desktop/src/electron/mainWindow.ts @@ -1,5 +1,5 @@ import { delay } from '@tonkeeper/core/dist/utils/common'; -import { BrowserWindow, ipcMain } from 'electron'; +import { BrowserWindow, ipcMain, shell } from 'electron'; import isDev from 'electron-is-dev'; import path from 'path'; import { Cookie, CookieJar } from 'tough-cookie'; @@ -20,7 +20,7 @@ export abstract class MainWindow { static mainWindow: BrowserWindow | undefined = undefined; static async openMainWindow() { - if (this.mainWindow != undefined) return this.mainWindow; + if (this.mainWindow !== undefined) return this.mainWindow; const icon = (() => { switch (process.platform) { @@ -41,7 +41,7 @@ export abstract class MainWindow { width: 1300, height: 700, resizable: isDev, - autoHideMenuBar: process.platform != 'darwin', + autoHideMenuBar: process.platform !== 'darwin', webPreferences: { zoomFactor: process.platform !== 'linux' ? 0.8 : undefined, preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY @@ -78,11 +78,20 @@ export abstract class MainWindow { } const result = cookies .map(cookie => `${cookie.key}=${cookie.value}`) - .join(', '); + .join('; '); + + /* patch tg auth headers */ + if (details.url === 'https://oauth.telegram.org/auth/get') { + details.requestHeaders.origin = 'https://tonkeeper.com'; + details.requestHeaders.referer = 'https://tonkeeper.com'; + } + callback({ - requestHeaders: Object.assign(details.requestHeaders, { + ...details, + requestHeaders: { + ...details.requestHeaders, cookie: result - }) + } }); }); } @@ -91,12 +100,21 @@ export abstract class MainWindow { this.mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => { const setCookie = details.responseHeaders['set-cookie'] ?? []; + /* patch tg auth headers cors */ + if (details.url === 'https://oauth.telegram.org/auth/get') { + const corsHeader = + Object.keys(details.responseHeaders).find( + k => k.toLowerCase() === 'access-control-allow-origin' + ) || 'access-control-allow-origin'; + details.responseHeaders[corsHeader] = ['*']; + } + Promise.all( setCookie.map(cookieRaw => cookieJar.setCookie(Cookie.parse(cookieRaw), details.url) ) ).finally(() => { - callback({}); + callback(details); }); }); diff --git a/apps/desktop/src/index.html b/apps/desktop/src/index.html index cb4735c4f..ae19f8277 100644 --- a/apps/desktop/src/index.html +++ b/apps/desktop/src/index.html @@ -9,9 +9,586 @@ rel="stylesheet" /> +
- \ No newline at end of file + diff --git a/apps/desktop/webpack.plugins.ts b/apps/desktop/webpack.plugins.ts index ebe2edcfd..cf7704055 100644 --- a/apps/desktop/webpack.plugins.ts +++ b/apps/desktop/webpack.plugins.ts @@ -13,6 +13,7 @@ export const plugins = [ }), new webpack.DefinePlugin({ REACT_APP_AMPLITUDE: JSON.stringify(process.env.REACT_APP_AMPLITUDE), - REACT_APP_TONCONSOLE_API: JSON.stringify(process.env.REACT_APP_TONCONSOLE_API) + REACT_APP_TONCONSOLE_API: JSON.stringify(process.env.REACT_APP_TONCONSOLE_API), + REACT_APP_TG_BOT_ID: JSON.stringify(process.env.REACT_APP_TG_BOT_ID) }) ]; diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index 301df02ff..3c5e7da7c 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -16,6 +16,7 @@ import { createTonProofItem, tonConnectProofPayload } from './tonConnect/connect import { estimateTonTransfer, sendTonTransfer } from './transfer/tonService'; import { walletStateInitFromState } from './wallet/contractService'; import { getWalletState } from './wallet/storeService'; +import { loginViaTG } from './telegram-oauth'; const getBackupState = async (storage: IStorage) => { const backup = await storage.get(AppKey.PRO_BACKUP); @@ -120,7 +121,7 @@ export const getProServiceTiers = async (lang?: Language | undefined, promoCode? }; export const createProServiceInvoice = async (tierId: number, promoCode?: string) => { - return await ProServiceService.createProServiceInvoice({ + return ProServiceService.createProServiceInvoice({ tier_id: tierId, promo_code: promoCode }); @@ -189,3 +190,10 @@ export const publishAndWaitProServiceInvoice = async ( } } while (updated.status === InvoiceStatus.PENDING); }; + +export async function startProServiceTrial(botId: string) { + const tgData = await loginViaTG(botId); + if (tgData) { + return ProServiceService.proServiceTrial(tgData); + } +} diff --git a/packages/core/src/service/telegram-oauth.ts b/packages/core/src/service/telegram-oauth.ts new file mode 100644 index 000000000..4f9188a1b --- /dev/null +++ b/packages/core/src/service/telegram-oauth.ts @@ -0,0 +1,69 @@ +export function getWindow(): Window | undefined { + if (typeof window !== 'undefined') { + return window; + } +} + +export function hasProperties( + value: unknown, + propertyKeys: T[] +): value is Record { + if (!value || typeof value !== 'object') { + return false; + } + + return propertyKeys.every(propertyKey => propertyKey in value); +} + +export function hasProperty( + value: unknown, + propertyKey: T +): value is Record { + return hasProperties(value, [propertyKey]); +} + +interface Options { + bot_id: string; + request_access?: string; + lang?: string; +} + +export interface TGLoginData { + auth_date: number; + first_name: string; + hash: string; + id: number; + last_name: string; + username: string; + + photo_url: string; +} + +type Callback = (dataOrFalse: TGLoginData | false) => void; + +function isTGAvailable(window: Window): window is Window & { + Telegram: { Login: { auth: (options: Options, callback: Callback) => void } }; +} { + return ( + hasProperty(window, 'Telegram') && + hasProperty(window.Telegram, 'Login') && + hasProperty(window.Telegram.Login, 'auth') && + typeof window.Telegram.Login.auth === 'function' + ); +} + +export async function loginViaTG(botId: string): Promise { + const window = getWindow(); + if (!window) { + return null; + } + + if (!isTGAvailable(window)) { + throw new Error('Telegram auth provider not found'); + } + return new Promise(res => { + window.Telegram.Login.auth({ bot_id: botId, request_access: 'write' }, data => { + res(data || null); + }); + }); +} diff --git a/packages/uikit/src/components/pro/ProBanner.tsx b/packages/uikit/src/components/pro/ProBanner.tsx index 4ec3417f0..279d5c8e0 100644 --- a/packages/uikit/src/components/pro/ProBanner.tsx +++ b/packages/uikit/src/components/pro/ProBanner.tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import styled from 'styled-components'; import { Body2, Label2 } from '../Text'; import { Button } from '../fields/Button'; -import { useProState } from '../../state/pro'; +import { useActivateTrialMutation, useProState } from '../../state/pro'; import { useDateTimeFormat } from '../../hooks/useDateTimeFormat'; import { ProNotification } from './ProNotification'; import { useDisclosure } from '../../hooks/useDisclosure'; @@ -30,6 +30,7 @@ const ButtonsContainerStyled = styled.div` export const ProBanner: FC<{ className?: string }> = ({ className }) => { const formatDate = useDateTimeFormat(); + const { mutate } = useActivateTrialMutation(); const { data } = useProState(); const { isOpen, onOpen, onClose } = useDisclosure(); @@ -64,7 +65,7 @@ export const ProBanner: FC<{ className?: string }> = ({ className }) => { ) : ( !used_trial && ( - ) diff --git a/packages/uikit/src/hooks/appContext.ts b/packages/uikit/src/hooks/appContext.ts index a7f23c972..1d2ee13df 100644 --- a/packages/uikit/src/hooks/appContext.ts +++ b/packages/uikit/src/hooks/appContext.ts @@ -11,6 +11,8 @@ import { } from '@tonkeeper/core/dist/tonkeeperApi/tonendpoint'; import { Configuration as TronConfiguration } from '@tonkeeper/core/dist/tronApi'; import React, { useContext } from 'react'; +import webpack from 'webpack'; +import Record = webpack.compilation.Record; export interface IAppContext { api: APIConfig; @@ -24,6 +26,9 @@ export interface IAppContext { ios: boolean; proFeatures: boolean; hideQrScanner?: boolean; + env?: { + tgAuthBotId: string; + }; } export const AppContext = React.createContext({ diff --git a/packages/uikit/src/state/dashboard/useDashboardData.ts b/packages/uikit/src/state/dashboard/useDashboardData.ts index fef864274..124298e00 100644 --- a/packages/uikit/src/state/dashboard/useDashboardData.ts +++ b/packages/uikit/src/state/dashboard/useDashboardData.ts @@ -17,22 +17,7 @@ export function useDashboardData() { const mockRow: DashboardCell[] = (selectedColumns?.map( c => mocks[c.id as keyof typeof mocks] ) || []) as DashboardCell[]; - return [ - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow, - mockRow - ]; + return [mockRow]; }, { enabled: !!selectedColumns diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index d37c5df8c..6de10d309 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -7,7 +7,8 @@ import { getProServiceTiers, getProState, logoutTonConsole, - publishAndWaitProServiceInvoice + publishAndWaitProServiceInvoice, + startProServiceTrial } from '@tonkeeper/core/dist/service/proService'; import { getWalletState } from '@tonkeeper/core/dist/service/wallet/storeService'; import { ProServiceTier } from '@tonkeeper/core/src/tonConsoleApi/models/ProServiceTier'; @@ -98,3 +99,13 @@ export const useBuyProServiceMutation = () => { } ); }; + +export const useActivateTrialMutation = () => { + const client = useQueryClient(); + const ctx = useAppContext(); + + return useMutation(async () => { + await startProServiceTrial((ctx.env as { tgAuthBotId: string }).tgAuthBotId); + await client.invalidateQueries([QueryKey.pro]); + }); +}; From 4e063d083f9d1fa8aecf6cc072ba45262c406bdb Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Tue, 5 Mar 2024 16:49:45 +0100 Subject: [PATCH 03/27] Fix rebase --- packages/uikit/src/hooks/appContext.ts | 2 -- packages/uikit/src/state/pro.ts | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/uikit/src/hooks/appContext.ts b/packages/uikit/src/hooks/appContext.ts index 1d2ee13df..bae524d79 100644 --- a/packages/uikit/src/hooks/appContext.ts +++ b/packages/uikit/src/hooks/appContext.ts @@ -11,8 +11,6 @@ import { } from '@tonkeeper/core/dist/tonkeeperApi/tonendpoint'; import { Configuration as TronConfiguration } from '@tonkeeper/core/dist/tronApi'; import React, { useContext } from 'react'; -import webpack from 'webpack'; -import Record = webpack.compilation.Record; export interface IAppContext { api: APIConfig; diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index 7e9fe986c..c463cc19c 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -12,6 +12,7 @@ import { getProState, logoutTonConsole, setBackupState, + startProServiceTrial, waitProServiceInvoice } from '@tonkeeper/core/dist/service/proService'; import { getWalletState } from '@tonkeeper/core/dist/service/wallet/storeService'; From 329637c4cd4452d6481eb327d0961909994650e8 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 7 Mar 2024 13:51:02 +0100 Subject: [PATCH 04/27] feat: dashboard api integrated --- .../src/entries/dashboard.ts} | 7 +- packages/core/src/service/proService.ts | 104 ++++++++- packages/core/src/tonConsoleApi/index.ts | 10 + .../tonConsoleApi/models/FiatCurrencies.ts | 8 + .../models/ProServiceDashboardCellAddress.ts | 12 ++ .../ProServiceDashboardCellNumericCrypto.ts | 14 ++ .../ProServiceDashboardCellNumericFiat.ts | 14 ++ .../models/ProServiceDashboardCellString.ts | 12 ++ .../models/ProServiceDashboardColumn.ts | 14 ++ .../models/ProServiceDashboardColumnID.ts | 14 ++ .../models/ProServiceDashboardColumnType.ts | 10 + .../models/ProServiceInvoiceWebhook.ts | 17 ++ .../src/tonConsoleApi/models/queryCurrency.ts | 9 + .../services/ProServiceService.ts | 69 +++++- packages/core/src/utils/types.ts | 5 + packages/locales/src/tonkeeper-web/en.json | 5 +- packages/locales/src/tonkeeper-web/ru-RU.json | 5 +- .../components/dashboard/CategoriesModal.tsx | 2 +- .../components/dashboard/DashboardTable.tsx | 2 +- .../dashboard/columns/DashboardCell.tsx | 2 +- .../state/dashboard/useDashboardColumns.ts | 115 +++++----- .../src/state/dashboard/useDashboardData.ts | 201 +++++++++++++----- 22 files changed, 516 insertions(+), 135 deletions(-) rename packages/{uikit/src/state/dashboard/dashboard-column.ts => core/src/entries/dashboard.ts} (89%) create mode 100644 packages/core/src/tonConsoleApi/models/FiatCurrencies.ts create mode 100644 packages/core/src/tonConsoleApi/models/ProServiceDashboardCellAddress.ts create mode 100644 packages/core/src/tonConsoleApi/models/ProServiceDashboardCellNumericCrypto.ts create mode 100644 packages/core/src/tonConsoleApi/models/ProServiceDashboardCellNumericFiat.ts create mode 100644 packages/core/src/tonConsoleApi/models/ProServiceDashboardCellString.ts create mode 100644 packages/core/src/tonConsoleApi/models/ProServiceDashboardColumn.ts create mode 100644 packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnID.ts create mode 100644 packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnType.ts create mode 100644 packages/core/src/tonConsoleApi/models/ProServiceInvoiceWebhook.ts create mode 100644 packages/core/src/tonConsoleApi/models/queryCurrency.ts create mode 100644 packages/core/src/utils/types.ts diff --git a/packages/uikit/src/state/dashboard/dashboard-column.ts b/packages/core/src/entries/dashboard.ts similarity index 89% rename from packages/uikit/src/state/dashboard/dashboard-column.ts rename to packages/core/src/entries/dashboard.ts index d8e5cb2cb..eac3c28a5 100644 --- a/packages/uikit/src/state/dashboard/dashboard-column.ts +++ b/packages/core/src/entries/dashboard.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js'; -import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; +import { FiatCurrencies } from './fiat'; export const columnsTypes = [ 'string', @@ -32,22 +32,26 @@ export type DashboardCell = | DashboardCellNumericFiat; export type DashboardCellString = { + columnId: string; type: 'string'; value: string; }; export type DashboardCellAddress = { + columnId: string; type: 'address'; raw: string; }; export type DashboardCellNumeric = { + columnId: string; type: 'numeric'; value: string; decimalPlaces?: number; }; export type DashboardCellNumericCrypto = { + columnId: string; type: 'numeric_crypto'; value: BigNumber; decimals: number; @@ -55,6 +59,7 @@ export type DashboardCellNumericCrypto = { }; export type DashboardCellNumericFiat = { + columnId: string; type: 'numeric_fiat'; value: BigNumber; fiat: FiatCurrencies; diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index 01b012a2c..bdb2d4453 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -11,12 +11,26 @@ import { ProState, ProStateSubscription } from '../entries/pro'; import { RecipientData, TonRecipientData } from '../entries/send'; import { WalletState } from '../entries/wallet'; import { AccountsApi } from '../tonApiV2'; -import { InvoiceStatus, InvoicesInvoice, Lang, ProServiceService } from '../tonConsoleApi'; +import { + InvoicesInvoice, + InvoiceStatus, + Lang, + ProServiceService, + FiatCurrencies as FiatCurrenciesGenerated, + ProServiceDashboardColumnType, + ProServiceDashboardCellString, + ProServiceDashboardCellAddress, + ProServiceDashboardCellNumericCrypto, + ProServiceDashboardCellNumericFiat +} from '../tonConsoleApi'; import { delay } from '../utils/common'; import { createTonProofItem, tonConnectProofPayload } from './tonConnect/connectService'; import { walletStateInitFromState } from './wallet/contractService'; import { getWalletState } from './wallet/storeService'; import { loginViaTG } from './telegram-oauth'; +import { DashboardCell, DashboardColumn } from '../entries/dashboard'; +import { FiatCurrencies } from '../entries/fiat'; +import { Flatten } from '../utils/types'; export const setBackupState = async (storage: IStorage, state: ProStateSubscription) => { await storage.set(AppKey.PRO_BACKUP, state); @@ -176,3 +190,91 @@ export async function startProServiceTrial(botId: string) { return ProServiceService.proServiceTrial(tgData); } } + +export async function getDashboardColumns(lang?: string): Promise { + if (!Object.values(Lang).includes(lang as Lang)) { + lang = Lang.EN; + } + + const result = await ProServiceService.proServiceDashboardColumns(lang as Lang); + return result.items.map(item => ({ + id: item.id, + name: item.name, + type: item.column_type, + defaultIsChecked: item.checked_default, + onlyPro: item.only_pro + })); +} + +export async function getDashboardData( + query: { + accounts: string[]; + columns: string[]; + }, + options?: { lang?: string; currency?: FiatCurrencies } +): Promise { + let lang = Lang.EN; + if (Object.values(Lang).includes(options?.lang as Lang)) { + lang = options?.lang as Lang; + } + + let currency = FiatCurrenciesGenerated.USD; + if ( + Object.values(FiatCurrenciesGenerated).includes( + options?.currency as FiatCurrenciesGenerated + ) + ) { + currency = options?.currency as FiatCurrenciesGenerated; + } + + const result = await ProServiceService.proServiceDashboardData(lang, currency, query); + return result.items.map(row => row.map(mapDtoCellToCell)); +} + +type DTOCell = Flatten< + Flatten>['items']> +>; + +function mapDtoCellToCell(dtoCell: DTOCell): DashboardCell { + switch (dtoCell.type) { + case ProServiceDashboardColumnType.STRING: { + const cell = dtoCell as ProServiceDashboardCellString; + + return { + columnId: cell.column_id, + type: 'string', + value: cell.value + }; + } + case ProServiceDashboardColumnType.ADDRESS: { + const cell = dtoCell as ProServiceDashboardCellAddress; + return { + columnId: cell.column_id, + type: 'address', + raw: cell.raw + }; + } + case ProServiceDashboardColumnType.NUMERIC_CRYPTO: { + const cell = dtoCell as ProServiceDashboardCellNumericCrypto; + return { + columnId: cell.column_id, + type: 'numeric_crypto', + value: new BigNumber(cell.value), + decimals: cell.decimals, + symbol: cell.symbol + }; + } + + case ProServiceDashboardColumnType.NUMERIC_FIAT: { + const cell = dtoCell as ProServiceDashboardCellNumericFiat; + return { + columnId: cell.column_id, + type: 'numeric_fiat', + value: new BigNumber(cell.value), + fiat: cell.fiat + }; + } + default: + throw new Error('Unsupported cell type'); + } +} diff --git a/packages/core/src/tonConsoleApi/index.ts b/packages/core/src/tonConsoleApi/index.ts index d33fd4705..0da33bcf1 100644 --- a/packages/core/src/tonConsoleApi/index.ts +++ b/packages/core/src/tonConsoleApi/index.ts @@ -17,6 +17,7 @@ export type { CnftCollection } from './models/CnftCollection'; export type { dashboardID } from './models/dashboardID'; export { Deposit } from './models/Deposit'; export { Error } from './models/Error'; +export { FiatCurrencies } from './models/FiatCurrencies'; export { InvoiceFieldOrder } from './models/InvoiceFieldOrder'; export type { invoiceID } from './models/invoiceID'; export type { InvoicesApp } from './models/InvoicesApp'; @@ -32,11 +33,20 @@ export type { projectID } from './models/projectID'; export type { ProjectTonApiToken } from './models/ProjectTonApiToken'; export type { promoCode } from './models/promoCode'; export type { ProServiceAppTier } from './models/ProServiceAppTier'; +export type { ProServiceDashboardCellAddress } from './models/ProServiceDashboardCellAddress'; +export type { ProServiceDashboardCellNumericCrypto } from './models/ProServiceDashboardCellNumericCrypto'; +export type { ProServiceDashboardCellNumericFiat } from './models/ProServiceDashboardCellNumericFiat'; +export type { ProServiceDashboardCellString } from './models/ProServiceDashboardCellString'; +export type { ProServiceDashboardColumn } from './models/ProServiceDashboardColumn'; +export { ProServiceDashboardColumnID } from './models/ProServiceDashboardColumnID'; +export { ProServiceDashboardColumnType } from './models/ProServiceDashboardColumnType'; +export type { ProServiceInvoiceWebhook } from './models/ProServiceInvoiceWebhook'; export type { ProServiceState } from './models/ProServiceState'; export type { ProServiceTier } from './models/ProServiceTier'; export type { queryAddresses } from './models/queryAddresses'; export type { queryAppID } from './models/queryAppID'; export type { queryChain } from './models/queryChain'; +export type { queryCurrency } from './models/queryCurrency'; export type { queryDetailed } from './models/queryDetailed'; export type { queryEndDate } from './models/queryEndDate'; export type { queryEndDateRequired } from './models/queryEndDateRequired'; diff --git a/packages/core/src/tonConsoleApi/models/FiatCurrencies.ts b/packages/core/src/tonConsoleApi/models/FiatCurrencies.ts new file mode 100644 index 000000000..61864cea0 --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/FiatCurrencies.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export enum FiatCurrencies { + USD = 'USD', + TON = 'TON', +} diff --git a/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellAddress.ts b/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellAddress.ts new file mode 100644 index 000000000..5292bc2be --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellAddress.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProServiceDashboardColumnID } from './ProServiceDashboardColumnID'; +import type { ProServiceDashboardColumnType } from './ProServiceDashboardColumnType'; +export type ProServiceDashboardCellAddress = { + column_id: ProServiceDashboardColumnID; + type: ProServiceDashboardColumnType; + raw: string; +}; + diff --git a/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellNumericCrypto.ts b/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellNumericCrypto.ts new file mode 100644 index 000000000..9820e08c7 --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellNumericCrypto.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProServiceDashboardColumnID } from './ProServiceDashboardColumnID'; +import type { ProServiceDashboardColumnType } from './ProServiceDashboardColumnType'; +export type ProServiceDashboardCellNumericCrypto = { + column_id: ProServiceDashboardColumnID; + type: ProServiceDashboardColumnType; + value: string; + decimals: number; + symbol: string; +}; + diff --git a/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellNumericFiat.ts b/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellNumericFiat.ts new file mode 100644 index 000000000..2c3135efc --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellNumericFiat.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { FiatCurrencies } from './FiatCurrencies'; +import type { ProServiceDashboardColumnID } from './ProServiceDashboardColumnID'; +import type { ProServiceDashboardColumnType } from './ProServiceDashboardColumnType'; +export type ProServiceDashboardCellNumericFiat = { + column_id: ProServiceDashboardColumnID; + type: ProServiceDashboardColumnType; + value: string; + fiat: FiatCurrencies; +}; + diff --git a/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellString.ts b/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellString.ts new file mode 100644 index 000000000..c0e17f94e --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/ProServiceDashboardCellString.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProServiceDashboardColumnID } from './ProServiceDashboardColumnID'; +import type { ProServiceDashboardColumnType } from './ProServiceDashboardColumnType'; +export type ProServiceDashboardCellString = { + column_id: ProServiceDashboardColumnID; + type: ProServiceDashboardColumnType; + value: string; +}; + diff --git a/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumn.ts b/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumn.ts new file mode 100644 index 000000000..b5f7d83ff --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumn.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProServiceDashboardColumnID } from './ProServiceDashboardColumnID'; +import type { ProServiceDashboardColumnType } from './ProServiceDashboardColumnType'; +export type ProServiceDashboardColumn = { + id: ProServiceDashboardColumnID; + name: string; + column_type: ProServiceDashboardColumnType; + checked_default: boolean; + only_pro: boolean; +}; + diff --git a/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnID.ts b/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnID.ts new file mode 100644 index 000000000..277d66fbd --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnID.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export enum ProServiceDashboardColumnID { + ADDRESS = 'address', + TOTAL_BALANCE = 'total_balance', + TOTAL_TON = 'total_ton', + KEY_STORAGE = 'key_storage', + SEND_CURRENT_MONTH = 'send_current_month', + SEND_LAST_MONTH = 'send_last_month', + RECEIVE_CURRENT_MONTH = 'receive_current_month', + RECEIVE_LAST_MONTH = 'receive_last_month', +} diff --git a/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnType.ts b/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnType.ts new file mode 100644 index 000000000..c816dd382 --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnType.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export enum ProServiceDashboardColumnType { + STRING = 'string', + ADDRESS = 'address', + NUMERIC_FIAT = 'numeric_fiat', + NUMERIC_CRYPTO = 'numeric_crypto', +} diff --git a/packages/core/src/tonConsoleApi/models/ProServiceInvoiceWebhook.ts b/packages/core/src/tonConsoleApi/models/ProServiceInvoiceWebhook.ts new file mode 100644 index 000000000..53a93b7bf --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/ProServiceInvoiceWebhook.ts @@ -0,0 +1,17 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { InvoiceStatus } from './InvoiceStatus'; +export type ProServiceInvoiceWebhook = { + id: string; + amount: string; + description: string; + status: InvoiceStatus; + pay_to_address: string; + paid_by_address?: string; + date_change: number; + date_expire: number; + date_create: number; +}; + diff --git a/packages/core/src/tonConsoleApi/models/queryCurrency.ts b/packages/core/src/tonConsoleApi/models/queryCurrency.ts new file mode 100644 index 000000000..a70f1b9f2 --- /dev/null +++ b/packages/core/src/tonConsoleApi/models/queryCurrency.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { FiatCurrencies } from './FiatCurrencies'; +/** + * Currency + */ +export type queryCurrency = FiatCurrencies; diff --git a/packages/core/src/tonConsoleApi/services/ProServiceService.ts b/packages/core/src/tonConsoleApi/services/ProServiceService.ts index 23d3c4255..8f22aeeb1 100644 --- a/packages/core/src/tonConsoleApi/services/ProServiceService.ts +++ b/packages/core/src/tonConsoleApi/services/ProServiceService.ts @@ -2,9 +2,16 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { FiatCurrencies } from '../models/FiatCurrencies'; import type { InvoicesInvoice } from '../models/InvoicesInvoice'; import type { Lang } from '../models/Lang'; import type { Ok } from '../models/Ok'; +import type { ProServiceDashboardCellAddress } from '../models/ProServiceDashboardCellAddress'; +import type { ProServiceDashboardCellNumericCrypto } from '../models/ProServiceDashboardCellNumericCrypto'; +import type { ProServiceDashboardCellNumericFiat } from '../models/ProServiceDashboardCellNumericFiat'; +import type { ProServiceDashboardCellString } from '../models/ProServiceDashboardCellString'; +import type { ProServiceDashboardColumn } from '../models/ProServiceDashboardColumn'; +import type { ProServiceInvoiceWebhook } from '../models/ProServiceInvoiceWebhook'; import type { ProServiceState } from '../models/ProServiceState'; import type { ProServiceTier } from '../models/ProServiceTier'; import type { TgAuth } from '../models/TgAuth'; @@ -175,7 +182,7 @@ export class ProServiceService { * @throws ApiError */ public static proServiceInvoiceWebhook( - requestBody?: InvoicesInvoice, + requestBody?: ProServiceInvoiceWebhook, ): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -234,6 +241,66 @@ export class ProServiceService { }, }); } + /** + * Get dashboard columns + * @param lang Lang + * @returns any Dashboard columns + * @throws ApiError + */ + public static proServiceDashboardColumns( + lang?: Lang, + ): CancelablePromise<{ + items: Array; + }> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v1/services/pro/dashboard/columns', + query: { + 'lang': lang, + }, + errors: { + 400: `Something went wrong on client side`, + 403: `Access token is missing or invalid`, + 404: `The specified resource was not found`, + 500: `Something went wrong on server side`, + }, + }); + } + /** + * Get dashboard data + * @param lang Lang + * @param currency Currency + * @param requestBody Data that is expected + * @returns any Dashboard data + * @throws ApiError + */ + public static proServiceDashboardData( + lang?: Lang, + currency?: FiatCurrencies, + requestBody?: { + accounts: Array; + columns: Array; + }, + ): CancelablePromise<{ + items: Array>; + }> { + return __request(OpenAPI, { + method: 'POST', + url: '/api/v1/services/pro/dashboard/data', + query: { + 'lang': lang, + 'currency': currency, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Something went wrong on client side`, + 403: `Access token is missing or invalid`, + 404: `The specified resource was not found`, + 500: `Something went wrong on server side`, + }, + }); + } /** * Get the state * @returns ProServiceState State diff --git a/packages/core/src/utils/types.ts b/packages/core/src/utils/types.ts new file mode 100644 index 000000000..01adf88f2 --- /dev/null +++ b/packages/core/src/utils/types.ts @@ -0,0 +1,5 @@ +export type Flatten = T extends (infer R)[] ? R : T; + +export function assertUnreachable(_: never): never { + throw new Error("Didn't expect to get here"); +} diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index 1e00a50fa..616d4a93b 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -63,5 +63,6 @@ "Unlock" : "Unlock", "wallet_address" : "Wallet address", "Wallet_name" : "Wallet name", - "wallet_sell" : "Sell" -} \ No newline at end of file + "wallet_sell" : "Sell", + "dashboard_column_name" : "Name" +} diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index 5eeff422b..82e0c838a 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -63,5 +63,6 @@ "Unlock" : "Разблокировать", "wallet_address" : "Адрес кошелька", "Wallet_name" : "Имя кошелька", - "wallet_sell" : "Продать" -} \ No newline at end of file + "wallet_sell" : "Продать", + "dashboard_column_name" : "Имя" +} diff --git a/packages/uikit/src/components/dashboard/CategoriesModal.tsx b/packages/uikit/src/components/dashboard/CategoriesModal.tsx index 9999bbf9e..8fffb8c8b 100644 --- a/packages/uikit/src/components/dashboard/CategoriesModal.tsx +++ b/packages/uikit/src/components/dashboard/CategoriesModal.tsx @@ -12,11 +12,11 @@ import { useDashboardColumnsForm } from '../../state/dashboard/useDashboardColumns'; import { Button } from '../fields/Button'; -import { DashboardColumn } from '../../state/dashboard/dashboard-column'; import { Badge } from '../shared'; import { useProState } from '../../state/pro'; import { ProNotification } from '../pro/ProNotification'; import { useDisclosure } from '../../hooks/useDisclosure'; +import { DashboardColumn } from "@tonkeeper/core/dist/entries/dashboard"; export const CategoriesModal: FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, diff --git a/packages/uikit/src/components/dashboard/DashboardTable.tsx b/packages/uikit/src/components/dashboard/DashboardTable.tsx index 198a7f1f2..655289db9 100644 --- a/packages/uikit/src/components/dashboard/DashboardTable.tsx +++ b/packages/uikit/src/components/dashboard/DashboardTable.tsx @@ -3,8 +3,8 @@ import { FC, useEffect, useRef, useState } from 'react'; import { Body2 } from '../Text'; import { useDashboardColumnsAsForm } from '../../state/dashboard/useDashboardColumns'; import { useDashboardData } from '../../state/dashboard/useDashboardData'; -import { DashboardCellAddress, DashboardColumnType } from '../../state/dashboard/dashboard-column'; import { DashboardCell } from './columns/DashboardCell'; +import { DashboardCellAddress, DashboardColumnType } from '@tonkeeper/core/dist/entries/dashboard'; const TableStyled = styled.table` width: 100%; diff --git a/packages/uikit/src/components/dashboard/columns/DashboardCell.tsx b/packages/uikit/src/components/dashboard/columns/DashboardCell.tsx index 01932c8fb..13d710a5e 100644 --- a/packages/uikit/src/components/dashboard/columns/DashboardCell.tsx +++ b/packages/uikit/src/components/dashboard/columns/DashboardCell.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { DashboardCell as DashboardCellProps } from '../../../state/dashboard/dashboard-column'; +import { DashboardCell as DashboardCellProps } from '@tonkeeper/core/dist/entries/dashboard'; import { StringCell } from './StringCell'; import { AddressCell } from './AddressCell'; import { Network } from '@tonkeeper/core/dist/entries/network'; diff --git a/packages/uikit/src/state/dashboard/useDashboardColumns.ts b/packages/uikit/src/state/dashboard/useDashboardColumns.ts index 240cf6f4c..12b74391f 100644 --- a/packages/uikit/src/state/dashboard/useDashboardColumns.ts +++ b/packages/uikit/src/state/dashboard/useDashboardColumns.ts @@ -1,14 +1,27 @@ import { useAppSdk } from '../../hooks/appSdk'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { QueryKey } from '../../libs/queryKey'; -import { DashboardColumn, isSupportedColumnType } from './dashboard-column'; import { useTranslation } from '../../hooks/translation'; +import { DashboardColumn, isSupportedColumnType } from '@tonkeeper/core/dist/entries/dashboard'; +import { getDashboardColumns } from '@tonkeeper/core/dist/service/proService'; +import { useProState } from '../pro'; export type DashboardColumnsForm = { id: string; isEnabled: boolean }[]; +export const ClientColumns: (Omit & { i18Key: string })[] = [ + { + type: 'string', + i18Key: 'dashboard_column_name', + id: 'name', + defaultIsChecked: true, + onlyPro: false + } +]; + export function useDashboardColumnsForm() { const client = useQueryClient(); const { storage } = useAppSdk(); + const { data: proState } = useProState(); const { data: columns } = useDashboardColumns(); @@ -21,18 +34,39 @@ export function useDashboardColumnsForm() { const stored = await storage.get('dashboard-columns'); if (stored) { - return columns - .filter(c => isSupportedColumnType(c.type)) + const storedList = stored + .map(storedColumn => { + const columnInfo = columns.find(column => column.id === storedColumn.id); + if (!columnInfo || !isSupportedColumnType(columnInfo.type)) { + return null; + } + + return { + id: storedColumn.id, + isEnabled: + columnInfo.onlyPro && !proState?.subscription.valid + ? false + : storedColumn.isEnabled ?? columnInfo.defaultIsChecked + }; + }) + .filter(Boolean) as DashboardColumnsForm; + + const newColumns = columns + .filter(c => stored.every(s => s.id !== c.id)) .map(c => ({ id: c.id, isEnabled: - stored.find(item => item.id === c.id)?.isEnabled ?? c.defaultIsChecked + c.onlyPro && !proState?.subscription.valid ? false : c.defaultIsChecked })); + return storedList.concat(newColumns); } - return columns.map(c => ({ id: c.id, isEnabled: c.defaultIsChecked })); + return columns.map(c => ({ + id: c.id, + isEnabled: c.onlyPro && !proState?.subscription.valid ? false : c.defaultIsChecked + })); }, { - enabled: !!columns + enabled: !!columns && !!proState } ); @@ -72,68 +106,17 @@ export function useDashboardColumnsAsForm() { export function useDashboardColumns() { const { - i18n: { language } + i18n: { language }, + t } = useTranslation(); return useQuery([QueryKey.dashboardColumns, language], async () => { - const response = [ - { - id: 'address', - name: 'Address', - type: 'address' as const, - defaultIsChecked: true, - onlyPro: false - }, - { - id: 'total', - name: 'Total', - type: 'numeric_fiat' as const, - defaultIsChecked: true, - onlyPro: false - }, - { - id: 'balance_ton', - name: 'Balance TON', - type: 'numeric_crypto' as const, - defaultIsChecked: true, - onlyPro: false - }, - { - id: 'storage', - name: 'Storage', - type: 'string' as const, - defaultIsChecked: true, - onlyPro: false - }, - { - id: 'send_current', - name: 'Send current month', - type: 'numeric_crypto' as const, - defaultIsChecked: false, - onlyPro: true - }, - { - id: 'send_prev', - name: 'Send previous month', - type: 'numeric_crypto' as const, - defaultIsChecked: false, - onlyPro: true - }, - { - id: 'received_current', - name: 'Received current month', - type: 'numeric_crypto' as const, - defaultIsChecked: false, - onlyPro: true - }, - { - id: 'received_prev', - name: 'Received previous month', - type: 'numeric_crypto' as const, - defaultIsChecked: true, - onlyPro: true - } - ]; + const response = await getDashboardColumns(language); + + const clientColumns: DashboardColumn[] = ClientColumns.map(c => ({ + ...c, + name: t(c.i18Key) + })); - return response.filter(column => isSupportedColumnType(column.type)); + return clientColumns.concat(response.filter(column => isSupportedColumnType(column.type))); }); } diff --git a/packages/uikit/src/state/dashboard/useDashboardData.ts b/packages/uikit/src/state/dashboard/useDashboardData.ts index 124298e00..501dd5905 100644 --- a/packages/uikit/src/state/dashboard/useDashboardData.ts +++ b/packages/uikit/src/state/dashboard/useDashboardData.ts @@ -1,72 +1,155 @@ -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { QueryKey } from '../../libs/queryKey'; -import { DashboardCell } from './dashboard-column'; -import { useDashboardColumnsAsForm } from './useDashboardColumns'; -import BigNumber from 'bignumber.js'; -import { FiatCurrencies } from '@tonkeeper/core/dist/entries/fiat'; +import { ClientColumns, useDashboardColumnsAsForm } from './useDashboardColumns'; import { useAppContext } from '../../hooks/appContext'; +import { DashboardCell } from '@tonkeeper/core/dist/entries/dashboard'; +import { getDashboardData } from '@tonkeeper/core/dist/service/proService'; +import { useTranslation } from '../../hooks/translation'; +import { useAppSdk } from '../../hooks/appSdk'; +import { getWalletState } from '@tonkeeper/core/dist/service/wallet/storeService'; +import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { Address } from 'ton-core'; export function useDashboardData() { const { data: columns } = useDashboardColumnsAsForm(); const selectedColumns = columns?.filter(c => c.isEnabled); - const { fiat } = useAppContext(); + const { fiat, account } = useAppContext(); + const { + i18n: { language }, + t + } = useTranslation(); + const selectedColIds = selectedColumns?.map(c => c.id); + const publicKeys = account?.publicKeys; + const { storage } = useAppSdk(); + const client = useQueryClient(); return useQuery( - [QueryKey.dashboardData, selectedColumns, fiat], - async () => { - const mockRow: DashboardCell[] = (selectedColumns?.map( - c => mocks[c.id as keyof typeof mocks] - ) || []) as DashboardCell[]; - return [mockRow]; + [QueryKey.dashboardData, selectedColIds, publicKeys, fiat, language], + async ctx => { + if (!selectedColIds?.length || !publicKeys?.length) { + return []; + } + + const wallets = await Promise.all(publicKeys.map(pk => getWalletState(storage, pk))); + const accounts = wallets.map(acc => acc!.active.friendlyAddress); + + const loadData = async (query: { columns: string[]; accounts: string[] }) => { + const queryToFetch = { + columns: query.columns.filter(col => !ClientColumns.some(c => c.id === col)), + accounts: query.accounts + }; + + let fetchResult: DashboardCell[][] = query.accounts.map(() => []); + if (queryToFetch.columns.length > 0) { + fetchResult = await getDashboardData(queryToFetch, { + currency: fiat, + lang: language + }); + } + + /* append client columns */ + const defaultWalletName = t('wallet_title'); + const result: DashboardCell[][] = query.accounts.map(() => []); + query.accounts.forEach((walletAddress, rowIndex) => { + const wallet = wallets.find(w => + Address.parse(w!.active.friendlyAddress).equals( + Address.parse(walletAddress) + ) + ); + query.columns.forEach((col, colIndex) => { + const ClientColumnName = ClientColumns.find(c => c.id === 'name')!; + if (col === ClientColumnName.id) { + result[rowIndex][colIndex] = { + columnId: ClientColumnName.id, + type: 'string', + value: wallet?.name || defaultWalletName + }; + return; + } + + result[rowIndex][colIndex] = + fetchResult[rowIndex][queryToFetch.columns.indexOf(col)]; + }); + }); + /* append client columns */ + + return result; + }; + + const pastQueries = client.getQueriesData({ + predicate: q => + q.queryKey[0] === QueryKey.dashboardData && + !!q.queryKey[1] && + !!q.queryKey[2] && + q.queryKey[3] === ctx.queryKey[3] && + q.queryKey[4] === ctx.queryKey[4], + fetchStatus: 'idle' + }); + + /* cache */ + if (pastQueries?.length) { + const walletsToQuerySet = new Set(); + const columnsToQuerySet = new Set(); + + const result: (DashboardCell | null)[][] = publicKeys.map(() => []); + publicKeys.forEach((pk, walletIndex) => { + selectedColIds.forEach((col, colIndex) => { + const matchingQueries = pastQueries.filter( + ([key, _]) => + (key[2] as string[] | undefined)?.includes(pk) && + (key[1] as string[] | undefined)?.includes(col) + ); + + if (!matchingQueries.length) { + result[walletIndex][colIndex] = null; + walletsToQuerySet.add(wallets[walletIndex]!); + columnsToQuerySet.add(col); + return; + } + + const [actualQueryKey, actualQueryValue] = + matchingQueries[matchingQueries.length - 1]; + const actualQueryWalletIndex = (actualQueryKey[2] as string[]).indexOf(pk); + const actualQueryColIndex = (actualQueryKey[1] as string[]).indexOf(col); + + result[walletIndex][colIndex] = (actualQueryValue as DashboardCell[][])[ + actualQueryWalletIndex + ][actualQueryColIndex]; + }); + }); + + const walletsToQuery = [...walletsToQuerySet.values()]; + let accountsToQuery = walletsToQuery.map(acc => acc.active.friendlyAddress); + if (columnsToQuerySet.size > 0) { + accountsToQuery = accounts; + } + const columnsToQuery = [...columnsToQuerySet.values()]; + + if (!accountsToQuery.length || !columnsToQuery.length) { + return result as DashboardCell[][]; + } + + const newData = await loadData({ + accounts: accountsToQuery, + columns: columnsToQuery + }); + + newData.forEach((row, rowIndex) => { + const walletIndex = publicKeys.indexOf(walletsToQuery[rowIndex].publicKey); + row.forEach(cell => { + const colIndex = selectedColIds.indexOf(cell.columnId); + result[walletIndex][colIndex] = cell; + }); + }); + + return result as DashboardCell[][]; + } + /* cache */ + + return loadData({ accounts, columns: selectedColIds }); }, { - enabled: !!selectedColumns + enabled: !!selectedColIds && !!publicKeys } ); } - -const mocks = { - address: { - type: 'address', - raw: '0:' + '0'.repeat(64) - }, - total: { - type: 'numeric_fiat', - value: new BigNumber(1234), - fiat: FiatCurrencies.USD - }, - balance_ton: { - type: 'numeric_crypto', - value: new BigNumber(12345678909876), - decimals: 9, - symbol: 'TON' - }, - storage: { - type: 'string', - value: 'keychain' - }, - send_current: { - type: 'numeric_crypto', - value: new BigNumber(123456), - decimals: 9, - symbol: 'TON' - }, - send_prev: { - type: 'numeric_crypto', - value: new BigNumber(1234111111), - decimals: 9, - symbol: 'TON' - }, - received_current: { - type: 'numeric_crypto', - value: new BigNumber(333333323456), - decimals: 9, - symbol: 'TON' - }, - received_prev: { - type: 'numeric_crypto', - value: new BigNumber(111111111123456), - decimals: 9, - symbol: 'TON' - } -}; From cb42b79f46daad2f9bed48eb1ae19ce2b2b307fe Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 7 Mar 2024 14:32:49 +0100 Subject: [PATCH 05/27] fix: telegram-widget script moved to a separate file --- apps/desktop/.env.example | 3 + apps/desktop/src/index.html | 578 ------------------- apps/desktop/src/renderer.ts | 1 + apps/desktop/src/telegram-widget.js | 580 ++++++++++++++++++++ apps/desktop/webpack.plugins.ts | 3 +- packages/core/src/service/proService.ts | 4 +- packages/core/src/service/telegram-oauth.ts | 4 +- packages/uikit/src/state/pro.ts | 16 +- 8 files changed, 600 insertions(+), 589 deletions(-) create mode 100644 apps/desktop/.env.example create mode 100644 apps/desktop/src/telegram-widget.js diff --git a/apps/desktop/.env.example b/apps/desktop/.env.example new file mode 100644 index 000000000..a3aa5d926 --- /dev/null +++ b/apps/desktop/.env.example @@ -0,0 +1,3 @@ +REACT_APP_TONCONSOLE_API=https://dev-pro.tonconsole.com +REACT_APP_TG_BOT_ID=6345183204 +REACT_APP_TG_BOT_ORIGIN=https://tonkeeper.com diff --git a/apps/desktop/src/index.html b/apps/desktop/src/index.html index ae19f8277..fe5df886e 100644 --- a/apps/desktop/src/index.html +++ b/apps/desktop/src/index.html @@ -8,584 +8,6 @@ href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap" rel="stylesheet" /> - - diff --git a/apps/desktop/src/renderer.ts b/apps/desktop/src/renderer.ts index a1802c880..ceb5f662e 100644 --- a/apps/desktop/src/renderer.ts +++ b/apps/desktop/src/renderer.ts @@ -36,4 +36,5 @@ log.info('UI Start-up'); Object.assign(console, log.functions); console.log('👋 This message is being logged by "renderer.js", included via webpack'); +import './telegram-widget'; import './react'; diff --git a/apps/desktop/src/telegram-widget.js b/apps/desktop/src/telegram-widget.js new file mode 100644 index 000000000..796f76e24 --- /dev/null +++ b/apps/desktop/src/telegram-widget.js @@ -0,0 +1,580 @@ +/* eslint-disable */ +/* patched version of 'https://telegram.org/js/telegram-widget.js?22' */ +/* changes: set iframe display to 'none' on line 342, change origin on 521-522 lines and `widgetsOrigin` on 576 line */ + +(function(window) { + (function(window){ + window.__parseFunction = function(__func, __attrs) { + __attrs = __attrs || []; + __func = '(function(' + __attrs.join(',') + '){' + __func + '})'; + return window.execScript ? window.execScript(__func) : eval(__func); + } + }(window)); + (function(window){ + + function addEvent(el, event, handler) { + var events = event.split(/\s+/); + for (var i = 0; i < events.length; i++) { + if (el.addEventListener) { + el.addEventListener(events[i], handler); + } else { + el.attachEvent('on' + events[i], handler); + } + } + } + function removeEvent(el, event, handler) { + var events = event.split(/\s+/); + for (var i = 0; i < events.length; i++) { + if (el.removeEventListener) { + el.removeEventListener(events[i], handler); + } else { + el.detachEvent('on' + events[i], handler); + } + } + } + function getCssProperty(el, prop) { + if (window.getComputedStyle) { + return window.getComputedStyle(el, '').getPropertyValue(prop) || null; + } else if (el.currentStyle) { + return el.currentStyle[prop] || null; + } + return null; + } + function geById(el_or_id) { + if (typeof el_or_id == 'string' || el_or_id instanceof String) { + return document.getElementById(el_or_id); + } else if (el_or_id instanceof HTMLElement) { + return el_or_id; + } + return null; + } + + var getWidgetsOrigin = function(default_origin, dev_origin) { + var link = document.createElement('A'), origin; + link.href = document.currentScript && document.currentScript.src || default_origin; + origin = link.origin || link.protocol + '//' + link.hostname; + if (origin == 'https://telegram.org') { + origin = default_origin; + } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') { + origin = dev_origin; + } + return origin; + }; + + var getPageCanonical = function() { + var a = document.createElement('A'), link, href; + if (document.querySelector) { + link = document.querySelector('link[rel="canonical"]'); + if (link && (href = link.getAttribute('href'))) { + a.href = href; + return a.href; + } + } else { + var links = document.getElementsByTagName('LINK'); + for (var i = 0; i < links.length; i++) { + if ((link = links[i]) && + (link.getAttribute('rel') == 'canonical') && + (href = link.getAttribute('href'))) { + a.href = href; + return a.href; + } + } + } + return false; + }; + + function haveTgAuthResult() { + var locationHash = '', re = /[#\?\&]tgAuthResult=([A-Za-z0-9\-_=]*)$/, match; + try { + locationHash = location.hash.toString(); + if (match = locationHash.match(re)) { + location.hash = locationHash.replace(re, ''); + var data = match[1] || ''; + data = data.replace(/-/g, '+').replace(/_/g, '/'); + var pad = data.length % 4; + if (pad > 1) { + data += new Array(5 - pad).join('='); + } + return JSON.parse(window.atob(data)); + } + } catch (e) {} + return false; + } + + function getXHR() { + if (navigator.appName == "Microsoft Internet Explorer"){ + return new ActiveXObject("Microsoft.XMLHTTP"); + } else { + return new XMLHttpRequest(); + } + } + + if (!window.Telegram) { + window.Telegram = {}; + } + if (!window.Telegram.__WidgetUuid) { + window.Telegram.__WidgetUuid = 0; + } + if (!window.Telegram.__WidgetLastId) { + window.Telegram.__WidgetLastId = 0; + } + if (!window.Telegram.__WidgetCallbacks) { + window.Telegram.__WidgetCallbacks = {}; + } + + function postMessageToIframe(iframe, event, data, callback) { + if (!iframe._ready) { + if (!iframe._readyQueue) iframe._readyQueue = []; + iframe._readyQueue.push([event, data, callback]); + return; + } + try { + data = data || {}; + data.event = event; + if (callback) { + data._cb = ++window.Telegram.__WidgetLastId; + window.Telegram.__WidgetCallbacks[data._cb] = { + iframe: iframe, + callback: callback + }; + } + iframe.contentWindow.postMessage(JSON.stringify(data), '*'); + } catch(e) {} + } + + function initWidget(widgetEl) { + var widgetId, widgetElId, widgetsOrigin, existsEl, + src, styles = {}, allowedAttrs = [], + defWidth, defHeight, scrollable = false, onInitAuthUser, onAuthUser, onUnauth; + if (!widgetEl.tagName || + !(widgetEl.tagName.toUpperCase() == 'SCRIPT' || + widgetEl.tagName.toUpperCase() == 'BLOCKQUOTE' && + widgetEl.classList.contains('telegram-post'))) { + return null; + } + if (widgetEl._iframe) { + return widgetEl._iframe; + } + if (widgetId = widgetEl.getAttribute('data-telegram-post')) { + var comment = widgetEl.getAttribute('data-comment') || ''; + widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); + widgetElId = 'telegram-post-' + widgetId.replace(/[^a-z0-9_]/ig, '-') + (comment ? '-comment' + comment : ''); + src = widgetsOrigin + '/' + widgetId + '?embed=1'; + allowedAttrs = ['comment', 'userpic', 'mode', 'single?', 'color', 'dark', 'dark_color']; + defWidth = widgetEl.getAttribute('data-width') || '100%'; + defHeight = ''; + styles.minWidth = '320px'; + } + else if (widgetId = widgetEl.getAttribute('data-telegram-discussion')) { + widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); + widgetElId = 'telegram-discussion-' + widgetId.replace(/[^a-z0-9_]/ig, '-') + '-' + (++window.Telegram.__WidgetUuid); + var websitePageUrl = widgetEl.getAttribute('data-page-url'); + if (!websitePageUrl) { + websitePageUrl = getPageCanonical(); + } + src = widgetsOrigin + '/' + widgetId + '?embed=1&discussion=1' + (websitePageUrl ? '&page_url=' + encodeURIComponent(websitePageUrl) : ''); + allowedAttrs = ['comments_limit', 'color', 'colorful', 'dark', 'dark_color', 'width', 'height']; + defWidth = widgetEl.getAttribute('data-width') || '100%'; + defHeight = widgetEl.getAttribute('data-height') || 0; + styles.minWidth = '320px'; + if (defHeight > 0) { + scrollable = true; + } + } + else if (widgetEl.hasAttribute('data-telegram-login')) { + widgetId = widgetEl.getAttribute('data-telegram-login'); + widgetsOrigin = getWidgetsOrigin('https://oauth.telegram.org', 'https://oauth.tg.dev'); + widgetElId = 'telegram-login-' + widgetId.replace(/[^a-z0-9_]/ig, '-'); + src = widgetsOrigin + '/embed/' + widgetId + '?origin=' + encodeURIComponent(location.origin || location.protocol + '//' + location.hostname) + '&return_to=' + encodeURIComponent(location.href); + allowedAttrs = ['size', 'userpic', 'init_auth', 'request_access', 'radius', 'min_width', 'max_width', 'lang']; + defWidth = 186; + defHeight = 28; + if (widgetEl.hasAttribute('data-size')) { + var size = widgetEl.getAttribute('data-size'); + if (size == 'small') defWidth = 148, defHeight = 20; + else if (size == 'large') defWidth = 238, defHeight = 40; + } + if (widgetEl.hasAttribute('data-onauth')) { + onInitAuthUser = onAuthUser = __parseFunction(widgetEl.getAttribute('data-onauth'), ['user']); + } + else if (widgetEl.hasAttribute('data-auth-url')) { + var a = document.createElement('A'); + a.href = widgetEl.getAttribute('data-auth-url'); + onAuthUser = function(user) { + var authUrl = a.href; + authUrl += (authUrl.indexOf('?') >= 0) ? '&' : '?'; + var params = []; + for (var key in user) { + params.push(key + '=' + encodeURIComponent(user[key])); + } + authUrl += params.join('&'); + location.href = authUrl; + }; + } + if (widgetEl.hasAttribute('data-onunauth')) { + onUnauth = __parseFunction(widgetEl.getAttribute('data-onunauth')); + } + var auth_result = haveTgAuthResult(); + if (auth_result && onAuthUser) { + onAuthUser(auth_result); + } + } + else if (widgetId = widgetEl.getAttribute('data-telegram-share-url')) { + widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); + widgetElId = 'telegram-share-' + window.btoa(widgetId); + src = widgetsOrigin + '/share/embed?origin=' + encodeURIComponent(location.origin || location.protocol + '//' + location.hostname); + allowedAttrs = ['telegram-share-url', 'comment', 'size', 'text']; + defWidth = 60; + defHeight = 20; + if (widgetEl.getAttribute('data-size') == 'large') { + defWidth = 76; + defHeight = 28; + } + } + else { + return null; + } + existsEl = document.getElementById(widgetElId); + if (existsEl) { + return existsEl; + } + for (var i = 0; i < allowedAttrs.length; i++) { + var attr = allowedAttrs[i]; + var novalue = attr.substr(-1) == '?'; + if (novalue) { + attr = attr.slice(0, -1); + } + var data_attr = 'data-' + attr.replace(/_/g, '-'); + if (widgetEl.hasAttribute(data_attr)) { + var attr_value = novalue ? '1' : encodeURIComponent(widgetEl.getAttribute(data_attr)); + src += '&' + attr + '=' + attr_value; + } + } + function getCurCoords(iframe) { + var docEl = document.documentElement; + var frect = iframe.getBoundingClientRect(); + return { + frameTop: frect.top, + frameBottom: frect.bottom, + frameLeft: frect.left, + frameRight: frect.right, + frameWidth: frect.width, + frameHeight: frect.height, + scrollTop: window.pageYOffset, + scrollLeft: window.pageXOffset, + clientWidth: docEl.clientWidth, + clientHeight: docEl.clientHeight + }; + } + function visibilityHandler() { + if (isVisible(iframe, 50)) { + postMessageToIframe(iframe, 'visible', {frame: widgetElId}); + } + } + function focusHandler() { + postMessageToIframe(iframe, 'focus', {has_focus: document.hasFocus()}); + } + function postMessageHandler(event) { + if (event.source !== iframe.contentWindow || + event.origin != widgetsOrigin) { + return; + } + try { + var data = JSON.parse(event.data); + } catch(e) { + var data = {}; + } + if (data.event == 'resize') { + if (data.height) { + iframe.style.height = data.height + 'px'; + } + if (data.width) { + iframe.style.width = data.width + 'px'; + } + } + else if (data.event == 'ready') { + iframe._ready = true; + focusHandler(); + for (var i = 0; i < iframe._readyQueue.length; i++) { + var queue_item = iframe._readyQueue[i]; + postMessageToIframe(iframe, queue_item[0], queue_item[1], queue_item[2]); + } + iframe._readyQueue = []; + } + else if (data.event == 'visible_off') { + removeEvent(window, 'scroll', visibilityHandler); + removeEvent(window, 'resize', visibilityHandler); + } + else if (data.event == 'get_coords') { + postMessageToIframe(iframe, 'callback', { + _cb: data._cb, + value: getCurCoords(iframe) + }); + } + else if (data.event == 'scroll_to') { + try { + window.scrollTo(data.x || 0, data.y || 0); + } catch(e) {} + } + else if (data.event == 'auth_user') { + if (data.init) { + onInitAuthUser && onInitAuthUser(data.auth_data); + } else { + onAuthUser && onAuthUser(data.auth_data); + } + } + else if (data.event == 'unauthorized') { + onUnauth && onUnauth(); + } + else if (data.event == 'callback') { + var cb_data = null; + if (cb_data = window.Telegram.__WidgetCallbacks[data._cb]) { + if (cb_data.iframe === iframe) { + cb_data.callback(data.value); + delete window.Telegram.__WidgetCallbacks[data._cb]; + } + } else { + console.warn('Callback #' + data._cb + ' not found'); + } + } + } + var iframe = document.createElement('iframe'); +/* PATCHED */ iframe.style.display = 'none'; + iframe.id = widgetElId; + iframe.src = src; + iframe.width = defWidth; + iframe.height = defHeight; + iframe.setAttribute('frameborder', '0'); + if (!scrollable) { + iframe.setAttribute('scrolling', 'no'); + iframe.style.overflow = 'hidden'; + } + iframe.style.colorScheme = 'light dark'; + iframe.style.border = 'none'; + for (var prop in styles) { + iframe.style[prop] = styles[prop]; + } + if (widgetEl.parentNode) { + widgetEl.parentNode.insertBefore(iframe, widgetEl); + if (widgetEl.tagName.toUpperCase() == 'BLOCKQUOTE') { + widgetEl.parentNode.removeChild(widgetEl); + } + } + iframe._ready = false; + iframe._readyQueue = []; + widgetEl._iframe = iframe; + addEvent(iframe, 'load', function() { + removeEvent(iframe, 'load', visibilityHandler); + addEvent(window, 'scroll', visibilityHandler); + addEvent(window, 'resize', visibilityHandler); + visibilityHandler(); + }); + addEvent(window, 'focus blur', focusHandler); + addEvent(window, 'message', postMessageHandler); + return iframe; + } + function isVisible(el, padding) { + var node = el, val; + var visibility = getCssProperty(node, 'visibility'); + if (visibility == 'hidden') return false; + while (node) { + if (node === document.documentElement) break; + var display = getCssProperty(node, 'display'); + if (display == 'none') return false; + var opacity = getCssProperty(node, 'opacity'); + if (opacity !== null && opacity < 0.1) return false; + node = node.parentNode; + } + if (el.getBoundingClientRect) { + padding = +padding || 0; + var rect = el.getBoundingClientRect(); + var html = document.documentElement; + if (rect.bottom < padding || + rect.right < padding || + rect.top > (window.innerHeight || html.clientHeight) - padding || + rect.left > (window.innerWidth || html.clientWidth) - padding) { + return false; + } + } + return true; + } + + function getAllWidgets() { + var widgets = []; + if (document.querySelectorAll) { + widgets = document.querySelectorAll('script[data-telegram-post],blockquote.telegram-post,script[data-telegram-discussion],script[data-telegram-login],script[data-telegram-share-url]'); + } else { + widgets = Array.prototype.slice.apply(document.getElementsByTagName('SCRIPT')); + widgets = widgets.concat(Array.prototype.slice.apply(document.getElementsByTagName('BLOCKQUOTE'))); + } + return widgets; + } + + function getWidgetInfo(el_or_id, callback) { + var e = null, iframe = null; + if (el = geById(el_or_id)) { + if (el.tagName && + el.tagName.toUpperCase() == 'IFRAME') { + iframe = el; + } else if (el._iframe) { + iframe = el._iframe; + } + if (iframe && callback) { + postMessageToIframe(iframe, 'get_info', {}, callback); + } + } + } + + function setWidgetOptions(options, el_or_id) { + var e = null, iframe = null; + if (typeof el_or_id === 'undefined') { + var widgets = getAllWidgets(); + for (var i = 0; i < widgets.length; i++) { + if (iframe = widgets[i]._iframe) { + postMessageToIframe(iframe, 'set_options', {options: options}); + } + } + } else { + if (el = geById(el_or_id)) { + if (el.tagName && + el.tagName.toUpperCase() == 'IFRAME') { + iframe = el; + } else if (el._iframe) { + iframe = el._iframe; + } + if (iframe) { + postMessageToIframe(iframe, 'set_options', {options: options}); + } + } + } + } + + if (!document.currentScript || + !initWidget(document.currentScript)) { + var widgets = getAllWidgets(); + for (var i = 0; i < widgets.length; i++) { + initWidget(widgets[i]); + } + } + + var TelegramLogin = { + popups: {}, + options: null, + auth_callback: null, + _init: function(options, auth_callback) { + TelegramLogin.options = options; + TelegramLogin.auth_callback = auth_callback; + var auth_result = haveTgAuthResult(); + if (auth_result && auth_callback) { + auth_callback(auth_result); + } + }, + _open: function(callback) { + TelegramLogin._auth(TelegramLogin.options, function(authData) { + if (TelegramLogin.auth_callback) { + TelegramLogin.auth_callback(authData); + } + if (callback) { + callback(authData); + } + }); + }, + _auth: function(options, callback) { + var bot_id = parseInt(options.bot_id); + if (!bot_id) { + throw new Error('Bot id required'); + } + var width = 550; + var height = 470; + var left = Math.max(0, (screen.width - width) / 2) + (screen.availLeft | 0), + top = Math.max(0, (screen.height - height) / 2) + (screen.availTop | 0); + var onMessage = function (event) { + try { + var data = JSON.parse(event.data); + } catch(e) { + var data = {}; + } + if (!TelegramLogin.popups[bot_id]) return; + if (event.source !== TelegramLogin.popups[bot_id].window) return; + if (data.event == 'auth_result') { + onAuthDone(data.result); + } + }; + var onAuthDone = function (authData) { + if (!TelegramLogin.popups[bot_id]) return; + if (TelegramLogin.popups[bot_id].authFinished) return; + callback && callback(authData); + TelegramLogin.popups[bot_id].authFinished = true; + removeEvent(window, 'message', onMessage); + }; + var checkClose = function(bot_id) { + if (!TelegramLogin.popups[bot_id]) return; + if (!TelegramLogin.popups[bot_id].window || + TelegramLogin.popups[bot_id].window.closed) { + return TelegramLogin.getAuthData(options, function(origin, authData) { + onAuthDone(authData); + }); + } + setTimeout(checkClose, 100, bot_id); + } + +/* PATCHED */ const origin = REACT_APP_TG_BOT_ORIGIN; +/* PATCHED */ var popup_url = Telegram.Login.widgetsOrigin + '/auth?bot_id=' + encodeURIComponent(options.bot_id) + '&origin=' + encodeURIComponent(origin) + (options.request_access ? '&request_access=' + encodeURIComponent(options.request_access) : '') + ('&lang=' + encodeURIComponent(options.lang)) + '&return_to=' + encodeURIComponent(origin); + var popup = window.open(popup_url, '_blank', 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',status=0,location=0,menubar=0,toolbar=0'); + TelegramLogin.popups[bot_id] = { + window: popup, + authFinished: false + }; + if (popup) { + addEvent(window, 'message', onMessage); + popup.focus(); + checkClose(bot_id); + } + }, + getAuthData: function(options, callback) { + var bot_id = parseInt(options.bot_id); + if (!bot_id) { + throw new Error('Bot id required'); + } + var xhr = getXHR(); + var url = Telegram.Login.widgetsOrigin + '/auth/get'; + xhr.open('POST', url); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (typeof xhr.responseBody == 'undefined' && xhr.responseText) { + try { + var result = JSON.parse(xhr.responseText); + } catch(e) { + var result = {}; + } + if (result.user) { + callback(result.origin, result.user); + } else { + callback(result.origin, false); + } + } else { + callback('*', false); + } + } + }; + xhr.onerror = function() { + callback('*', false); + }; + xhr.withCredentials = false; + xhr.send('bot_id=' + encodeURIComponent(options.bot_id) + (options.lang ? '&lang=' + encodeURIComponent(options.lang) : '')); + } + }; + + window.Telegram.getWidgetInfo = getWidgetInfo; + window.Telegram.setWidgetOptions = setWidgetOptions; + window.Telegram.Login = { + init: TelegramLogin._init, + open: TelegramLogin._open, + auth: TelegramLogin._auth, +/* PATCHED */ widgetsOrigin: 'https://oauth.telegram.org' + }; + + }(window)); +})(window); diff --git a/apps/desktop/webpack.plugins.ts b/apps/desktop/webpack.plugins.ts index cf7704055..3c4eb2135 100644 --- a/apps/desktop/webpack.plugins.ts +++ b/apps/desktop/webpack.plugins.ts @@ -14,6 +14,7 @@ export const plugins = [ new webpack.DefinePlugin({ REACT_APP_AMPLITUDE: JSON.stringify(process.env.REACT_APP_AMPLITUDE), REACT_APP_TONCONSOLE_API: JSON.stringify(process.env.REACT_APP_TONCONSOLE_API), - REACT_APP_TG_BOT_ID: JSON.stringify(process.env.REACT_APP_TG_BOT_ID) + REACT_APP_TG_BOT_ID: JSON.stringify(process.env.REACT_APP_TG_BOT_ID), + REACT_APP_TG_BOT_ORIGIN: JSON.stringify(process.env.REACT_APP_TG_BOT_ORIGIN) }) ]; diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index bdb2d4453..4b38e218c 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -184,8 +184,8 @@ export const waitProServiceInvoice = async (invoice: InvoicesInvoice) => { } while (updated.status === InvoiceStatus.PENDING); }; -export async function startProServiceTrial(botId: string) { - const tgData = await loginViaTG(botId); +export async function startProServiceTrial(botId: string, lang?: string) { + const tgData = await loginViaTG(botId, lang); if (tgData) { return ProServiceService.proServiceTrial(tgData); } diff --git a/packages/core/src/service/telegram-oauth.ts b/packages/core/src/service/telegram-oauth.ts index 4f9188a1b..6a79adb06 100644 --- a/packages/core/src/service/telegram-oauth.ts +++ b/packages/core/src/service/telegram-oauth.ts @@ -52,7 +52,7 @@ function isTGAvailable(window: Window): window is Window & { ); } -export async function loginViaTG(botId: string): Promise { +export async function loginViaTG(botId: string, lang?: string): Promise { const window = getWindow(); if (!window) { return null; @@ -62,7 +62,7 @@ export async function loginViaTG(botId: string): Promise { throw new Error('Telegram auth provider not found'); } return new Promise(res => { - window.Telegram.Login.auth({ bot_id: botId, request_access: 'write' }, data => { + window.Telegram.Login.auth({ bot_id: botId, request_access: 'write', lang }, data => { res(data || null); }); }); diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index bc963a243..5d5f935a2 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -23,6 +23,7 @@ import { useAppContext, useWalletContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { QueryKey } from '../libs/queryKey'; import { signTonConnect } from './mnemonic'; +import { useTranslation } from '../hooks/translation'; export const useProBackupState = () => { const sdk = useAppSdk(); @@ -132,11 +133,14 @@ export const useWaitInvoiceMutation = () => { }; export const useActivateTrialMutation = () => { - const client = useQueryClient(); - const ctx = useAppContext(); + const client = useQueryClient(); + const ctx = useAppContext(); + const { + i18n: { language } + } = useTranslation(); - return useMutation(async () => { - await startProServiceTrial((ctx.env as { tgAuthBotId: string }).tgAuthBotId); - await client.invalidateQueries([QueryKey.pro]); - }); + return useMutation(async () => { + await startProServiceTrial((ctx.env as { tgAuthBotId: string }).tgAuthBotId, language); + await client.invalidateQueries([QueryKey.pro]); + }); }; From ebb4cf943b03e0576c3bffd6cd444ed90cd50321 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 8 Mar 2024 14:27:03 +0100 Subject: [PATCH 06/27] fix: trial & auth updates --- packages/core/src/entries/pro.ts | 53 +++++++++++++--- packages/core/src/service/proService.ts | 62 ++++++++++++++----- .../uikit/src/components/Notification.tsx | 3 +- .../uikit/src/components/aside/AsideMenu.tsx | 9 +-- .../src/components/aside/SubscriptionInfo.tsx | 54 +++++++++------- .../components/dashboard/CategoriesModal.tsx | 15 ++++- .../uikit/src/components/pro/ProBanner.tsx | 41 +++++++----- .../src/components/settings/ProSettings.tsx | 36 ++++++----- packages/uikit/src/pages/dashboard/index.tsx | 4 +- packages/uikit/src/state/pro.ts | 9 ++- 10 files changed, 198 insertions(+), 88 deletions(-) diff --git a/packages/core/src/entries/pro.ts b/packages/core/src/entries/pro.ts index 900aeab1f..6e71e3b41 100644 --- a/packages/core/src/entries/pro.ts +++ b/packages/core/src/entries/pro.ts @@ -1,7 +1,9 @@ -export interface ProState { - wallet: ProStateWallet; +type TgUserId = number; + +export interface ProState { + auth: A; hasCookie: boolean; - subscription: ProStateSubscription; + subscription: ProSubscription; } export interface ProStateWallet { @@ -9,9 +11,44 @@ export interface ProStateWallet { rawAddress: string; } -export interface ProStateSubscription { - valid: boolean; - is_trial: boolean; - used_trial: boolean; - next_charge?: number | undefined; +export type ProSubscription = ProSubscriptionValid | ProSubscriptionInvalid; + +export interface ProSubscriptionPaid { + valid: true; + isTrial: false; + usedTrial: boolean; + nextChargeDate: Date; +} + +export interface ProSubscriptionTrial { + valid: true; + isTrial: true; + trialEndDate: Date; + usedTrial: true; +} + +export type ProSubscriptionValid = ProSubscriptionPaid | ProSubscriptionTrial; + +export interface ProSubscriptionInvalid { + valid: false; + isTrial: false; + usedTrial: boolean; +} + +export function isTrialSubscription( + subscription: ProSubscription +): subscription is ProSubscriptionTrial { + return subscription.isTrial; +} + +export function isValidSubscription( + subscription: ProSubscription +): subscription is ProSubscriptionValid { + return subscription.valid; +} + +export function isPaidSubscription( + subscription: ProSubscription +): subscription is ProSubscriptionPaid { + return subscription.valid && !subscription.isTrial; } diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index 4b38e218c..80771c436 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -7,7 +7,7 @@ import { BLOCKCHAIN_NAME } from '../entries/crypto'; import { AssetAmount } from '../entries/crypto/asset/asset-amount'; import { TON_ASSET } from '../entries/crypto/asset/constants'; import { Language, localizationText } from '../entries/language'; -import { ProState, ProStateSubscription } from '../entries/pro'; +import { ProState, ProSubscription, ProSubscriptionInvalid } from '../entries/pro'; import { RecipientData, TonRecipientData } from '../entries/send'; import { WalletState } from '../entries/wallet'; import { AccountsApi } from '../tonApiV2'; @@ -32,12 +32,12 @@ import { DashboardCell, DashboardColumn } from '../entries/dashboard'; import { FiatCurrencies } from '../entries/fiat'; import { Flatten } from '../utils/types'; -export const setBackupState = async (storage: IStorage, state: ProStateSubscription) => { +export const setBackupState = async (storage: IStorage, state: ProSubscription) => { await storage.set(AppKey.PRO_BACKUP, state); }; export const getBackupState = async (storage: IStorage) => { - const backup = await storage.get(AppKey.PRO_BACKUP); + const backup = await storage.get(AppKey.PRO_BACKUP); return backup ?? toEmptySubscription(); }; @@ -48,7 +48,7 @@ export const getProState = async (storage: IStorage, wallet: WalletState): Promi return { subscription: toEmptySubscription(), hasCookie: false, - wallet: { + auth: { publicKey: wallet.publicKey, rawAddress: wallet.active.rawAddress } @@ -56,30 +56,62 @@ export const getProState = async (storage: IStorage, wallet: WalletState): Promi } }; -const toEmptySubscription = (): ProStateSubscription => { +const toEmptySubscription = (): ProSubscriptionInvalid => { return { valid: false, - is_trial: false, - used_trial: false + isTrial: false, + usedTrial: false }; }; export const loadProState = async (storage: IStorage): Promise => { const user = await ProServiceService.proServiceGetUserInfo(); - const wallet = await getWalletState(storage, user.pub_key); - if (!wallet) { - throw new Error('Unknown wallet'); + let auth: ProState['auth']; + if (user.tg_id !== undefined) { + auth = user.tg_id; + } else { + const wallet = await getWalletState(storage, user.pub_key); + if (!wallet) { + throw new Error('Unknown wallet'); + } + auth = { + publicKey: wallet.publicKey, + rawAddress: wallet.active.rawAddress + }; + } + + const subscriptionDTO = await ProServiceService.proServiceVerify(); + + let subscription: ProSubscription; + if (subscriptionDTO.is_trial) { + subscription = { + valid: true, + isTrial: true, + usedTrial: true, + trialEndDate: new Date(subscriptionDTO.next_charge! * 1000) + }; + } else { + if (subscriptionDTO.valid) { + subscription = { + valid: true, + isTrial: false, + usedTrial: subscriptionDTO.used_trial, + nextChargeDate: new Date(subscriptionDTO.next_charge! * 1000) + }; + } else { + subscription = { + valid: false, + isTrial: false, + usedTrial: subscriptionDTO.used_trial + }; + } } - const subscription = await ProServiceService.proServiceVerify(); return { subscription, hasCookie: true, - wallet: { - publicKey: wallet.publicKey, - rawAddress: wallet.active.rawAddress - } + auth }; }; diff --git a/packages/uikit/src/components/Notification.tsx b/packages/uikit/src/components/Notification.tsx index 8d4dfd8a4..222424333 100644 --- a/packages/uikit/src/components/Notification.tsx +++ b/packages/uikit/src/components/Notification.tsx @@ -234,6 +234,7 @@ const HeaderWrapper = styled.div` const RowTitle = styled(H3)` margin: 0; user-select: none; + flex: 1; `; const BackShadow = styled.div` @@ -400,7 +401,7 @@ export const Notification: FC<{ handleClose: () => void; hideButton?: boolean; backShadow?: boolean; - title?: string; + title?: ReactNode; footer?: ReactNode; children: (afterClose: (action?: () => void) => void) => React.ReactNode; }> = ({ children, isOpen, hideButton, backShadow, handleClose, title, footer }) => { diff --git a/packages/uikit/src/components/aside/AsideMenu.tsx b/packages/uikit/src/components/aside/AsideMenu.tsx index 23d302512..64c90ce50 100644 --- a/packages/uikit/src/components/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/aside/AsideMenu.tsx @@ -89,7 +89,7 @@ export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = }, [publicKey, mutate, handleNavigateHome]); if (!wallet) { - return null; //TODO + return null; } const address = formatAddress(wallet.active.rawAddress, wallet.network); @@ -104,6 +104,7 @@ export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = }; export const AsideMenu: FC<{ className?: string }> = ({ className }) => { + const { t } = useTranslation(); const [isOpenImport, setIsOpenImport] = useState(false); const { account, proFeatures } = useAppContext(); const navigate = useNavigate(); @@ -140,7 +141,7 @@ export const AsideMenu: FC<{ className?: string }> = ({ className }) => { padding="m" onClick={() => handleNavigateClick(AppProRoute.dashboard)} > - Dashboard {/*TODO*/} + {t('aside_dashboard')} )} {account.publicKeys.map(publicKey => ( @@ -157,7 +158,7 @@ export const AsideMenu: FC<{ className?: string }> = ({ className }) => { setIsOpenImport(true)}> - Add Wallet + {t('aside_add_wallet')} @@ -169,7 +170,7 @@ export const AsideMenu: FC<{ className?: string }> = ({ className }) => { isSelected={activeRoute === AppRoute.settings} > - Settings + {t('aside_settings')} diff --git a/packages/uikit/src/components/aside/SubscriptionInfo.tsx b/packages/uikit/src/components/aside/SubscriptionInfo.tsx index e5b181b07..76f9e580f 100644 --- a/packages/uikit/src/components/aside/SubscriptionInfo.tsx +++ b/packages/uikit/src/components/aside/SubscriptionInfo.tsx @@ -1,4 +1,8 @@ -import { ProState } from '@tonkeeper/core/dist/entries/pro'; +import { + isTrialSubscription, + isValidSubscription, + ProState +} from '@tonkeeper/core/dist/entries/pro'; import { FC } from 'react'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; @@ -22,36 +26,42 @@ export const SubscriptionStatus: FC<{ data: ProState }> = ({ data }) => { const { t } = useTranslation(); const formatDate = useDateTimeFormat(); - const { - subscription: { is_trial, valid, next_charge } - } = data; + const { subscription } = data; - const Expires = next_charge ? ( - <> - {` ${t('it_expires_on')} `} - {formatDate(next_charge, { - day: 'numeric', - month: 'short', - year: 'numeric', - inputUnit: 'seconds' - })} - - ) : null; - - if (is_trial) { + if (isTrialSubscription(subscription)) { return ( <> - {t('pro_trial_is_active')} - {Expires} +
{t('aside_pro_trial_is_active')}
+
+ {t('aside_expires_on').replace( + '%date%', + formatDate(subscription.trialEndDate, { + day: 'numeric', + month: 'short', + year: 'numeric', + inputUnit: 'seconds' + }) + )} +
); } - if (valid) { + if (isValidSubscription(subscription)) { return ( <> - {t('pro_subscription_is_active')} - {Expires} +
{t('aside_pro_subscription_is_active')}
+
+ {t('aside_expires_on').replace( + '%date%', + formatDate(subscription.nextChargeDate, { + day: 'numeric', + month: 'short', + year: 'numeric', + inputUnit: 'seconds' + }) + )} +
); } diff --git a/packages/uikit/src/components/dashboard/CategoriesModal.tsx b/packages/uikit/src/components/dashboard/CategoriesModal.tsx index 8fffb8c8b..562d82c38 100644 --- a/packages/uikit/src/components/dashboard/CategoriesModal.tsx +++ b/packages/uikit/src/components/dashboard/CategoriesModal.tsx @@ -16,12 +16,21 @@ import { Badge } from '../shared'; import { useProState } from '../../state/pro'; import { ProNotification } from '../pro/ProNotification'; import { useDisclosure } from '../../hooks/useDisclosure'; -import { DashboardColumn } from "@tonkeeper/core/dist/entries/dashboard"; +import { DashboardColumn } from '@tonkeeper/core/dist/entries/dashboard'; +import { useTranslation } from '../../hooks/translation'; + +const HeaderStyled = styled.div` + width: 100%; + padding-left: 48px; + text-align: center; + box-sizing: border-box; +`; export const CategoriesModal: FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpen, onClose }) => { + const { t } = useTranslation(); const [_, { mutate, isLoading }] = useDashboardColumnsForm(); const { data } = useDashboardColumnsAsForm(); const [categoriesForm, setCategoriesForm] = useState([]); @@ -57,9 +66,9 @@ export const CategoriesModal: FC<{ isOpen: boolean; onClose: () => void }> = ({ return ( {t('dashboard_manage_modal_title')}} footer={ } diff --git a/packages/uikit/src/components/pro/ProBanner.tsx b/packages/uikit/src/components/pro/ProBanner.tsx index 279d5c8e0..853833e78 100644 --- a/packages/uikit/src/components/pro/ProBanner.tsx +++ b/packages/uikit/src/components/pro/ProBanner.tsx @@ -6,6 +6,8 @@ import { useActivateTrialMutation, useProState } from '../../state/pro'; import { useDateTimeFormat } from '../../hooks/useDateTimeFormat'; import { ProNotification } from './ProNotification'; import { useDisclosure } from '../../hooks/useDisclosure'; +import { useTranslation } from '../../hooks/translation'; +import { isPaidSubscription, isTrialSubscription } from '@tonkeeper/core/dist/entries/pro'; const ProBannerStyled = styled.div` background: ${p => p.theme.backgroundContent}; @@ -24,11 +26,17 @@ const TextContainerStyled = styled.div` min-width: 300px; `; const ButtonsContainerStyled = styled.div` + align-items: center; display: flex; gap: 8px; `; +const Label2Styled = styled(Label2)` + padding-right: 12px; +`; + export const ProBanner: FC<{ className?: string }> = ({ className }) => { + const { t } = useTranslation(); const formatDate = useDateTimeFormat(); const { mutate } = useActivateTrialMutation(); const { data } = useProState(); @@ -38,41 +46,40 @@ export const ProBanner: FC<{ className?: string }> = ({ className }) => { return null; } - const { - subscription: { is_trial, used_trial, valid, next_charge } - } = data; + const { subscription } = data; - if (valid) { + if (isPaidSubscription(subscription)) { return null; } return ( - Get more with Tonkeeper Pro - Access advanced features and tools to boost your work. + {t('pro_banner_title')} + {t('pro_banner_subtitle')} - {is_trial ? ( - - {next_charge && - formatDate(next_charge, { + {isTrialSubscription(subscription) ? ( + + {t('pro_banner_days_left').replace( + '%days%', + formatDate(subscription.trialEndDate, { day: 'numeric', month: 'short', - year: 'numeric', - inputUnit: 'seconds' - })} - + year: 'numeric' + }) + )} + ) : ( - !used_trial && ( + !subscription.usedTrial && ( ) )} diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index 614304efc..28b412cfc 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -1,5 +1,5 @@ import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; -import { ProState } from '@tonkeeper/core/dist/entries/pro'; +import { ProState, ProStateWallet } from '@tonkeeper/core/dist/entries/pro'; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { ProServiceTier } from '@tonkeeper/core/src/tonConsoleApi'; import { FC, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react'; @@ -89,7 +89,7 @@ const SelectWallet: FC = () => { {t('select_wallet_for_authorization')} {accounts.publicKeys.map(publicKey => ( - mutate(publicKey)}> + mutate(publicKey)}> @@ -106,16 +106,16 @@ const SelectIconWrapper = styled.span` display: flex; `; -const ProWallet: FC<{ data: ProState; onClick: () => void; disabled?: boolean }> = ({ - data, - onClick, - disabled -}) => { +const ProWallet: FC<{ + data: ProState; + onClick: () => void; + disabled?: boolean; +}> = ({ data, onClick, disabled }) => { return ( !disabled && onClick()}> - + @@ -136,7 +136,7 @@ const SelectProPlans: FC<{ <> {plans.map(plan => ( - !disabled && setPlan(plan.id)}> + !disabled && setPlan(plan.id)}> ; }; -const BuyProService: FC<{ data: ProState; setReLogin: () => void }> = ({ data, setReLogin }) => { +const BuyProService: FC<{ data: ProState; setReLogin: () => void }> = ({ + data, + setReLogin +}) => { const { t } = useTranslation(); const ref = useRef(null); @@ -262,7 +265,7 @@ const BuyProService: FC<{ data: ProState; setReLogin: () => void }> = ({ data, s /> void }> = ({ data, setReLogin }) => { +const PreServiceStatus: FC<{ data: ProState; setReLogin: () => void }> = ({ + data, + setReLogin +}) => { const { t } = useTranslation(); const { mutate: logOut, isLoading } = useProLogout(); @@ -311,7 +317,7 @@ const PreServiceStatus: FC<{ data: ProState; setReLogin: () => void }> = ({ data ); }; -const ProContent: FC<{ data: ProState }> = ({ data }) => { +const ProContent: FC<{ data: ProState }> = ({ data }) => { const [reLogin, setReLogin] = useState(false); if (!data.hasCookie || reLogin) { @@ -335,7 +341,9 @@ export const ProSettingsContent: FC<{ showLogo?: boolean }> = ({ showLogo = true {t('tonkeeper_pro')} {t('tonkeeper_pro_description')} - {data ? : undefined} + {data && typeof data.auth === 'object' ? ( + } /> + ) : undefined} ); }; diff --git a/packages/uikit/src/pages/dashboard/index.tsx b/packages/uikit/src/pages/dashboard/index.tsx index ad2f6a4e9..91fdc563c 100644 --- a/packages/uikit/src/pages/dashboard/index.tsx +++ b/packages/uikit/src/pages/dashboard/index.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; import { CategoriesModal } from '../../components/dashboard/CategoriesModal'; import { Button } from '../../components/fields/Button'; import { ProBanner } from '../../components/pro/ProBanner'; +import { useTranslation } from '../../hooks/translation'; const DashboardTableStyled = styled(DashboardTable)``; @@ -35,13 +36,14 @@ const PageWrapper = styled.div` `; const DashboardPage: FC = () => { + const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); return ( diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index 5d5f935a2..2c9720ced 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -1,6 +1,6 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; -import { ProState, ProStateSubscription } from '@tonkeeper/core/dist/entries/pro'; +import { ProState, ProSubscription } from '@tonkeeper/core/dist/entries/pro'; import { RecipientData } from '@tonkeeper/core/dist/entries/send'; import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; import { @@ -27,7 +27,7 @@ import { useTranslation } from '../hooks/translation'; export const useProBackupState = () => { const sdk = useAppSdk(); - return useQuery( + return useQuery( [QueryKey.proBackup], () => getBackupState(sdk.storage), { keepPreviousData: true } @@ -108,7 +108,10 @@ export const useCreateInvoiceMutation = () => { if (data.tierId === null) { throw new Error('missing tier'); } - const wallet = await getWalletState(sdk.storage, data.state.wallet.publicKey); + if (typeof data.state.auth !== 'object') { + throw new Error('Invoice can be created only with wallet auth'); + } + const wallet = await getWalletState(sdk.storage, data.state.auth.publicKey); if (!wallet) { throw new Error('Missing wallet'); } From c67bc53ca625def46a7d827acbb7b1030f5bbaeb Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 8 Mar 2024 14:49:07 +0100 Subject: [PATCH 07/27] chore: locales update --- packages/locales/src/tonkeeper-web/en.json | 20 ++++++- packages/locales/src/tonkeeper-web/ru-RU.json | 20 ++++++- packages/locales/src/tonkeeper/en.json | 9 +++ packages/locales/src/tonkeeper/ru-RU.json | 9 +++ packages/locales/src/tonkeeper/tr-TR.json | 54 ++++++++++++++++-- .../locales/src/tonkeeper/zh-Hans-CN.json | 56 +++++++++++++++++-- 6 files changed, 151 insertions(+), 17 deletions(-) diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index 616d4a93b..bf9bfdb2e 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -5,6 +5,12 @@ "appName" : "Tonkeeper", "appTitle" : "Tonkeeper — wallet for TON", "App_version_copied" : "App version copied", + "aside_add_wallet" : "Add Wallet", + "aside_dashboard" : "Dashboard", + "aside_expires_on" : "It expires on %date%", + "aside_pro_subscription_is_active" : "Pro Subscription is active.", + "aside_pro_trial_is_active" : "Pro Trial is active.", + "aside_settings" : "Settings", "auto" : "Auto", "Back_up_now" : "Back up now", "Change" : "Change", @@ -15,6 +21,8 @@ "Copy_address" : "Copy address", "country" : "Country", "Create_password" : "Create password", + "dashboard_column_name" : "Name", + "dashboard_manage_modal_title" : "Dashboard", "Delete_all_accounts_and_logout" : "Delete all accounts and logout", "Delete_wallet_data" : "Delete wallet data", "Delete_wallet_data_description" : "Wallet keys and all personal data will be erased from this device.", @@ -28,6 +36,7 @@ "Lock_screen" : "Lock screen", "logout_on_unlock_many" : "This will erase keys to all your wallets. Make sure you have backed up your secret recovery phrases.", "logout_on_unlock_one" : "This will erase keys to the wallet. Make sure you have backed up your secret recovery phrase.", + "manage" : "Manage", "Manage" : "Manage", "Manage_wallets" : "Manage wallets", "MinPassword" : "Must be at least 6 characters.", @@ -41,6 +50,11 @@ "Password" : "Password", "PasswordChanged" : "Password Changed", "PasswordDoNotMatch" : "Passwords do not match.", + "pro_banner_buy" : "Buy Pro", + "pro_banner_days_left" : "Trial ends in %days% days", + "pro_banner_start_trial" : "Try Pro for Free", + "pro_banner_subtitle" : "Access advanced features and tools to boost your work", + "pro_banner_title" : "Get more with Tonkeeper Pro", "receive_ton" : "Receive Toncoin", "receive_ton_description" : "Send only Toncoin TON and tokens\nin TON network to this address, or you\nmight lose your funds.", "receive_trc20" : "Receive USDT TRC20", @@ -51,6 +65,7 @@ "renew_nft_renewed" : "Domain renewed for 1 year", "replace" : "Replace", "reset_tron_cache" : "Reset Tron cache", + "save" : "Save", "tme_linked_with_another_address_warn" : "The name is not linked to your current address. Be careful with transactions to this name.", "Toncoin" : "Toncoin", "Ton_page_description" : "TON is a fully decentralized layer-1 blockchain designed by Telegram to onboard billions of users. It boasts ultra-fast transactions, tiny fees, easy-to-use apps, and is environmentally friendly.", @@ -63,6 +78,5 @@ "Unlock" : "Unlock", "wallet_address" : "Wallet address", "Wallet_name" : "Wallet name", - "wallet_sell" : "Sell", - "dashboard_column_name" : "Name" -} + "wallet_sell" : "Sell" +} \ No newline at end of file diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index 82e0c838a..506015c06 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -5,6 +5,12 @@ "appName" : "Tonkeeper", "appTitle" : "Tonkeeper — wallet for TON", "App_version_copied" : "Версия приложения скопирована", + "aside_add_wallet" : "Добавить кошелек", + "aside_dashboard" : "Дэшборд", + "aside_expires_on" : "Подписка истекает %date%", + "aside_pro_subscription_is_active" : "Подписка Pro активна.", + "aside_pro_trial_is_active" : "Пробная версия Pro активна.", + "aside_settings" : "Настройки", "auto" : "Авто", "Back_up_now" : "Сделать резервную копию сейчас", "Change" : "Изменить", @@ -15,6 +21,8 @@ "Copy_address" : "Скопировать адрес", "country" : "Страна", "Create_password" : "Придумайте пароль", + "dashboard_column_name" : "Имя", + "dashboard_manage_modal_title" : "Дэшборд", "Delete_all_accounts_and_logout" : "Удалить все учетные записи и выйти", "Delete_wallet_data" : "Удалить данные кошелька", "Delete_wallet_data_description" : "Ключи кошелька и все личные данные будут удалены с этого устройства.", @@ -28,6 +36,7 @@ "Lock_screen" : "Экран блокировки", "logout_on_unlock_many" : "Доступ к кошелькам будет отключен. Убедитесь, что вы сохранили секретные ключи.", "logout_on_unlock_one" : "Доступ к кошельку будет отключен. Убедитесь, что вы сохранили секретный ключ.", + "manage" : "Настроить", "Manage" : "Управлять", "Manage_wallets" : "Управление кошельками", "MinPassword" : "Должно быть не менее 6 символов.", @@ -41,6 +50,11 @@ "Password" : "Пароль", "PasswordChanged" : "Пароль изменен", "PasswordDoNotMatch" : "Пароли не совпадают.", + "pro_banner_buy" : "Купить Про", + "pro_banner_days_left" : "Дней до истечения пробной подписки: %days%", + "pro_banner_start_trial" : "Попробуйте Pro бесплатно", + "pro_banner_subtitle" : "Получите доступ к расширенным функциям и инструментам для улучшения вашей работы", + "pro_banner_title" : "Откройте все возможности с Tonkeeper Pro", "receive_ton" : "Получить Toncoin", "receive_ton_description" : "Отправляйте на этот адрес только Toncoin TON и токены в сети TON, иначе вы можете потерять свои средства.", "receive_trc20" : "Получить USDT TRC20", @@ -51,6 +65,7 @@ "renew_nft_renewed" : "Домен продлён на 1 год", "replace" : "Заменить", "reset_tron_cache" : "Reset Tron cache", + "save" : "Сохранять", "tme_linked_with_another_address_warn" : "Имя не связано с вашим текущим адресом. Будьте осторожными с транзакциями на это имя.", "Toncoin" : "Toncoin", "Ton_page_description" : "TON — это полностью децентрализованный блокчейн первого уровня, разработанный Telegram для миллиардов пользователей. Он может похвастаться сверхбыстрыми транзакциями, небольшими комиссиями, простыми в использовании приложениями и экологичностью.", @@ -63,6 +78,5 @@ "Unlock" : "Разблокировать", "wallet_address" : "Адрес кошелька", "Wallet_name" : "Имя кошелька", - "wallet_sell" : "Продать", - "dashboard_column_name" : "Имя" -} + "wallet_sell" : "Продать" +} \ No newline at end of file diff --git a/packages/locales/src/tonkeeper/en.json b/packages/locales/src/tonkeeper/en.json index f5fd9ffe6..f64eeb642 100644 --- a/packages/locales/src/tonkeeper/en.json +++ b/packages/locales/src/tonkeeper/en.json @@ -805,6 +805,8 @@ "settings_delete_alert_button" : "Delete account and data", "settings_delete_alert_caption" : "This action will delete your account and all data from this application.", "settings_delete_alert_title" : "Are you sure you want to delete your account?", + "settings_delete_watch_account" : "Delete Watch Account?", + "settings_delete_watch_account_button" : "Delete", "settings_jettons_list" : "Tokens", "settings_legal_documents" : "Legal", "settings_network_alert_title" : "Select network", @@ -1007,6 +1009,12 @@ "tab_swap" : "Swap", "tab_wallet" : "Wallet", "today" : "Today", + "tokenDetails" : { + "name" : "Name", + "title" : "Token Details", + "token_id" : "Token ID", + "tonviewer_button" : "View on Tonviewer" + }, "tonkeeper_pro" : "Tonkeeper Pro", "tonkeeper_pro_description" : "Tonkeeper Pro's subscription comes with an extended wallet feature, offering a toolset for crypto management. ", "ton_login_back_to_button" : "Back to %{name}", @@ -1105,6 +1113,7 @@ "transfer_deeplink_amount_error" : "Incorrect amount request", "transfer_deeplink_expired_error" : "Expired link", "transfer_deeplink_nft_address_error" : "Incorrect NFT address", + "transfer_deeplink_unknown_jetton_error" : "Unknown token", "transfer_deeplink_unknown_token" : "Unknown token", "transfer_deeplink_wrong_params" : "Wrong parameters", "transfer_from_old_wallet_btn" : "Transfer", diff --git a/packages/locales/src/tonkeeper/ru-RU.json b/packages/locales/src/tonkeeper/ru-RU.json index 560f077c3..ecb91557d 100644 --- a/packages/locales/src/tonkeeper/ru-RU.json +++ b/packages/locales/src/tonkeeper/ru-RU.json @@ -806,6 +806,8 @@ "settings_delete_alert_button" : "Удалить аккаунт и данные", "settings_delete_alert_caption" : "Это действие приведет к удалению вашего аккаунта и всех данных из этого приложения.", "settings_delete_alert_title" : "Вы уверены, что хотите удалить аккаунт?", + "settings_delete_watch_account" : "Удалить аккаунт для просмотра?", + "settings_delete_watch_account_button" : "Удалить", "settings_jettons_list" : "Токены", "settings_legal_documents" : "Юридические документы", "settings_network_alert_title" : "Выберите сеть", @@ -1034,6 +1036,12 @@ "tab_swap" : "Обмен", "tab_wallet" : "Кошелёк", "today" : "Сегодня", + "tokenDetails" : { + "name" : "Имя", + "title" : "Подробнее о токене", + "token_id" : "ID токена", + "tonviewer_button" : "Открыть в Tonviewer" + }, "ton_login_back_to_button" : "Вернуться в %{name}", "ton_login_caption" : "%{name} запрашивает доступ к адресу вашего кошелька", "ton_login_connect_button" : "Подключить кошелёк", @@ -1136,6 +1144,7 @@ "transfer_deeplink_amount_error" : "Некорректный запрос суммы", "transfer_deeplink_expired_error" : "Срок действия ссылки истёк", "transfer_deeplink_nft_address_error" : "Неверный адрес NFT", + "transfer_deeplink_unknown_jetton_error" : "Неизвестный токен", "transfer_from_old_wallet_btn" : "Перевести", "transfer_from_old_wallet_caption" : "Tonkeeper переведет всю сумму со старого адреса на текущий.", "transfer_from_old_wallet_in_progress" : "Идет перевод", diff --git a/packages/locales/src/tonkeeper/tr-TR.json b/packages/locales/src/tonkeeper/tr-TR.json index fb06ffd76..59c821238 100644 --- a/packages/locales/src/tonkeeper/tr-TR.json +++ b/packages/locales/src/tonkeeper/tr-TR.json @@ -149,6 +149,29 @@ "whitelisted_token" : "Beyaz listeye alınmış token" }, "auth_failed" : "Kimlik doğrulama başarısız oldu", + "backup_check" : { + "caption" : "Her şeyi doğru yapıp yapmadığınızı görelim. %{one}, %{two} ve %{three} numaralı kelimeleri girin.", + "done_button" : "Tamamlandı", + "title" : "Yedekleme Kontrolü" + }, + "backup_screen" : { + "last_backup_time" : "Son yedekleme %{time}", + "manual_backup_on" : "Manuel Yedekleme Açık", + "manual_button" : "Manuel Olarak Yedekleyin", + "manual_caption" : "Kurtarma ifadesini yazarak cüzdanınızı manuel olarak yedekleyin.", + "manual_title" : "Manuel", + "show_recovery_phrase" : "Kurtarma İfadesini Göster", + "title" : "Yedekle" + }, + "backup_warning" : { + "cancel_button" : "İptal", + "caption" : "Kurtarma ifadenizi görüntülemeden önce lütfen aşağıdakileri dikkatlice okuyun.", + "continue_button" : "Devam et", + "p1" : "Cüzdanınıza erişmek için kurtarma cümlenizi asla Tonkeeper'dan başka bir yere girmeyin.", + "p2" : "Tonkeeper Destek hiçbir zaman size kurtarma ifadesini sormaz.", + "p3" : "Kurtarma ifadenizi bilen herkes cüzdanınıza erişebilir.", + "title" : "Dikkat" + }, "balances_setup_wallet" : "Cüzdanı ayarlayın", "battery" : { "description" : { @@ -188,14 +211,22 @@ "android" : { "face_recognition" : "yüz tanıma", "face_recognition_genitive" : "yüz tanıma", + "face_recognition_instrumental" : "yüz tanıma", "fingerprint" : "Parmak izi", - "fingerprint_genitive" : "Parmak izi" + "fingerprint_genitive" : "Parmak izi", + "fingerprint_instrumental" : "Parmak izi" }, + "default" : "Biyometrik", + "default_accusative" : "Biyometrik", + "default_genitive" : "Biyometrik", + "default_instrumental" : "Biyometrik", "ios" : { - "face_recognition" : "Yüz kimliği", - "face_recognition_genitive" : "Yüz kimliği", + "face_recognition" : "Face ID", + "face_recognition_genitive" : "Face ID", + "face_recognition_instrumental" : "Face ID", "fingerprint" : "Touch ID", - "fingerprint_genitive" : "Touch ID" + "fingerprint_genitive" : "Touch ID", + "fingerprint_instrumental" : "Touch ID" } }, "browser" : { @@ -409,6 +440,13 @@ "exchange_telegram_bot" : "TELEGRAM BOTU", "exchange_title" : "TON satın alın", "expiring_domains" : "Süresi dolan alan adları", + "finish_setup" : { + "backup" : "Cüzdan kurtarma ifadesini yedekleyin", + "done_button" : "Tamamlandı", + "enable_notifications" : "İşlem bildirimlerini etkinleştirin", + "header_title" : "Kurulumu tamamla", + "use_biometry" : "İşlemi onaylamak için %{name} kullanın" + }, "form_optional_indicator" : "İsteğe bağlı", "import_add_wallet" : "Cüzdan Ekle", "import_add_wallet_description" : "Yeni bir cüzdan oluşturun veya mevcut bir cüzdan ekleyin.", @@ -634,6 +672,12 @@ "receive_share" : "Paylaş", "receive_title" : "%{currency} al", "receive_ton_and_jettons" : "TON ve diğer token'ları cüzdana alın", + "recovery_phrase" : { + "caption" : "Bu kelimeleri numaralarıyla birlikte not edin ve güvenli bir yerde saklayın.", + "check_button" : "Yedekleme Kontrolü", + "copy_button" : "Kopyala", + "title" : "Gizli kurtarma ifadesi" + }, "refresh_app" : "Yeniden Başlat", "region_nokyc" : "Tarafsız bölge", "reminder_notifications_caption" : "Cüzdanınıza TON, token ve NFT geldiğinde bildirimler alın.", @@ -728,7 +772,7 @@ "send_sending_wrong_time_title" : "Hata oluştu", "send_title" : "%{currency} gönder", "settings_appearance" : "Tema", - "settings_backup_seed" : "Gizli kurtarma ifadesini göster", + "settings_backup_seed" : "Yedekle", "settings_contact_support" : "Bize Ulaşın", "settings_delete_account" : "Hesabı sil", "settings_delete_alert_button" : "Hesabı ve verileri sil", diff --git a/packages/locales/src/tonkeeper/zh-Hans-CN.json b/packages/locales/src/tonkeeper/zh-Hans-CN.json index 78471446b..229fddba5 100644 --- a/packages/locales/src/tonkeeper/zh-Hans-CN.json +++ b/packages/locales/src/tonkeeper/zh-Hans-CN.json @@ -113,6 +113,29 @@ "whitelisted_token" : "白名单代币" }, "auth_failed" : "认证失败", + "backup_check" : { + "caption" : "让我们看看是否全部正确。输入单词%{one},%{two}和%{three}。", + "done_button" : "完成", + "title" : "备份检查" + }, + "backup_screen" : { + "last_backup_time" : "上次备份 %{time}", + "manual_backup_on" : "手动备份开启", + "manual_button" : "手动备份", + "manual_caption" : "通过写下助记词以手动备份您的钱包。", + "manual_title" : "手动", + "show_recovery_phrase" : "显示恢复短语", + "title" : "备份" + }, + "backup_warning" : { + "cancel_button" : "取消", + "caption" : "在查看您的助记词之前,请仔细阅读以下内容。", + "continue_button" : "继续", + "p1" : "切勿在Tonkeeper以外的任何其他地方输入您的助记词来访问您的钱包。", + "p2" : "Tonkeeper 支持人员永远不会要求您提供助记词。", + "p3" : "每一个知道您助记词的人都可以控制您的钱包。", + "title" : "请注意" + }, "balances_setup_wallet" : "注册钱包", "battery" : { "description" : { @@ -150,16 +173,24 @@ }, "biometry" : { "android" : { - "face_recognition" : "面部识别", - "face_recognition_genitive" : "面部识别", - "fingerprint" : "指纹", - "fingerprint_genitive" : "指纹" + "face_recognition" : "人脸识别", + "face_recognition_genitive" : "人脸识别", + "face_recognition_instrumental" : "人脸识别", + "fingerprint" : "指纹识别", + "fingerprint_genitive" : "指纹识别", + "fingerprint_instrumental" : "指纹识别" }, + "default" : "生物识别", + "default_accusative" : "生物识别", + "default_genitive" : "生物识别", + "default_instrumental" : "生物识别", "ios" : { "face_recognition" : "Face ID", "face_recognition_genitive" : "Face ID", + "face_recognition_instrumental" : "Face ID", "fingerprint" : "Touch ID", - "fingerprint_genitive" : "Touch ID" + "fingerprint_genitive" : "Touch ID", + "fingerprint_instrumental" : "Touch ID" } }, "browser" : { @@ -323,6 +354,13 @@ "exchange_other_ways" : "其他购买或出售TON的方式", "exchange_telegram_bot" : "TELEGRAM机器人", "exchange_title" : "购买TON", + "finish_setup" : { + "backup" : "备份钱包助记词", + "done_button" : "完成", + "enable_notifications" : "启用交易通知", + "header_title" : "完成设置", + "use_biometry" : "使用 %{name} 批准交易" + }, "form_optional_indicator" : "可选", "import_add_wallet" : "添加钱包", "import_add_wallet_description" : "创建一个新钱包或添加现有钱包。", @@ -510,6 +548,12 @@ "receive_share" : "分享", "receive_title" : "接收 %{currency}", "receive_ton_and_jettons" : "接收TON和其他代币", + "recovery_phrase" : { + "caption" : "写下这些单词及其编号并将其存放在安全的地方。", + "check_button" : "检查备份", + "copy_button" : "复制", + "title" : "助记词" + }, "refresh_app" : "重新刷新", "reminder_notifications_caption" : "当您收到TON、代币和NFT时获得通知。", "reminder_notifications_enable_button" : "启用通知", @@ -600,7 +644,7 @@ "send_sending_wrong_time_title" : "发生错误", "send_title" : "发送 %{currency}", "settings_appearance" : "主题", - "settings_backup_seed" : "显示恢复短语", + "settings_backup_seed" : "备份", "settings_contact_support" : "联系我们", "settings_delete_account" : "删除账户", "settings_delete_alert_button" : "删除账户和数据", From 2771464dd90a7263a6dccea0fa3d57845d36814f Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 8 Mar 2024 15:22:57 +0100 Subject: [PATCH 08/27] fix: updating from trial to pro --- packages/core/src/entries/pro.ts | 7 ++--- packages/core/src/service/proService.ts | 31 +++++++++++-------- .../tonConsoleApi/models/FiatCurrencies.ts | 13 +++++++- .../models/ProServiceDashboardColumnID.ts | 1 - .../src/components/settings/ProSettings.tsx | 18 +++++------ packages/uikit/src/state/pro.ts | 6 ++-- 6 files changed, 43 insertions(+), 33 deletions(-) diff --git a/packages/core/src/entries/pro.ts b/packages/core/src/entries/pro.ts index 6e71e3b41..676f72f25 100644 --- a/packages/core/src/entries/pro.ts +++ b/packages/core/src/entries/pro.ts @@ -1,7 +1,5 @@ -type TgUserId = number; - -export interface ProState
{ - auth: A; +export interface ProState { + wallet: ProStateWallet; hasCookie: boolean; subscription: ProSubscription; } @@ -21,6 +19,7 @@ export interface ProSubscriptionPaid { } export interface ProSubscriptionTrial { + trialUserId: number; valid: true; isTrial: true; trialEndDate: Date; diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index 80771c436..60b73a7bf 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -43,12 +43,12 @@ export const getBackupState = async (storage: IStorage) => { export const getProState = async (storage: IStorage, wallet: WalletState): Promise => { try { - return await loadProState(storage); + return await loadProState(storage, wallet); } catch (e) { return { subscription: toEmptySubscription(), hasCookie: false, - auth: { + wallet: { publicKey: wallet.publicKey, rawAddress: wallet.active.rawAddress } @@ -64,20 +64,24 @@ const toEmptySubscription = (): ProSubscriptionInvalid => { }; }; -export const loadProState = async (storage: IStorage): Promise => { +export const loadProState = async ( + storage: IStorage, + fallbackWallet: WalletState +): Promise => { const user = await ProServiceService.proServiceGetUserInfo(); - let auth: ProState['auth']; - if (user.tg_id !== undefined) { - auth = user.tg_id; - } else { - const wallet = await getWalletState(storage, user.pub_key); - if (!wallet) { + let wallet = { + publicKey: fallbackWallet.publicKey, + rawAddress: fallbackWallet.active.rawAddress + }; + if (user.pub_key) { + const actualWallet = await getWalletState(storage, user.pub_key); + if (!actualWallet) { throw new Error('Unknown wallet'); } - auth = { - publicKey: wallet.publicKey, - rawAddress: wallet.active.rawAddress + wallet = { + publicKey: actualWallet.publicKey, + rawAddress: actualWallet.active.rawAddress }; } @@ -89,6 +93,7 @@ export const loadProState = async (storage: IStorage): Promise => { valid: true, isTrial: true, usedTrial: true, + trialUserId: user.tg_id!, trialEndDate: new Date(subscriptionDTO.next_charge! * 1000) }; } else { @@ -111,7 +116,7 @@ export const loadProState = async (storage: IStorage): Promise => { return { subscription, hasCookie: true, - auth + wallet }; }; diff --git a/packages/core/src/tonConsoleApi/models/FiatCurrencies.ts b/packages/core/src/tonConsoleApi/models/FiatCurrencies.ts index 61864cea0..fe787edf1 100644 --- a/packages/core/src/tonConsoleApi/models/FiatCurrencies.ts +++ b/packages/core/src/tonConsoleApi/models/FiatCurrencies.ts @@ -4,5 +4,16 @@ /* eslint-disable */ export enum FiatCurrencies { USD = 'USD', - TON = 'TON', + EUR = 'EUR', + RUB = 'RUB', + AED = 'AED', + KZT = 'KZT', + UAH = 'UAH', + GBP = 'GBP', + CHF = 'CHF', + CNY = 'CNY', + KRW = 'KRW', + IDR = 'IDR', + INR = 'INR', + JPY = 'JPY', } diff --git a/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnID.ts b/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnID.ts index 277d66fbd..1047e9632 100644 --- a/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnID.ts +++ b/packages/core/src/tonConsoleApi/models/ProServiceDashboardColumnID.ts @@ -6,7 +6,6 @@ export enum ProServiceDashboardColumnID { ADDRESS = 'address', TOTAL_BALANCE = 'total_balance', TOTAL_TON = 'total_ton', - KEY_STORAGE = 'key_storage', SEND_CURRENT_MONTH = 'send_current_month', SEND_LAST_MONTH = 'send_last_month', RECEIVE_CURRENT_MONTH = 'receive_current_month', diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index 28b412cfc..65aae9ed2 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -1,5 +1,5 @@ import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; -import { ProState, ProStateWallet } from '@tonkeeper/core/dist/entries/pro'; +import { isPaidSubscription, ProState, ProStateWallet } from "@tonkeeper/core/dist/entries/pro"; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { ProServiceTier } from '@tonkeeper/core/src/tonConsoleApi'; import { FC, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react'; @@ -107,7 +107,7 @@ const SelectIconWrapper = styled.span` `; const ProWallet: FC<{ - data: ProState; + data: ProState; onClick: () => void; disabled?: boolean; }> = ({ data, onClick, disabled }) => { @@ -115,7 +115,7 @@ const ProWallet: FC<{ !disabled && onClick()}> - + @@ -212,7 +212,7 @@ const ConfirmBuyProService: FC< return ; }; -const BuyProService: FC<{ data: ProState; setReLogin: () => void }> = ({ +const BuyProService: FC<{ data: ProState; setReLogin: () => void }> = ({ data, setReLogin }) => { @@ -294,7 +294,7 @@ const StatusText = styled(Label1)` display: block; `; -const PreServiceStatus: FC<{ data: ProState; setReLogin: () => void }> = ({ +const PreServiceStatus: FC<{ data: ProState; setReLogin: () => void }> = ({ data, setReLogin }) => { @@ -317,13 +317,13 @@ const PreServiceStatus: FC<{ data: ProState; setReLogin: () => v ); }; -const ProContent: FC<{ data: ProState }> = ({ data }) => { +const ProContent: FC<{ data: ProState }> = ({ data }) => { const [reLogin, setReLogin] = useState(false); if (!data.hasCookie || reLogin) { return ; } - if (data.subscription.valid) { + if (isPaidSubscription(data.subscription)) { return setReLogin(true)} />; } return setReLogin(true)} />; @@ -341,9 +341,7 @@ export const ProSettingsContent: FC<{ showLogo?: boolean }> = ({ showLogo = true {t('tonkeeper_pro')} {t('tonkeeper_pro_description')} - {data && typeof data.auth === 'object' ? ( - } /> - ) : undefined} + {data && } ); }; diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index 2c9720ced..73e41fcf6 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -108,10 +108,8 @@ export const useCreateInvoiceMutation = () => { if (data.tierId === null) { throw new Error('missing tier'); } - if (typeof data.state.auth !== 'object') { - throw new Error('Invoice can be created only with wallet auth'); - } - const wallet = await getWalletState(sdk.storage, data.state.auth.publicKey); + + const wallet = await getWalletState(sdk.storage, data.state.wallet.publicKey); if (!wallet) { throw new Error('Missing wallet'); } From 20abca37874f4cb5b5f59bc1da39967d9b66a223 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 8 Mar 2024 16:10:07 +0100 Subject: [PATCH 09/27] fix: ton connect modal desktop view; Swap manifest.url and manifest.name in the connection modal --- .../connect/TonConnectNotification.tsx | 11 +- .../connect/TonTransactionNotification.tsx | 137 +++++++++--------- 2 files changed, 75 insertions(+), 73 deletions(-) diff --git a/packages/uikit/src/components/connect/TonConnectNotification.tsx b/packages/uikit/src/components/connect/TonConnectNotification.tsx index 51b74ef43..bcc0cddef 100644 --- a/packages/uikit/src/components/connect/TonConnectNotification.tsx +++ b/packages/uikit/src/components/connect/TonConnectNotification.tsx @@ -174,6 +174,13 @@ const ConnectContent: FC<{ const address = formatAddress(wallet.active.rawAddress, wallet.network); + let shortUrl = manifest.url; + try { + shortUrl = new URL(manifest.url).hostname; + } catch { + /* eslint-stub */ + } + return ( @@ -182,9 +189,9 @@ const ConnectContent: FC<{
- {t('ton_login_title').replace('%{name}', manifest.name)} + {t('ton_login_title').replace(/%\{name}|%domain/, shortUrl)} - {t('ton_login_caption').replace('%{name}', getDomain(manifest.url))}{' '} + {t('ton_login_caption').replace('%{name}', getDomain(manifest.name))}{' '}
{toShortValue(address)}
{' '} {walletVersionText(wallet.active.version)}
diff --git a/packages/uikit/src/components/connect/TonTransactionNotification.tsx b/packages/uikit/src/components/connect/TonTransactionNotification.tsx index 1226c3ba1..3c6ed41ba 100644 --- a/packages/uikit/src/components/connect/TonTransactionNotification.tsx +++ b/packages/uikit/src/components/connect/TonTransactionNotification.tsx @@ -14,43 +14,35 @@ import { useTranslation } from '../../hooks/translation'; import { QueryKey } from '../../libs/queryKey'; import { getMnemonic } from '../../state/mnemonic'; import { CheckmarkCircleIcon } from '../Icon'; -import { Notification, NotificationBlock } from '../Notification'; +import { + Notification, + NotificationBlock, + NotificationFooter, + NotificationFooterPortal +} from '../Notification'; import { SkeletonList } from '../Skeleton'; import { Label2 } from '../Text'; -import { Button, ButtonRow } from '../fields/Button'; +import { Button } from '../fields/Button'; import { ResultButton } from '../transfer/common'; import { EmulationList } from './EstimationLayout'; const ButtonGap = styled.div` height: 56px; + + ${props => + props.theme.displayType === 'full-width' && + css` + height: 1rem; + `} `; -const ButtonRowFixed = styled(ButtonRow)<{ standalone: boolean }>` - position: fixed; +const ButtonRowStyled = styled.div` + display: flex; + gap: 1rem; + width: 100%; - ${props => - props.standalone - ? css` - bottom: 32px; - ` - : css` - bottom: 16px; - `} - - padding: 0 16px; - box-sizing: border-box; - width: var(--app-width); - - &:after { - content: ''; - position: absolute; - width: 100%; - top: 0; - left: 0; - bottom: -32px; - height: calc(100% + 2rem); - z-index: -1; - background: ${props => props.theme.gradientBackgroundBottom}; + & > * { + flex: 1; } `; @@ -70,21 +62,24 @@ const useSendMutation = (params: TonConnectTransactionPayload, estimate?: Estima }; const NotificationSkeleton: FC<{ handleClose: (result?: string) => void }> = ({ handleClose }) => { - const { standalone } = useAppContext(); const { t } = useTranslation(); return ( - - - - + + + + + + + + ); }; @@ -94,7 +89,6 @@ const ConnectContent: FC<{ handleClose: (result?: string) => void; }> = ({ params, handleClose }) => { const sdk = useAppSdk(); - const { standalone } = useAppContext(); const [done, setDone] = useState(false); const { t } = useTranslation(); @@ -109,8 +103,7 @@ const ConnectContent: FC<{ sdk.hapticNotification('success'); }, []); - const onSubmit: React.FormEventHandler = async e => { - e.preventDefault(); + const onSubmit = async () => { const result = await mutateAsync(); setDone(true); sdk.hapticNotification('success'); @@ -122,40 +115,42 @@ const ConnectContent: FC<{ } return ( - + - - {done && ( - - - {t('ton_login_success')} - - )} - {!done && ( - <> - - - - )} - + + + {done && ( + + + {t('ton_login_success')} + + )} + {!done && ( + + + + + )} + + ); }; From 6881592211b9dd5b8a18c762b71c0e712166c694 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 8 Mar 2024 19:00:48 +0100 Subject: [PATCH 10/27] fix: trial start notification (modal) added --- packages/uikit/src/components/Icon.tsx | 33 ++++++++ .../uikit/src/components/pro/ProBanner.tsx | 22 ++++-- .../pro/ProTrialStartNotification.tsx | 75 +++++++++++++++++++ 3 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 packages/uikit/src/components/pro/ProTrialStartNotification.tsx diff --git a/packages/uikit/src/components/Icon.tsx b/packages/uikit/src/components/Icon.tsx index 2371f57ba..b93707b9b 100644 --- a/packages/uikit/src/components/Icon.tsx +++ b/packages/uikit/src/components/Icon.tsx @@ -822,3 +822,36 @@ export const SlidersIcon = () => { ); }; + +export const TelegramIcon: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + + + + + + + ); +}; diff --git a/packages/uikit/src/components/pro/ProBanner.tsx b/packages/uikit/src/components/pro/ProBanner.tsx index 853833e78..b16bd4048 100644 --- a/packages/uikit/src/components/pro/ProBanner.tsx +++ b/packages/uikit/src/components/pro/ProBanner.tsx @@ -2,12 +2,13 @@ import { FC } from 'react'; import styled from 'styled-components'; import { Body2, Label2 } from '../Text'; import { Button } from '../fields/Button'; -import { useActivateTrialMutation, useProState } from '../../state/pro'; +import { useProState } from '../../state/pro'; import { useDateTimeFormat } from '../../hooks/useDateTimeFormat'; import { ProNotification } from './ProNotification'; import { useDisclosure } from '../../hooks/useDisclosure'; import { useTranslation } from '../../hooks/translation'; import { isPaidSubscription, isTrialSubscription } from '@tonkeeper/core/dist/entries/pro'; +import { ProTrialStartNotification } from './ProTrialStartNotification'; const ProBannerStyled = styled.div` background: ${p => p.theme.backgroundContent}; @@ -36,11 +37,19 @@ const Label2Styled = styled(Label2)` `; export const ProBanner: FC<{ className?: string }> = ({ className }) => { + const { + isOpen: isTrialModalOpen, + onClose: onTrialModalClose, + onOpen: onTrialModalOpen + } = useDisclosure(); const { t } = useTranslation(); const formatDate = useDateTimeFormat(); - const { mutate } = useActivateTrialMutation(); const { data } = useProState(); - const { isOpen, onOpen, onClose } = useDisclosure(); + const { + isOpen: isPurchaseModalOpen, + onOpen: onPurchaseModalOpen, + onClose: onPurchaseModalClose + } = useDisclosure(); if (!data) { return null; @@ -72,17 +81,18 @@ export const ProBanner: FC<{ className?: string }> = ({ className }) => { ) : ( !subscription.usedTrial && ( - ) )} - - + + ); }; diff --git a/packages/uikit/src/components/pro/ProTrialStartNotification.tsx b/packages/uikit/src/components/pro/ProTrialStartNotification.tsx new file mode 100644 index 000000000..7d16b3fec --- /dev/null +++ b/packages/uikit/src/components/pro/ProTrialStartNotification.tsx @@ -0,0 +1,75 @@ +import { FC } from 'react'; +import { Notification } from '../Notification'; +import styled from 'styled-components'; +import { Body2, Label2 } from '../Text'; +import { Button } from '../fields/Button'; +import { useActivateTrialMutation } from '../../state/pro'; +import { TelegramIcon } from '../Icon'; + +const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + max-width: 360px; + margin: 0 auto; + padding-bottom: 8px; + + & > ${Body2} { + color: ${props => props.theme.textSecondary}; + } +`; + +const FooterStyled = styled.div` + padding: 1rem 0; +`; + +const ButtonStyled = styled(Button)` + display: flex; + gap: 0.5rem; + justify-content: center; +`; + +const ImageStyled = styled.img` + width: 56px; + height: 56px; + margin-bottom: 1rem; +`; + +export const ProTrialStartNotification: FC<{ isOpen: boolean; onClose: () => void }> = ({ + isOpen, + onClose +}) => { + const { mutateAsync, isLoading } = useActivateTrialMutation(); + + const onConfirm = async () => { + await mutateAsync(); + onClose(); + }; + + return ( + + + + Connect Telegram + + + } + > + {() => ( + + + Connect Telegram to Pro for Free + + Telegram connection is required solely for the purpose of verification that + you are not a bot. + + + )} + + ); +}; From 10b4f39565a6041b09d115a60d9b9723b114ce51 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 8 Mar 2024 19:06:10 +0100 Subject: [PATCH 11/27] chore: change telegram-oauth filename --- packages/core/src/service/proService.ts | 2 +- .../core/src/service/{telegram-oauth.ts => telegramOauth.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/core/src/service/{telegram-oauth.ts => telegramOauth.ts} (100%) diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index 60b73a7bf..241f564b3 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -27,7 +27,7 @@ import { delay } from '../utils/common'; import { createTonProofItem, tonConnectProofPayload } from './tonConnect/connectService'; import { walletStateInitFromState } from './wallet/contractService'; import { getWalletState } from './wallet/storeService'; -import { loginViaTG } from './telegram-oauth'; +import { loginViaTG } from './telegramOauth'; import { DashboardCell, DashboardColumn } from '../entries/dashboard'; import { FiatCurrencies } from '../entries/fiat'; import { Flatten } from '../utils/types'; diff --git a/packages/core/src/service/telegram-oauth.ts b/packages/core/src/service/telegramOauth.ts similarity index 100% rename from packages/core/src/service/telegram-oauth.ts rename to packages/core/src/service/telegramOauth.ts From ab640036a42abd4f90e3383900b376a203b66cc4 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 8 Mar 2024 19:26:41 +0100 Subject: [PATCH 12/27] fix: start trial modal i18n translations added --- packages/locales/src/tonkeeper-web/en.json | 3 +++ packages/locales/src/tonkeeper-web/ru-RU.json | 3 +++ .../src/components/pro/ProTrialStartNotification.tsx | 11 +++++------ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index bf9bfdb2e..b5d4ca2c1 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -18,6 +18,7 @@ "check_words_caption" : "To check whether you’ve written down your recovery phrase correctly, please enter the %1%, %2%, and  %3% words.", "ConfirmPassword" : "Re-enter password", "confirm_unlink" : "Confirm unlink", + "connect_telegram" : "Connect Telegram", "Copy_address" : "Copy address", "country" : "Country", "Create_password" : "Create password", @@ -66,6 +67,8 @@ "replace" : "Replace", "reset_tron_cache" : "Reset Tron cache", "save" : "Save", + "start_trial_notification_description" : "Telegram connection is required solely for the purpose of verification that\n you are not a bot.", + "start_trial_notification_heading" : "Connect Telegram to Pro for Free", "tme_linked_with_another_address_warn" : "The name is not linked to your current address. Be careful with transactions to this name.", "Toncoin" : "Toncoin", "Ton_page_description" : "TON is a fully decentralized layer-1 blockchain designed by Telegram to onboard billions of users. It boasts ultra-fast transactions, tiny fees, easy-to-use apps, and is environmentally friendly.", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index 506015c06..e642a0e2d 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -18,6 +18,7 @@ "check_words_caption" : "Чтобы убедиться, что вы записали секретный ключ правильно, введите слова %1%, %2% и %3%.", "ConfirmPassword" : "Введите пароль ещё раз", "confirm_unlink" : "Подтвердите отвязку", + "connect_telegram" : "Подключить Telegram", "Copy_address" : "Скопировать адрес", "country" : "Страна", "Create_password" : "Придумайте пароль", @@ -66,6 +67,8 @@ "replace" : "Заменить", "reset_tron_cache" : "Reset Tron cache", "save" : "Сохранять", + "start_trial_notification_description" : "Подключение к Telegram необходимо только для проверки, что вы не бот.", + "start_trial_notification_heading" : "Подключите Telegram для пробного периода Pro", "tme_linked_with_another_address_warn" : "Имя не связано с вашим текущим адресом. Будьте осторожными с транзакциями на это имя.", "Toncoin" : "Toncoin", "Ton_page_description" : "TON — это полностью децентрализованный блокчейн первого уровня, разработанный Telegram для миллиардов пользователей. Он может похвастаться сверхбыстрыми транзакциями, небольшими комиссиями, простыми в использовании приложениями и экологичностью.", diff --git a/packages/uikit/src/components/pro/ProTrialStartNotification.tsx b/packages/uikit/src/components/pro/ProTrialStartNotification.tsx index 7d16b3fec..db9223f87 100644 --- a/packages/uikit/src/components/pro/ProTrialStartNotification.tsx +++ b/packages/uikit/src/components/pro/ProTrialStartNotification.tsx @@ -5,6 +5,7 @@ import { Body2, Label2 } from '../Text'; import { Button } from '../fields/Button'; import { useActivateTrialMutation } from '../../state/pro'; import { TelegramIcon } from '../Icon'; +import { useTranslation } from '../../hooks/translation'; const ContentWrapper = styled.div` display: flex; @@ -40,6 +41,7 @@ export const ProTrialStartNotification: FC<{ isOpen: boolean; onClose: () => voi isOpen, onClose }) => { + const { t } = useTranslation(); const { mutateAsync, isLoading } = useActivateTrialMutation(); const onConfirm = async () => { @@ -55,7 +57,7 @@ export const ProTrialStartNotification: FC<{ isOpen: boolean; onClose: () => voi - Connect Telegram + {t('connect_telegram')} } @@ -63,11 +65,8 @@ export const ProTrialStartNotification: FC<{ isOpen: boolean; onClose: () => voi {() => ( - Connect Telegram to Pro for Free - - Telegram connection is required solely for the purpose of verification that - you are not a bot. - + {t('start_trial_notification_heading')} + {t('start_trial_notification_description')} )} From f05ebca4e2115edae749d32cda8ff2e7554bba61 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 8 Mar 2024 19:31:37 +0100 Subject: [PATCH 13/27] chore: i18n update --- packages/locales/src/tonkeeper-web/en.json | 2 +- packages/locales/src/tonkeeper-web/ru-RU.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index b5d4ca2c1..84450ab17 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -67,7 +67,7 @@ "replace" : "Replace", "reset_tron_cache" : "Reset Tron cache", "save" : "Save", - "start_trial_notification_description" : "Telegram connection is required solely for the purpose of verification that\n you are not a bot.", + "start_trial_notification_description" : "Telegram connection is required solely for the purpose of verification that you are not a bot.", "start_trial_notification_heading" : "Connect Telegram to Pro for Free", "tme_linked_with_another_address_warn" : "The name is not linked to your current address. Be careful with transactions to this name.", "Toncoin" : "Toncoin", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index e642a0e2d..1cb53543d 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -68,7 +68,7 @@ "reset_tron_cache" : "Reset Tron cache", "save" : "Сохранять", "start_trial_notification_description" : "Подключение к Telegram необходимо только для проверки, что вы не бот.", - "start_trial_notification_heading" : "Подключите Telegram для пробного периода Pro", + "start_trial_notification_heading" : "Подключите Telegram для пробного периода Pro", "tme_linked_with_another_address_warn" : "Имя не связано с вашим текущим адресом. Будьте осторожными с транзакциями на это имя.", "Toncoin" : "Toncoin", "Ton_page_description" : "TON — это полностью децентрализованный блокчейн первого уровня, разработанный Telegram для миллиардов пользователей. Он может похвастаться сверхбыстрыми транзакциями, небольшими комиссиями, простыми в использовании приложениями и экологичностью.", From 9c68a946b1cfef28f6f9b04b6d18c1e650202e63 Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Mon, 11 Mar 2024 10:56:00 +0100 Subject: [PATCH 14/27] Add REACT_APP_TG_BOT_ID to CI --- .github/workflows/cd.yaml | 1 + .github/workflows/pull-request.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 9b6afb73b..c92b15a76 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -18,6 +18,7 @@ jobs: APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE }} + REACT_APP_TG_BOT_ID: ${{ secrets.REACT_APP_TG_BOT_ID }} REACT_APP_TONCONSOLE_API: https://pro.tonconsole.com GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} DEBUG: electron* diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 4daea7e53..20d14a825 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -18,6 +18,7 @@ jobs: APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE }} + REACT_APP_TG_BOT_ID: ${{ secrets.REACT_APP_TG_BOT_ID }} REACT_APP_TONCONSOLE_API: https://pro.tonconsole.com DEBUG: electron* From 3bc8f204254c583f4301055cd1e2d6ef4d0b2d64 Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Mon, 11 Mar 2024 10:56:34 +0100 Subject: [PATCH 15/27] Update version --- apps/desktop/package.json | 2 +- apps/extension/package.json | 2 +- apps/web/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 332bd5b07..5da5eda6f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@tonkeeper/desktop", "license": "Apache-2.0", - "version": "3.7.1", + "version": "3.8.0", "description": "Your desktop wallet on The Open Network", "main": ".webpack/main", "repository": { diff --git a/apps/extension/package.json b/apps/extension/package.json index b6930e78d..e1b74b89f 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/extension", - "version": "3.7.1", + "version": "3.8.0", "author": "Nikita Kuznetsov ", "description": "Your extension wallet on The Open Network", "dependencies": { diff --git a/apps/web/package.json b/apps/web/package.json index ed9382013..173208716 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/web", - "version": "3.7.1", + "version": "3.8.0", "author": "Nikita Kuznetsov ", "description": "Your web wallet on The Open Network", "dependencies": { From 93f1a1dbba977e13a55f05cf3a25513c2688379e Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 11 Mar 2024 12:05:43 +0100 Subject: [PATCH 16/27] chore: pipeline env vars added --- .github/workflows/cd.yaml | 2 ++ .github/workflows/pull-request.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 9b6afb73b..bd7e820a5 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -19,6 +19,8 @@ jobs: APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE }} REACT_APP_TONCONSOLE_API: https://pro.tonconsole.com + REACT_APP_TG_BOT_ID: 6345183204 + REACT_APP_TG_BOT_ORIGIN: https://tonkeeper.com GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} DEBUG: electron* diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 4daea7e53..544745fc0 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -19,6 +19,8 @@ jobs: APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE }} REACT_APP_TONCONSOLE_API: https://pro.tonconsole.com + REACT_APP_TG_BOT_ID: 6345183204 + REACT_APP_TG_BOT_ORIGIN: https://tonkeeper.com DEBUG: electron* steps: From 871bd1ee3d5731588bd0eee01b27229c933ca8f3 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 11 Mar 2024 12:06:09 +0100 Subject: [PATCH 17/27] chore: pipeline env vars added --- .github/workflows/cd.yaml | 1 - .github/workflows/pull-request.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index bc7937cd1..32f42380a 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -20,7 +20,6 @@ jobs: REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE }} REACT_APP_TG_BOT_ID: ${{ secrets.REACT_APP_TG_BOT_ID }} REACT_APP_TONCONSOLE_API: https://pro.tonconsole.com - REACT_APP_TG_BOT_ID: 6345183204 REACT_APP_TG_BOT_ORIGIN: https://tonkeeper.com GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} DEBUG: electron* diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 6c179b916..e33afddaf 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -20,7 +20,6 @@ jobs: REACT_APP_AMPLITUDE: ${{ secrets.REACT_APP_AMPLITUDE }} REACT_APP_TG_BOT_ID: ${{ secrets.REACT_APP_TG_BOT_ID }} REACT_APP_TONCONSOLE_API: https://pro.tonconsole.com - REACT_APP_TG_BOT_ID: 6345183204 REACT_APP_TG_BOT_ORIGIN: https://tonkeeper.com DEBUG: electron* From 6150e13647b169c206817ea45b74d1310ca29e23 Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Mon, 11 Mar 2024 13:15:12 +0100 Subject: [PATCH 18/27] Show menu bar --- apps/desktop/src/electron/mainWindow.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/desktop/src/electron/mainWindow.ts b/apps/desktop/src/electron/mainWindow.ts index cb2fa41af..491e0f08a 100644 --- a/apps/desktop/src/electron/mainWindow.ts +++ b/apps/desktop/src/electron/mainWindow.ts @@ -1,5 +1,5 @@ import { delay } from '@tonkeeper/core/dist/utils/common'; -import { BrowserWindow, ipcMain, shell } from 'electron'; +import { BrowserWindow, ipcMain } from 'electron'; import isDev from 'electron-is-dev'; import path from 'path'; import { Cookie, CookieJar } from 'tough-cookie'; @@ -43,7 +43,6 @@ export abstract class MainWindow { height: 750, minHeight: 700, resizable: isDev, - autoHideMenuBar: process.platform !== 'darwin', webPreferences: { zoomFactor: process.platform !== 'linux' ? 0.8 : undefined, preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY From a97e9e8f709bdf5253b82e5afc679d447281fce1 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 11 Mar 2024 13:49:43 +0100 Subject: [PATCH 19/27] fix: ton connect modal didn't open in dashboard page --- apps/desktop/src/app/App.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 4e053a9c7..c6e9c86da 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -272,7 +272,7 @@ export const Loader: FC = () => { proFeatures: true, ios: false, env: { - tgAuthBotId: REACT_APP_TG_BOT_ID + tgAuthBotId: REACT_APP_TG_BOT_ID } }; @@ -336,6 +336,7 @@ export const Content: FC<{ } /> + ); @@ -370,6 +371,13 @@ const OldAppRouting = () => { + + ); +}; + +const BackgroundElements = () => { + return ( + <> @@ -378,6 +386,6 @@ const OldAppRouting = () => { - + ); }; From 05c89f4c6a810f206050e6de53a7911753cb8701 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 11 Mar 2024 13:56:22 +0100 Subject: [PATCH 20/27] fix: locales update --- packages/locales/src/tonkeeper-web/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index 84450ab17..ead76bf7c 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -52,7 +52,7 @@ "PasswordChanged" : "Password Changed", "PasswordDoNotMatch" : "Passwords do not match.", "pro_banner_buy" : "Buy Pro", - "pro_banner_days_left" : "Trial ends in %days% days", + "pro_banner_days_left" : "Trial ends in %days%", "pro_banner_start_trial" : "Try Pro for Free", "pro_banner_subtitle" : "Access advanced features and tools to boost your work", "pro_banner_title" : "Get more with Tonkeeper Pro", From 87020530480dbfe4aad7f7497b18a89578d5e196 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 11 Mar 2024 14:23:51 +0100 Subject: [PATCH 21/27] fix: dashboard skeleton added --- .../components/dashboard/DashboardTable.tsx | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/packages/uikit/src/components/dashboard/DashboardTable.tsx b/packages/uikit/src/components/dashboard/DashboardTable.tsx index 655289db9..3cae171e1 100644 --- a/packages/uikit/src/components/dashboard/DashboardTable.tsx +++ b/packages/uikit/src/components/dashboard/DashboardTable.tsx @@ -3,8 +3,10 @@ import { FC, useEffect, useRef, useState } from 'react'; import { Body2 } from '../Text'; import { useDashboardColumnsAsForm } from '../../state/dashboard/useDashboardColumns'; import { useDashboardData } from '../../state/dashboard/useDashboardData'; -import { DashboardCell } from './columns/DashboardCell'; import { DashboardCellAddress, DashboardColumnType } from '@tonkeeper/core/dist/entries/dashboard'; +import { Skeleton } from '../shared/Skeleton'; +import { useAppContext } from '../../hooks/appContext'; +import { DashboardCell } from './columns/DashboardCell'; const TableStyled = styled.table` width: 100%; @@ -98,6 +100,8 @@ const isNumericColumn = (columnType: DashboardColumnType): boolean => { export const DashboardTable: FC<{ className?: string }> = ({ className }) => { const { data: columns } = useDashboardColumnsAsForm(); const { data: dashboardData } = useDashboardData(); + const { account } = useAppContext(); + const publicKeys = account?.publicKeys; const [isResizing, setIsResizing] = useState(false); const [hoverOnColumn, setHoverOnColumn] = useState(undefined); @@ -152,7 +156,7 @@ export const DashboardTable: FC<{ className?: string }> = ({ className }) => { return hoverOnColumn !== undefined && hoverOnColumn >= i && hoverOnColumn <= i + 1; }; - if (!columns || !dashboardData) { + if (!columns) { return null; } @@ -198,24 +202,42 @@ export const DashboardTable: FC<{ className?: string }> = ({ className }) => { - {dashboardData.map((dataRow, index) => ( - - {dataRow.map((cell, i) => ( - c.type === 'address' - ) as DashboardCellAddress - )?.raw || i.toString() - } - textAlign={isNumericColumn(cell.type) ? 'right' : undefined} - > - - - ))} - - ))} + {dashboardData + ? dashboardData.map((dataRow, index) => ( + + {dataRow.map((cell, i) => ( + c.type === 'address' + ) as DashboardCellAddress + )?.raw || i.toString() + } + textAlign={isNumericColumn(cell.type) ? 'right' : undefined} + > + + + ))} + + )) + : (publicKeys || [1, 2, 3]).map(key => ( + + {selectedColumns.map((col, colIndex) => ( + + + + ))} + + ))} ); From 87718e3eaddaf9fa7b2cfda17160ead5c5e22808 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 11 Mar 2024 15:03:45 +0100 Subject: [PATCH 22/27] fix: dashboard wallets filtered, only mainnet are shown --- .../components/dashboard/DashboardTable.tsx | 13 ++++--- packages/uikit/src/libs/queryKey.ts | 1 + .../src/state/dashboard/useDashboardData.ts | 38 +++++++++---------- packages/uikit/src/state/wallet.ts | 9 +++++ 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/packages/uikit/src/components/dashboard/DashboardTable.tsx b/packages/uikit/src/components/dashboard/DashboardTable.tsx index 3cae171e1..b371250ff 100644 --- a/packages/uikit/src/components/dashboard/DashboardTable.tsx +++ b/packages/uikit/src/components/dashboard/DashboardTable.tsx @@ -5,8 +5,9 @@ import { useDashboardColumnsAsForm } from '../../state/dashboard/useDashboardCol import { useDashboardData } from '../../state/dashboard/useDashboardData'; import { DashboardCellAddress, DashboardColumnType } from '@tonkeeper/core/dist/entries/dashboard'; import { Skeleton } from '../shared/Skeleton'; -import { useAppContext } from '../../hooks/appContext'; import { DashboardCell } from './columns/DashboardCell'; +import { useWalletsState } from '../../state/wallet'; +import { Network } from '@tonkeeper/core/dist/entries/network'; const TableStyled = styled.table` width: 100%; @@ -100,8 +101,10 @@ const isNumericColumn = (columnType: DashboardColumnType): boolean => { export const DashboardTable: FC<{ className?: string }> = ({ className }) => { const { data: columns } = useDashboardColumnsAsForm(); const { data: dashboardData } = useDashboardData(); - const { account } = useAppContext(); - const publicKeys = account?.publicKeys; + const { data: wallets, isFetched: isWalletsFetched } = useWalletsState(); + const mainnetPubkeys = wallets + ?.filter(w => w?.network === Network.MAINNET) + .map(w => w!.publicKey); const [isResizing, setIsResizing] = useState(false); const [hoverOnColumn, setHoverOnColumn] = useState(undefined); @@ -156,7 +159,7 @@ export const DashboardTable: FC<{ className?: string }> = ({ className }) => { return hoverOnColumn !== undefined && hoverOnColumn >= i && hoverOnColumn <= i + 1; }; - if (!columns) { + if (!columns || !isWalletsFetched) { return null; } @@ -221,7 +224,7 @@ export const DashboardTable: FC<{ className?: string }> = ({ className }) => { ))} )) - : (publicKeys || [1, 2, 3]).map(key => ( + : (mainnetPubkeys || [1, 2, 3]).map(key => ( {selectedColumns.map((col, colIndex) => ( diff --git a/packages/uikit/src/libs/queryKey.ts b/packages/uikit/src/libs/queryKey.ts index c9a7d295e..556d72159 100644 --- a/packages/uikit/src/libs/queryKey.ts +++ b/packages/uikit/src/libs/queryKey.ts @@ -1,6 +1,7 @@ export enum QueryKey { account = 'account', wallet = 'wallet', + wallets = 'wallets', lock = 'lock', country = 'country', password = 'password', diff --git a/packages/uikit/src/state/dashboard/useDashboardData.ts b/packages/uikit/src/state/dashboard/useDashboardData.ts index 501dd5905..dc97a5685 100644 --- a/packages/uikit/src/state/dashboard/useDashboardData.ts +++ b/packages/uikit/src/state/dashboard/useDashboardData.ts @@ -5,33 +5,34 @@ import { useAppContext } from '../../hooks/appContext'; import { DashboardCell } from '@tonkeeper/core/dist/entries/dashboard'; import { getDashboardData } from '@tonkeeper/core/dist/service/proService'; import { useTranslation } from '../../hooks/translation'; -import { useAppSdk } from '../../hooks/appSdk'; -import { getWalletState } from '@tonkeeper/core/dist/service/wallet/storeService'; import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; import { Address } from 'ton-core'; +import { Network } from '@tonkeeper/core/dist/entries/network'; +import { useWalletsState } from '../wallet'; export function useDashboardData() { const { data: columns } = useDashboardColumnsAsForm(); const selectedColumns = columns?.filter(c => c.isEnabled); - const { fiat, account } = useAppContext(); + const { fiat } = useAppContext(); const { i18n: { language }, t } = useTranslation(); const selectedColIds = selectedColumns?.map(c => c.id); - const publicKeys = account?.publicKeys; - const { storage } = useAppSdk(); const client = useQueryClient(); + const { data: walletsState } = useWalletsState(); + const mainnetWallets = walletsState?.filter(w => w?.network === Network.MAINNET); + const publicKeysMainnet = mainnetWallets?.map(w => w!.publicKey); + return useQuery( - [QueryKey.dashboardData, selectedColIds, publicKeys, fiat, language], + [QueryKey.dashboardData, selectedColIds, publicKeysMainnet, fiat, language], async ctx => { - if (!selectedColIds?.length || !publicKeys?.length) { + if (!selectedColIds?.length || !publicKeysMainnet?.length || !mainnetWallets?.length) { return []; } - const wallets = await Promise.all(publicKeys.map(pk => getWalletState(storage, pk))); - const accounts = wallets.map(acc => acc!.active.friendlyAddress); + const accounts = mainnetWallets.map(acc => acc!.active.friendlyAddress); const loadData = async (query: { columns: string[]; accounts: string[] }) => { const queryToFetch = { @@ -51,7 +52,7 @@ export function useDashboardData() { const defaultWalletName = t('wallet_title'); const result: DashboardCell[][] = query.accounts.map(() => []); query.accounts.forEach((walletAddress, rowIndex) => { - const wallet = wallets.find(w => + const wallet = mainnetWallets.find(w => Address.parse(w!.active.friendlyAddress).equals( Address.parse(walletAddress) ) @@ -91,8 +92,8 @@ export function useDashboardData() { const walletsToQuerySet = new Set(); const columnsToQuerySet = new Set(); - const result: (DashboardCell | null)[][] = publicKeys.map(() => []); - publicKeys.forEach((pk, walletIndex) => { + const result: (DashboardCell | null)[][] = publicKeysMainnet.map(() => []); + publicKeysMainnet.forEach((pk, walletIndex) => { selectedColIds.forEach((col, colIndex) => { const matchingQueries = pastQueries.filter( ([key, _]) => @@ -102,7 +103,7 @@ export function useDashboardData() { if (!matchingQueries.length) { result[walletIndex][colIndex] = null; - walletsToQuerySet.add(wallets[walletIndex]!); + walletsToQuerySet.add(mainnetWallets[walletIndex]!); columnsToQuerySet.add(col); return; } @@ -119,10 +120,7 @@ export function useDashboardData() { }); const walletsToQuery = [...walletsToQuerySet.values()]; - let accountsToQuery = walletsToQuery.map(acc => acc.active.friendlyAddress); - if (columnsToQuerySet.size > 0) { - accountsToQuery = accounts; - } + const accountsToQuery = walletsToQuery.map(acc => acc.active.friendlyAddress); const columnsToQuery = [...columnsToQuerySet.values()]; if (!accountsToQuery.length || !columnsToQuery.length) { @@ -135,7 +133,9 @@ export function useDashboardData() { }); newData.forEach((row, rowIndex) => { - const walletIndex = publicKeys.indexOf(walletsToQuery[rowIndex].publicKey); + const walletIndex = publicKeysMainnet.indexOf( + walletsToQuery[rowIndex].publicKey + ); row.forEach(cell => { const colIndex = selectedColIds.indexOf(cell.columnId); result[walletIndex][colIndex] = cell; @@ -149,7 +149,7 @@ export function useDashboardData() { return loadData({ accounts, columns: selectedColIds }); }, { - enabled: !!selectedColIds && !!publicKeys + enabled: !!selectedColIds && !!mainnetWallets } ); } diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 5b03a8c2f..dca0b5a92 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -41,6 +41,15 @@ export const useWalletState = (publicKey: string) => { ); }; +export const useWalletsState = () => { + const { account } = useAppContext(); + const sdk = useAppSdk(); + return useQuery<(WalletState | null)[], Error>( + [QueryKey.account, QueryKey.wallets, account.publicKeys], + () => Promise.all(account.publicKeys.map(key => getWalletState(sdk.storage, key))) + ); +}; + export const useMutateLogOut = (publicKey: string, remove = false) => { const sdk = useAppSdk(); const client = useQueryClient(); From fd4b1f2db7a05d6f9aba84a55e637c9ec5534287 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 11 Mar 2024 18:59:17 +0100 Subject: [PATCH 23/27] feat: emoji picker added --- packages/core/src/entries/wallet.ts | 1 + .../uikit/src/components/fields/Input.tsx | 24 +- .../settings/WalletNameNotification.tsx | 71 - .../wallet-name/WalletNameNotification.tsx | 160 ++ .../components/settings/wallet-name/emojis.ts | 1642 +++++++++++++++++ packages/uikit/src/hooks/useIsScrolled.ts | 46 + packages/uikit/src/pages/settings/Account.tsx | 2 +- packages/uikit/src/state/wallet.ts | 6 +- 8 files changed, 1872 insertions(+), 80 deletions(-) delete mode 100644 packages/uikit/src/components/settings/WalletNameNotification.tsx create mode 100644 packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx create mode 100644 packages/uikit/src/components/settings/wallet-name/emojis.ts create mode 100644 packages/uikit/src/hooks/useIsScrolled.ts diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index cc5e5972b..b56b56b32 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -56,6 +56,7 @@ export interface WalletState { active: WalletAddress; name?: string; + emoji?: string; revision: number; diff --git a/packages/uikit/src/components/fields/Input.tsx b/packages/uikit/src/components/fields/Input.tsx index 75f09b9f0..e5f7cc23e 100644 --- a/packages/uikit/src/components/fields/Input.tsx +++ b/packages/uikit/src/components/fields/Input.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { ReactNode, useState } from 'react'; import styled, { css } from 'styled-components'; import { XmarkIcon } from '../Icon'; import { Body2 } from '../Text'; @@ -63,7 +63,7 @@ export const InputBlock = styled.div<{ `} `; -export const InputField = styled.input` +export const InputField = styled.input<{ marginRight?: string }>` outline: none; border: none; background: transparent; @@ -74,6 +74,11 @@ export const InputField = styled.input` box-sizing: border-box; color: ${props => props.theme.textPrimary}; + ${props => + props.marginRight && + css` + margin-right: ${props.marginRight}; + `}; `; export const Label = styled.label<{ active?: boolean }>` @@ -116,12 +121,15 @@ export const HelpText = styled(Body2)<{ valid: boolean }>` `} `; -const ClearBlock = styled.div` +const RightBlock = styled.div` position: absolute; right: 1rem; height: 100%; display: flex; align-items: center; +`; + +const ClearBlock = styled(RightBlock)` cursor: pointer; color: ${props => props.theme.textSecondary}; @@ -142,6 +150,8 @@ export interface InputProps { helpText?: string; tabIndex?: number; clearButton?: boolean; + rightElement?: ReactNode; + marginRight?: string; } export const Input = React.forwardRef( @@ -156,7 +166,9 @@ export const Input = React.forwardRef( disabled, helpText, tabIndex, - clearButton + clearButton, + rightElement, + marginRight }, ref ) => { @@ -184,12 +196,14 @@ export const Input = React.forwardRef( value={value} spellCheck={false} tabIndex={tabIndex} + marginRight={marginRight} onChange={e => onChange && onChange(e.target.value)} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} /> {label && } - {!!value && clearButton && ( + {rightElement && {rightElement}} + {!!value && clearButton && !rightElement && ( diff --git a/packages/uikit/src/components/settings/WalletNameNotification.tsx b/packages/uikit/src/components/settings/WalletNameNotification.tsx deleted file mode 100644 index 1fcf41e03..000000000 --- a/packages/uikit/src/components/settings/WalletNameNotification.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; -import { formatAddress } from '@tonkeeper/core/dist/utils/common'; -import React, { FC, useCallback, useState } from 'react'; -import { useTranslation } from '../../hooks/translation'; -import { useMutateRenameWallet } from '../../state/wallet'; -import { Notification, NotificationBlock } from '../Notification'; -import { Button } from '../fields/Button'; -import { Input } from '../fields/Input'; - -const RenameWalletContent: FC<{ - wallet: WalletState; - afterClose: (action: () => void) => void; -}> = ({ afterClose, wallet }) => { - const { t } = useTranslation(); - - const { mutateAsync, isLoading, isError } = useMutateRenameWallet(wallet); - - const [name, setName] = useState(wallet.name ?? ''); - const onSubmit: React.FormEventHandler = async e => { - e.preventDefault(); - await mutateAsync(name); - afterClose(() => null); - }; - - const address = formatAddress(wallet.active.rawAddress, wallet.network); - - return ( - - - - - - - ); -}; - -export const RenameWalletNotification: FC<{ - wallet?: WalletState; - handleClose: () => void; -}> = ({ wallet, handleClose }) => { - const { t } = useTranslation(); - - const Content = useCallback( - (afterClose: (action: () => void) => void) => { - if (!wallet) return undefined; - return ; - }, - [wallet] - ); - - return ( - - {Content} - - ); -}; diff --git a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx new file mode 100644 index 000000000..1f15eb53c --- /dev/null +++ b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx @@ -0,0 +1,160 @@ +import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; +import { formatAddress } from '@tonkeeper/core/dist/utils/common'; +import React, { FC, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from '../../../hooks/translation'; +import { useMutateRenameWallet } from '../../../state/wallet'; +import { Notification, NotificationBlock } from '../../Notification'; +import { Button } from '../../fields/Button'; +import { Input } from '../../fields/Input'; +import styled from 'styled-components'; +import { emojis } from './emojis'; + +const EmojisListScroll = styled.div` + max-height: 240px; + display: flex; + flex-wrap: wrap; + align-items: center; + overflow: auto; + position: relative; + + &::-webkit-scrollbar { + display: none; + width: 0; + background: transparent; + height: 0; + } + + -ms-overflow-style: none; + scrollbar-width: none; +`; + +const Shadow = styled.div` + position: sticky; + width: 100%; + height: 16px; +`; + +const ShadowBottom = styled(Shadow)` + bottom: -1px; + background: ${props => props.theme.gradientBackgroundBottom}; +`; + +const ShadowTop = styled(Shadow)` + top: 0; + background: ${props => props.theme.gradientBackgroundTop}; +`; + +const EmojiWrapper = styled.div` + height: 32px; + width: 32px; + line-height: 24px; + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +`; + +const EmojiButton = styled(EmojiWrapper)` + cursor: pointer; +`; + +const shortEmojisList = emojis.slice(0, 150); + +const EmojisList: FC<{ onClick: (emoji: string) => void; keepShortListForMS?: number }> = + React.memo(({ onClick, keepShortListForMS }) => { + const [emojisList, setEmojisList] = useState(keepShortListForMS ? shortEmojisList : emojis); + + useEffect(() => { + if (keepShortListForMS) { + setTimeout(() => setEmojisList(emojis), keepShortListForMS); + } + }, []); + + return ( + + + {emojisList.map(emoji => ( + onClick(emoji)}> + {emoji} + + ))} + + + ); + }); + +const RenameWalletContent: FC<{ + wallet: WalletState; + afterClose: (action: () => void) => void; + animationTime?: number; +}> = ({ animationTime, afterClose, wallet }) => { + const { t } = useTranslation(); + + const { mutateAsync, isLoading, isError } = useMutateRenameWallet(wallet); + + const [name, setName] = useState(wallet.name ?? ''); + const [emoji, setEmoji] = useState(wallet.emoji ?? ''); + const onSubmit: React.FormEventHandler = async e => { + e.preventDefault(); + await mutateAsync({ name, emoji }); + afterClose(() => null); + }; + + const address = formatAddress(wallet.active.rawAddress, wallet.network); + + return ( + + + {emoji} : null} + marginRight="36px" + /> + + + + + ); +}; + +export const RenameWalletNotification: FC<{ + wallet?: WalletState; + handleClose: () => void; +}> = ({ wallet, handleClose }) => { + const { t } = useTranslation(); + + const Content = useCallback( + (afterClose: (action: () => void) => void) => { + if (!wallet) return undefined; + return ( + + ); + }, + [wallet] + ); + + return ( + + {Content} + + ); +}; diff --git a/packages/uikit/src/components/settings/wallet-name/emojis.ts b/packages/uikit/src/components/settings/wallet-name/emojis.ts new file mode 100644 index 000000000..ef8d67978 --- /dev/null +++ b/packages/uikit/src/components/settings/wallet-name/emojis.ts @@ -0,0 +1,1642 @@ +export const emojis = [ + '😀', + '😃', + '😄', + '😁', + '😆', + '😅', + '🤣', + '😂', + '🙂', + '🙃', + '😉', + '😊', + '😇', + '🥰', + '😍', + '🤩', + '😘', + '😗', + '☺️', + '😚', + '😙', + '😋', + '😛', + '😜', + '🤪', + '😝', + '🤑', + '🤗', + '🤭', + '🤫', + '🤔', + '🤐', + '🤨', + '😐', + '😑', + '😶', + '😏', + '😒', + '🙄', + '😬', + '🤥', + '😌', + '😔', + '😪', + '🤤', + '😴', + '😷', + '🤒', + '🤕', + '🤢', + '🤮', + '🤧', + '🥵', + '🥶', + '🥴', + '😵', + '🤯', + '🤠', + '🥳', + '😎', + '🤓', + '🧐', + '😕', + '😟', + '🙁', + '☹️', + '😮', + '😯', + '😲', + '😳', + '🥺', + '😦', + '😧', + '😨', + '😰', + '😥', + '😢', + '😭', + '😱', + '😖', + '😣', + '😞', + '😓', + '😩', + '😫', + '😤', + '😡', + '😠', + '🤬', + '😈', + '👿', + '💀', + '☠️', + '💩', + '🤡', + '👹', + '👺', + '👻', + '👽', + '👾', + '🤖', + '😺', + '😸', + '😹', + '😻', + '😼', + '😽', + '🙀', + '😿', + '😾', + '🙈', + '🙉', + '🙊', + '💌', + '💘', + '💝', + '💖', + '💗', + '💓', + '💞', + '💕', + '💟', + '❣️', + '💔', + '❤️', + '🧡', + '💛', + '💚', + '💙', + '💜', + '🖤', + '💋', + '💯', + '💢', + '💥', + '💫', + '💦', + '💨', + '🕳️', + '💬', + '👁️‍🗨️', + '🗨️', + '🗯️', + '💭', + '💤', + '👋', + '🤚', + '🖐️', + '✋', + '🖖', + '👌', + '✌️', + '🤞', + '🤟', + '🤘', + '🤙', + '👈', + '👉', + '👆', + '🖕', + '👇', + '☝️', + '👍', + '👎', + '✊', + '👊', + '🤛', + '🤜', + '👏', + '🙌', + '👐', + '🤲', + '🤝', + '🙏', + '✍️', + '💅', + '🤳', + '💪', + '🦵', + '🦶', + '👂', + '👃', + '🧠', + '🦷', + '🦴', + '👀', + '👁️', + '👅', + '👄', + '👶', + '🧒', + '👦', + '👧', + '🧑', + '👱', + '👨', + '🧔', + '👨‍🦰', + '👨‍🦱', + '👨‍🦳', + '👨‍🦲', + '👩', + '👩‍🦰', + '👩‍🦱', + '👩‍🦳', + '👩‍🦲', + '👱‍♀️', + '👱‍♂️', + '🧓', + '👴', + '👵', + '🙍', + '🙍‍♂️', + '🙍‍♀️', + '🙎', + '🙎‍♂️', + '🙎‍♀️', + '🙅', + '🙅‍♂️', + '🙅‍♀️', + '🙆', + '🙆‍♂️', + '🙆‍♀️', + '💁', + '💁‍♂️', + '💁‍♀️', + '🙋', + '🙋‍♂️', + '🙋‍♀️', + '🙇', + '🙇‍♂️', + '🙇‍♀️', + '🤦', + '🤦‍♂️', + '🤦‍♀️', + '🤷', + '🤷‍♂️', + '🤷‍♀️', + '👨‍⚕️', + '👩‍⚕️', + '👨‍🎓', + '👩‍🎓', + '👨‍🏫', + '👩‍🏫', + '👨‍⚖️', + '👩‍⚖️', + '👨‍🌾', + '👩‍🌾', + '👨‍🍳', + '👩‍🍳', + '👨‍🔧', + '👩‍🔧', + '👨‍🏭', + '👩‍🏭', + '👨‍💼', + '👩‍💼', + '👨‍🔬', + '👩‍🔬', + '👨‍💻', + '👩‍💻', + '👨‍🎤', + '👩‍🎤', + '👨‍🎨', + '👩‍🎨', + '👨‍✈️', + '👩‍✈️', + '👨‍🚀', + '👩‍🚀', + '👨‍🚒', + '👩‍🚒', + '👮', + '👮‍♂️', + '👮‍♀️', + '🕵️', + '🕵️‍♂️', + '🕵️‍♀️', + '💂', + '💂‍♂️', + '💂‍♀️', + '👷', + '👷‍♂️', + '👷‍♀️', + '🤴', + '👸', + '👳', + '👳‍♂️', + '👳‍♀️', + '👲', + '🧕', + '🤵', + '👰', + '🤰', + '🤱', + '👼', + '🎅', + '🤶', + '🦸', + '🦸‍♂️', + '🦸‍♀️', + '🦹', + '🦹‍♂️', + '🦹‍♀️', + '🧙', + '🧙‍♂️', + '🧙‍♀️', + '🧚', + '🧚‍♂️', + '🧚‍♀️', + '🧛', + '🧛‍♂️', + '🧛‍♀️', + '🧜', + '🧜‍♂️', + '🧜‍♀️', + '🧝', + '🧝‍♂️', + '🧝‍♀️', + '🧞', + '🧞‍♂️', + '🧞‍♀️', + '🧟', + '🧟‍♂️', + '🧟‍♀️', + '💆', + '💆‍♂️', + '💆‍♀️', + '💇', + '💇‍♂️', + '💇‍♀️', + '🚶', + '🚶‍♂️', + '🚶‍♀️', + '🏃', + '🏃‍♂️', + '🏃‍♀️', + '💃', + '🕺', + '🕴️', + '👯', + '👯‍♂️', + '👯‍♀️', + '🧖', + '🧖‍♂️', + '🧖‍♀️', + '🧗', + '🧗‍♂️', + '🧗‍♀️', + '🤺', + '🏇', + '⛷️', + '🏂', + '🏌️', + '🏌️‍♂️', + '🏌️‍♀️', + '🏄', + '🏄‍♂️', + '🏄‍♀️', + '🚣', + '🚣‍♂️', + '🚣‍♀️', + '🏊', + '🏊‍♂️', + '🏊‍♀️', + '⛹️', + '⛹️‍♂️', + '⛹️‍♀️', + '🏋️', + '🏋️‍♂️', + '🏋️‍♀️', + '🚴', + '🚴‍♂️', + '🚴‍♀️', + '🚵', + '🚵‍♂️', + '🚵‍♀️', + '🤸', + '🤸‍♂️', + '🤸‍♀️', + '🤼', + '🤼‍♂️', + '🤼‍♀️', + '🤽', + '🤽‍♂️', + '🤽‍♀️', + '🤾', + '🤾‍♂️', + '🤾‍♀️', + '🤹', + '🤹‍♂️', + '🤹‍♀️', + '🧘', + '🧘‍♂️', + '🧘‍♀️', + '🛀', + '🛌', + '👭', + '👫', + '👬', + '💏', + '👩‍❤️‍💋‍👨', + '👨‍❤️‍💋‍👨', + '👩‍❤️‍💋‍👩', + '💑', + '👩‍❤️‍👨', + '👨‍❤️‍👨', + '👩‍❤️‍👩', + '👪', + '👨‍👩‍👦', + '👨‍👩‍👧', + '👨‍👩‍👧‍👦', + '👨‍👩‍👦‍👦', + '👨‍👩‍👧‍👧', + '👨‍👨‍👦', + '👨‍👨‍👧', + '👨‍👨‍👧‍👦', + '👨‍👨‍👦‍👦', + '👨‍👨‍👧‍👧', + '👩‍👩‍👦', + '👩‍👩‍👧', + '👩‍👩‍👧‍👦', + '👩‍👩‍👦‍👦', + '👩‍👩‍👧‍👧', + '👨‍👦', + '👨‍👦‍👦', + '👨‍👧', + '👨‍👧‍👦', + '👨‍👧‍👧', + '👩‍👦', + '👩‍👦‍👦', + '👩‍👧', + '👩‍👧‍👦', + '👩‍👧‍👧', + '🗣️', + '👤', + '👥', + '👣', + '🐵', + '🐒', + '🦍', + '🐶', + '🐕', + '🐩', + '🐺', + '🦊', + '🦝', + '🐱', + '🐈', + '🦁', + '🐯', + '🐅', + '🐆', + '🐴', + '🐎', + '🦄', + '🦓', + '🦌', + '🐮', + '🐂', + '🐃', + '🐄', + '🐷', + '🐖', + '🐗', + '🐽', + '🐏', + '🐑', + '🐐', + '🐪', + '🐫', + '🦙', + '🦒', + '🐘', + '🦏', + '🦛', + '🐭', + '🐁', + '🐀', + '🐹', + '🐰', + '🐇', + '🐿️', + '🦔', + '🦇', + '🐻', + '🐨', + '🐼', + '🦘', + '🦡', + '🐾', + '🦃', + '🐔', + '🐓', + '🐣', + '🐤', + '🐥', + '🐦', + '🐧', + '🕊️', + '🦅', + '🦆', + '🦢', + '🦉', + '🦚', + '🦜', + '🐸', + '🐊', + '🐢', + '🦎', + '🐍', + '🐲', + '🐉', + '🦕', + '🦖', + '🐳', + '🐋', + '🐬', + '🐟', + '🐠', + '🐡', + '🦈', + '🐙', + '🐚', + '🐌', + '🦋', + '🐛', + '🐜', + '🐝', + '🐞', + '🦗', + '🕷️', + '🕸️', + '🦂', + '🦟', + '🦠', + '💐', + '🌸', + '💮', + '🏵️', + '🌹', + '🥀', + '🌺', + '🌻', + '🌼', + '🌷', + '🌱', + '🌲', + '🌳', + '🌴', + '🌵', + '🌾', + '🌿', + '☘️', + '🍀', + '🍁', + '🍂', + '🍃', + '🍄', + '🍇', + '🍈', + '🍉', + '🍊', + '🍋', + '🍌', + '🍍', + '🥭', + '🍎', + '🍏', + '🍐', + '🍑', + '🍒', + '🍓', + '🥝', + '🍅', + '🥥', + '🥑', + '🍆', + '🥔', + '🥕', + '🌽', + '🌶️', + '🥒', + '🥬', + '🥦', + '🥜', + '🌰', + '🍞', + '🥐', + '🥖', + '🥨', + '🥯', + '🥞', + '🧀', + '🍖', + '🍗', + '🥩', + '🥓', + '🍔', + '🍟', + '🍕', + '🌭', + '🥪', + '🌮', + '🌯', + '🥙', + '🥚', + '🍳', + '🥘', + '🍲', + '🥣', + '🥗', + '🍿', + '🧂', + '🥫', + '🍱', + '🍘', + '🍙', + '🍚', + '🍛', + '🍜', + '🍝', + '🍠', + '🍢', + '🍣', + '🍤', + '🍥', + '🥮', + '🍡', + '🥟', + '🥠', + '🥡', + '🦀', + '🦞', + '🦐', + '🦑', + '🍦', + '🍧', + '🍨', + '🍩', + '🍪', + '🎂', + '🍰', + '🧁', + '🥧', + '🍫', + '🍬', + '🍭', + '🍮', + '🍯', + '🍼', + '🥛', + '☕', + '🍵', + '🍶', + '🍾', + '🍷', + '🍸', + '🍹', + '🍺', + '🍻', + '🥂', + '🥃', + '🥤', + '🥢', + '🍽️', + '🍴', + '🥄', + '🔪', + '🏺', + '🌍', + '🌎', + '🌏', + '🌐', + '🗺️', + '🗾', + '🧭', + '🏔️', + '⛰️', + '🌋', + '🗻', + '🏕️', + '🏖️', + '🏜️', + '🏝️', + '🏞️', + '🏟️', + '🏛️', + '🏗️', + '🧱', + '🏘️', + '🏚️', + '🏠', + '🏡', + '🏢', + '🏣', + '🏤', + '🏥', + '🏦', + '🏨', + '🏩', + '🏪', + '🏫', + '🏬', + '🏭', + '🏯', + '🏰', + '💒', + '🗼', + '🗽', + '⛪', + '🕌', + '🕍', + '⛩️', + '🕋', + '⛲', + '⛺', + '🌁', + '🌃', + '🏙️', + '🌄', + '🌅', + '🌆', + '🌇', + '🌉', + '♨️', + '🎠', + '🎡', + '🎢', + '💈', + '🎪', + '🚂', + '🚃', + '🚄', + '🚅', + '🚆', + '🚇', + '🚈', + '🚉', + '🚊', + '🚝', + '🚞', + '🚋', + '🚌', + '🚍', + '🚎', + '🚐', + '🚑', + '🚒', + '🚓', + '🚔', + '🚕', + '🚖', + '🚗', + '🚘', + '🚙', + '🚚', + '🚛', + '🚜', + '🏎️', + '🏍️', + '🛵', + '🚲', + '🛴', + '🛹', + '🚏', + '🛣️', + '🛤️', + '🛢️', + '⛽', + '🚨', + '🚥', + '🚦', + '🛑', + '🚧', + '⚓', + '⛵', + '🛶', + '🚤', + '🛳️', + '⛴️', + '🛥️', + '🚢', + '✈️', + '🛩️', + '🛫', + '🛬', + '💺', + '🚁', + '🚟', + '🚠', + '🚡', + '🛰️', + '🚀', + '🛸', + '🛎️', + '🧳', + '⌛', + '⏳', + '⌚', + '⏰', + '⏱️', + '⏲️', + '🕰️', + '🕛', + '🕧', + '🕐', + '🕜', + '🕑', + '🕝', + '🕒', + '🕞', + '🕓', + '🕟', + '🕔', + '🕠', + '🕕', + '🕡', + '🕖', + '🕢', + '🕗', + '🕣', + '🕘', + '🕤', + '🕙', + '🕥', + '🕚', + '🕦', + '🌑', + '🌒', + '🌓', + '🌔', + '🌕', + '🌖', + '🌗', + '🌘', + '🌙', + '🌚', + '🌛', + '🌜', + '🌡️', + '☀️', + '🌝', + '🌞', + '⭐', + '🌟', + '🌠', + '🌌', + '☁️', + '⛅', + '⛈️', + '🌤️', + '🌥️', + '🌦️', + '🌧️', + '🌨️', + '🌩️', + '🌪️', + '🌫️', + '🌬️', + '🌀', + '🌈', + '🌂', + '☂️', + '☔', + '⛱️', + '⚡', + '❄️', + '☃️', + '⛄', + '☄️', + '🔥', + '💧', + '🌊', + '🎃', + '🎄', + '🎆', + '🎇', + '🧨', + '✨', + '🎈', + '🎉', + '🎊', + '🎋', + '🎍', + '🎎', + '🎏', + '🎐', + '🎑', + '🧧', + '🎀', + '🎁', + '🎗️', + '🎟️', + '🎫', + '🎖️', + '🏆', + '🏅', + '🥇', + '🥈', + '🥉', + '⚽', + '⚾', + '🥎', + '🏀', + '🏐', + '🏈', + '🏉', + '🎾', + '🥏', + '🎳', + '🏏', + '🏑', + '🏒', + '🥍', + '🏓', + '🏸', + '🥊', + '🥋', + '🥅', + '⛳', + '⛸️', + '🎣', + '🎽', + '🎿', + '🛷', + '🥌', + '🎯', + '🔫', + '🎱', + '🔮', + '🎮', + '🕹️', + '🎰', + '🎲', + '🧩', + '🧸', + '♠️', + '♥️', + '♦️', + '♣️', + '♟️', + '🃏', + '🀄', + '🎴', + '🎭', + '🖼️', + '🎨', + '🧵', + '🧶', + '👓', + '🕶️', + '🥽', + '🥼', + '👔', + '👕', + '👖', + '🧣', + '🧤', + '🧥', + '🧦', + '👗', + '👘', + '👙', + '👚', + '👛', + '👜', + '👝', + '🛍️', + '🎒', + '👞', + '👟', + '🥾', + '🥿', + '👠', + '👡', + '👢', + '👑', + '👒', + '🎩', + '🎓', + '🧢', + '⛑️', + '📿', + '💄', + '💍', + '💎', + '🔇', + '🔈', + '🔉', + '🔊', + '📢', + '📣', + '📯', + '🔔', + '🔕', + '🎼', + '🎵', + '🎶', + '🎙️', + '🎚️', + '🎛️', + '🎤', + '🎧', + '📻', + '🎷', + '🎸', + '🎹', + '🎺', + '🎻', + '🥁', + '📱', + '📲', + '☎️', + '📞', + '📟', + '📠', + '🔋', + '🔌', + '💻', + '🖥️', + '🖨️', + '⌨️', + '🖱️', + '🖲️', + '💽', + '💾', + '💿', + '📀', + '🧮', + '🎥', + '🎞️', + '📽️', + '🎬', + '📺', + '📷', + '📸', + '📹', + '📼', + '🔍', + '🔎', + '🕯️', + '💡', + '🔦', + '🏮', + '📔', + '📕', + '📖', + '📗', + '📘', + '📙', + '📚', + '📓', + '📒', + '📃', + '📜', + '📄', + '📰', + '🗞️', + '📑', + '🔖', + '🏷️', + '💰', + '💴', + '💵', + '💶', + '💷', + '💸', + '💳', + '🧾', + '💹', + '✉️', + '📧', + '📨', + '📩', + '📤', + '📥', + '📦', + '📫', + '📪', + '📬', + '📭', + '📮', + '🗳️', + '✏️', + '✒️', + '🖋️', + '🖊️', + '🖌️', + '🖍️', + '📝', + '💼', + '📁', + '📂', + '🗂️', + '📅', + '📆', + '🗒️', + '🗓️', + '📇', + '📈', + '📉', + '📊', + '📋', + '📌', + '📍', + '📎', + '🖇️', + '📏', + '📐', + '✂️', + '🗃️', + '🗄️', + '🗑️', + '🔒', + '🔓', + '🔏', + '🔐', + '🔑', + '🗝️', + '🔨', + '⛏️', + '⚒️', + '🛠️', + '🗡️', + '⚔️', + '💣', + '🏹', + '🛡️', + '🔧', + '🔩', + '⚙️', + '🗜️', + '⚖️', + '🔗', + '⛓️', + '🧰', + '🧲', + '⚗️', + '🧪', + '🧫', + '🧬', + '🔬', + '🔭', + '📡', + '💉', + '💊', + '🚪', + '🛏️', + '🛋️', + '🚽', + '🚿', + '🛁', + '🧴', + '🧷', + '🧹', + '🧺', + '🧻', + '🧼', + '🧽', + '🧯', + '🛒', + '🚬', + '⚰️', + '⚱️', + '🧿', + '🗿', + '🏧', + '🚮', + '🚰', + '♿', + '🚹', + '🚺', + '🚻', + '🚼', + '🚾', + '🛂', + '🛃', + '🛄', + '🛅', + '⚠️', + '🚸', + '⛔', + '🚫', + '🚳', + '🚭', + '🚯', + '🚱', + '🚷', + '📵', + '🔞', + '☢️', + '☣️', + '⬆️', + '↗️', + '➡️', + '↘️', + '⬇️', + '↙️', + '⬅️', + '↖️', + '↕️', + '↔️', + '↩️', + '↪️', + '⤴️', + '⤵️', + '🔃', + '🔄', + '🔙', + '🔚', + '🔛', + '🔜', + '🔝', + '🛐', + '⚛️', + '🕉️', + '✡️', + '☸️', + '☯️', + '✝️', + '☦️', + '☪️', + '☮️', + '🕎', + '🔯', + '♈', + '♉', + '♊', + '♋', + '♌', + '♍', + '♎', + '♏', + '♐', + '♑', + '♒', + '♓', + '⛎', + '🔀', + '🔁', + '🔂', + '▶️', + '⏩', + '⏭️', + '⏯️', + '◀️', + '⏪', + '⏮️', + '🔼', + '⏫', + '🔽', + '⏬', + '⏸️', + '⏹️', + '⏺️', + '⏏️', + '🎦', + '🔅', + '🔆', + '📶', + '📳', + '📴', + '♀️', + '♂️', + '✖️', + '➕', + '➖', + '➗', + '♾️', + '‼️', + '⁉️', + '❓', + '❔', + '❕', + '❗', + '〰️', + '💱', + '💲', + '⚕️', + '♻️', + '⚜️', + '🔱', + '📛', + '🔰', + '⭕', + '✅', + '☑️', + '✔️', + '❌', + '❎', + '➰', + '➿', + '〽️', + '✳️', + '✴️', + '❇️', + '©️', + '®️', + '™️', + '#️⃣', + '*️⃣', + '0️⃣', + '1️⃣', + '2️⃣', + '3️⃣', + '4️⃣', + '5️⃣', + '6️⃣', + '7️⃣', + '8️⃣', + '9️⃣', + '🔟', + '🔠', + '🔡', + '🔢', + '🔣', + '🔤', + '🅰️', + '🆎', + '🅱️', + '🆑', + '🆒', + '🆓', + 'ℹ️', + '🆔', + 'Ⓜ️', + '🆕', + '🆖', + '🅾️', + '🆗', + '🅿️', + '🆘', + '🆙', + '🆚', + '🈁', + '🈂️', + '🈷️', + '🈶', + '🈯', + '🉐', + '🈹', + '🈚', + '🈲', + '🉑', + '🈸', + '🈴', + '🈳', + '㊗️', + '㊙️', + '🈺', + '🈵', + '🔴', + '🔵', + '⚫', + '⚪', + '⬛', + '⬜', + '◼️', + '◻️', + '◾', + '◽', + '▪️', + '▫️', + '🔶', + '🔷', + '🔸', + '🔹', + '🔺', + '🔻', + '💠', + '🔘', + '🔳', + '🔲', + '🏁', + '🚩', + '🎌', + '🏴', + '🏳️', + '🏳️‍🌈', + '🏴‍☠️', + '🇦🇨', + '🇦🇩', + '🇦🇪', + '🇦🇫', + '🇦🇬', + '🇦🇮', + '🇦🇱', + '🇦🇲', + '🇦🇴', + '🇦🇶', + '🇦🇷', + '🇦🇸', + '🇦🇹', + '🇦🇺', + '🇦🇼', + '🇦🇽', + '🇦🇿', + '🇧🇦', + '🇧🇧', + '🇧🇩', + '🇧🇪', + '🇧🇫', + '🇧🇬', + '🇧🇭', + '🇧🇮', + '🇧🇯', + '🇧🇱', + '🇧🇲', + '🇧🇳', + '🇧🇴', + '🇧🇶', + '🇧🇷', + '🇧🇸', + '🇧🇹', + '🇧🇻', + '🇧🇼', + '🇧🇾', + '🇧🇿', + '🇨🇦', + '🇨🇨', + '🇨🇩', + '🇨🇫', + '🇨🇬', + '🇨🇭', + '🇨🇮', + '🇨🇰', + '🇨🇱', + '🇨🇲', + '🇨🇳', + '🇨🇴', + '🇨🇵', + '🇨🇷', + '🇨🇺', + '🇨🇻', + '🇨🇼', + '🇨🇽', + '🇨🇾', + '🇨🇿', + '🇩🇪', + '🇩🇬', + '🇩🇯', + '🇩🇰', + '🇩🇲', + '🇩🇴', + '🇩🇿', + '🇪🇦', + '🇪🇨', + '🇪🇪', + '🇪🇬', + '🇪🇭', + '🇪🇷', + '🇪🇸', + '🇪🇹', + '🇪🇺', + '🇫🇮', + '🇫🇯', + '🇫🇰', + '🇫🇲', + '🇫🇴', + '🇫🇷', + '🇬🇦', + '🇬🇧', + '🇬🇩', + '🇬🇪', + '🇬🇫', + '🇬🇬', + '🇬🇭', + '🇬🇮', + '🇬🇱', + '🇬🇲', + '🇬🇳', + '🇬🇵', + '🇬🇶', + '🇬🇷', + '🇬🇸', + '🇬🇹', + '🇬🇺', + '🇬🇼', + '🇬🇾', + '🇭🇰', + '🇭🇲', + '🇭🇳', + '🇭🇷', + '🇭🇹', + '🇭🇺', + '🇮🇨', + '🇮🇩', + '🇮🇪', + '🇮🇱', + '🇮🇲', + '🇮🇳', + '🇮🇴', + '🇮🇶', + '🇮🇷', + '🇮🇸', + '🇮🇹', + '🇯🇪', + '🇯🇲', + '🇯🇴', + '🇯🇵', + '🇰🇪', + '🇰🇬', + '🇰🇭', + '🇰🇮', + '🇰🇲', + '🇰🇳', + '🇰🇵', + '🇰🇷', + '🇰🇼', + '🇰🇾', + '🇰🇿', + '🇱🇦', + '🇱🇧', + '🇱🇨', + '🇱🇮', + '🇱🇰', + '🇱🇷', + '🇱🇸', + '🇱🇹', + '🇱🇺', + '🇱🇻', + '🇱🇾', + '🇲🇦', + '🇲🇨', + '🇲🇩', + '🇲🇪', + '🇲🇫', + '🇲🇬', + '🇲🇭', + '🇲🇰', + '🇲🇱', + '🇲🇲', + '🇲🇳', + '🇲🇴', + '🇲🇵', + '🇲🇶', + '🇲🇷', + '🇲🇸', + '🇲🇹', + '🇲🇺', + '🇲🇻', + '🇲🇼', + '🇲🇽', + '🇲🇾', + '🇲🇿', + '🇳🇦', + '🇳🇨', + '🇳🇪', + '🇳🇫', + '🇳🇬', + '🇳🇮', + '🇳🇱', + '🇳🇴', + '🇳🇵', + '🇳🇷', + '🇳🇺', + '🇳🇿', + '🇴🇲', + '🇵🇦', + '🇵🇪', + '🇵🇫', + '🇵🇬', + '🇵🇭', + '🇵🇰', + '🇵🇱', + '🇵🇲', + '🇵🇳', + '🇵🇷', + '🇵🇸', + '🇵🇹', + '🇵🇼', + '🇵🇾', + '🇶🇦', + '🇷🇪', + '🇷🇴', + '🇷🇸', + '🇷🇺', + '🇷🇼', + '🇸🇦', + '🇸🇧', + '🇸🇨', + '🇸🇩', + '🇸🇪', + '🇸🇬', + '🇸🇭', + '🇸🇮', + '🇸🇯', + '🇸🇰', + '🇸🇱', + '🇸🇲', + '🇸🇳', + '🇸🇴', + '🇸🇷', + '🇸🇸', + '🇸🇹', + '🇸🇻', + '🇸🇽', + '🇸🇾', + '🇸🇿', + '🇹🇦', + '🇹🇨', + '🇹🇩', + '🇹🇫', + '🇹🇬', + '🇹🇭', + '🇹🇯', + '🇹🇰', + '🇹🇱', + '🇹🇲', + '🇹🇳', + '🇹🇴', + '🇹🇷', + '🇹🇹', + '🇹🇻', + '🇹🇼', + '🇹🇿', + '🇺🇦', + '🇺🇬', + '🇺🇲', + '🇺🇳', + '🇺🇸', + '🇺🇾', + '🇺🇿', + '🇻🇦', + '🇻🇨', + '🇻🇪', + '🇻🇬', + '🇻🇮', + '🇻🇳', + '🇻🇺', + '🇼🇫', + '🇼🇸', + '🇽🇰', + '🇾🇪', + '🇾🇹', + '🇿🇦', + '🇿🇲', + '🇿🇼', + '🏴󠁧󠁢󠁥󠁮󠁧󠁿', + '🏴󠁧󠁢󠁳󠁣󠁴󠁿', + '🏴󠁧󠁢󠁷󠁬󠁳󠁿' +]; diff --git a/packages/uikit/src/hooks/useIsScrolled.ts b/packages/uikit/src/hooks/useIsScrolled.ts new file mode 100644 index 000000000..fb096d8d2 --- /dev/null +++ b/packages/uikit/src/hooks/useIsScrolled.ts @@ -0,0 +1,46 @@ +import { useLayoutEffect, useRef, useState } from 'react'; +import { throttle } from '@tonkeeper/core/dist/utils/common'; + +export function useIsScrolled(options?: { + gapTop: number; + gapBottom: number; +}) { + const gapTop = options?.gapTop ?? 10; + const gapBottom = options?.gapBottom ?? 10; + const ref = useRef(null); + const [closeTop, setCloseTop] = useState(true); + const [closeBottom, setCloseBottom] = useState(false); + + useLayoutEffect(() => { + const element = ref.current; + if (!element) return; + + let timer: NodeJS.Timeout | undefined; + + const handlerScroll = throttle(() => { + setCloseTop(element.scrollTop < gapTop); + setCloseBottom( + element.scrollTop + element.clientHeight < element.scrollHeight - gapBottom + ); + + clearTimeout(timer); + if (!document.body.classList.contains('scroll')) { + document.body.classList.add('scroll'); + } + timer = setTimeout(function () { + document.body.classList.remove('scroll'); + }, 300); + }, 50); + + element.addEventListener('scroll', handlerScroll); + handlerScroll(); + + return () => { + clearTimeout(timer); + + element.removeEventListener('scroll', handlerScroll); + }; + }, []); + + return { ref, closeTop, closeBottom }; +} diff --git a/packages/uikit/src/pages/settings/Account.tsx b/packages/uikit/src/pages/settings/Account.tsx index c93de5633..c3480b6f0 100644 --- a/packages/uikit/src/pages/settings/Account.tsx +++ b/packages/uikit/src/pages/settings/Account.tsx @@ -24,7 +24,7 @@ import { } from '../../components/settings/LogOutNotification'; import { SetUpWalletIcon } from '../../components/settings/SettingsIcons'; import { SettingsList } from '../../components/settings/SettingsList'; -import { RenameWalletNotification } from '../../components/settings/WalletNameNotification'; +import { RenameWalletNotification } from '../../components/settings/wallet-name/WalletNameNotification'; import { useAppContext } from '../../hooks/appContext'; import { useTranslation } from '../../hooks/translation'; import { AppRoute, SettingsRoute } from '../../libs/routes'; diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index dca0b5a92..c31a6b4e3 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -63,12 +63,12 @@ export const useMutateRenameWallet = (wallet: WalletState) => { const sdk = useAppSdk(); const client = useQueryClient(); - return useMutation(async name => { - if (name.length <= 0) { + return useMutation(async form => { + if (form.name !== undefined && form.name.length <= 0) { throw new Error('Missing name'); } - await updateWalletProperty(sdk.storage, wallet, { name }); + await updateWalletProperty(sdk.storage, wallet, form); await client.invalidateQueries([QueryKey.account]); }); }; From 848c97cc750ced6af955c4d7da2161975878173b Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 11 Mar 2024 19:40:58 +0100 Subject: [PATCH 24/27] feat: custom emojis added --- .../wallet-name/WalletNameNotification.tsx | 10 +- .../settings/wallet-name/emojiIcons.tsx | 515 ++++++++++++++++++ .../components/shared/wallet/WalletEmoji.tsx | 31 ++ 3 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 packages/uikit/src/components/settings/wallet-name/emojiIcons.tsx create mode 100644 packages/uikit/src/components/shared/wallet/WalletEmoji.tsx diff --git a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx index 1f15eb53c..84553bb3a 100644 --- a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx +++ b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx @@ -8,6 +8,8 @@ import { Button } from '../../fields/Button'; import { Input } from '../../fields/Input'; import styled from 'styled-components'; import { emojis } from './emojis'; +import { emojiIcons } from './emojiIcons'; +import { WalletEmoji } from '../../shared/wallet/WalletEmoji'; const EmojisListScroll = styled.div` max-height: 240px; @@ -52,7 +54,6 @@ const EmojiWrapper = styled.div` display: flex; align-items: center; justify-content: center; - cursor: pointer; `; const EmojiButton = styled(EmojiWrapper)` @@ -74,6 +75,11 @@ const EmojisList: FC<{ onClick: (emoji: string) => void; keepShortListForMS?: nu return ( + {emojiIcons.map(item => ( + onClick(item.name)}> + + + ))} {emojisList.map(emoji => ( onClick(emoji)}> {emoji} @@ -111,7 +117,7 @@ const RenameWalletContent: FC<{ onChange={setName} isValid={!isError} label={t('Wallet_name')} - rightElement={emoji ? {emoji} : null} + rightElement={emoji ? : null} marginRight="36px" /> diff --git a/packages/uikit/src/components/settings/wallet-name/emojiIcons.tsx b/packages/uikit/src/components/settings/wallet-name/emojiIcons.tsx new file mode 100644 index 000000000..7eee21d27 --- /dev/null +++ b/packages/uikit/src/components/settings/wallet-name/emojiIcons.tsx @@ -0,0 +1,515 @@ +import { useTheme } from 'styled-components'; + +export const WalletIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const LeafIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const LockIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const KeyIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const InboxIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const SnowflakeIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const SparklesIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const SunIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const HareIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const FlashIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const BankCardIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const GearIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const HandIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const GlassCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; +export const FlashCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const DollarCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const EuroCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const SterlingCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const YuanCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const RubleCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const RupeeCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const HashCircleIcon = () => { + const theme = useTheme(); + return ( + + + + ); +}; + +export const emojiIcons = [ + { + name: 'custom:wallet', + icon: WalletIcon + }, + { + name: 'custom:leaf', + icon: LeafIcon + }, + { + name: 'custom:lock', + icon: LockIcon + }, + { + name: 'custom:key', + icon: KeyIcon + }, + { + name: 'custom:inbox', + icon: InboxIcon + }, + { + name: 'custom:snowflake', + icon: SnowflakeIcon + }, + { + name: 'custom:sparkles', + icon: SparklesIcon + }, + { + name: 'custom:sun', + icon: SunIcon + }, + { + name: 'custom:hare', + icon: HareIcon + }, + { + name: 'custom:flash', + icon: FlashIcon + }, + { + name: 'custom:bankcard', + icon: BankCardIcon + }, + { + name: 'custom:gear', + icon: GearIcon + }, + { + name: 'custom:hand', + icon: HandIcon + }, + { + name: 'custom:glass_circle', + icon: GlassCircleIcon + }, + { + name: 'custom:flash_circle', + icon: FlashCircleIcon + }, + { + name: 'custom:dollar_circle', + icon: DollarCircleIcon + }, + { + name: 'custom:euro_circle', + icon: EuroCircleIcon + }, + { + name: 'custom:sterling_circle', + icon: SterlingCircleIcon + }, + { + name: 'custom:yuan_circle', + icon: YuanCircleIcon + }, + { + name: 'custom:ruble_circle', + icon: RubleCircleIcon + }, + { + name: 'custom:rupee_circle', + icon: RupeeCircleIcon + }, + { + name: 'custom:hash_circle', + icon: HashCircleIcon + } +]; diff --git a/packages/uikit/src/components/shared/wallet/WalletEmoji.tsx b/packages/uikit/src/components/shared/wallet/WalletEmoji.tsx new file mode 100644 index 000000000..bfb6c764d --- /dev/null +++ b/packages/uikit/src/components/shared/wallet/WalletEmoji.tsx @@ -0,0 +1,31 @@ +import { FC } from 'react'; +import { emojiIcons } from '../../settings/wallet-name/emojiIcons'; +import styled from 'styled-components'; + +const EmojiWrapper = styled.div` + height: 32px; + width: 32px; + line-height: 24px; + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; +`; + +export const WalletEmoji: FC<{ emoji: string; className?: string }> = ({ emoji, className }) => { + if (emoji.startsWith('custom:')) { + const Emoji = emojiIcons.find(icon => icon.name === emoji); + + if (!Emoji) { + return null; + } + + return ( + + + + ); + } + + return {emoji}; +}; From 3aabacd4a307cc50ed2d855a7b2e7123b31b4a75 Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 12 Mar 2024 11:45:14 +0100 Subject: [PATCH 25/27] feat: emojis added to all layouts that includes wallet.name --- packages/core/src/entries/wallet.ts | 2 +- .../core/src/service/wallet/storeService.ts | 8 + packages/core/src/service/walletService.ts | 7 +- .../wallet-name => core/src/utils}/emojis.ts | 0 packages/uikit/src/components/Header.tsx | 3 + packages/uikit/src/components/Icon.tsx | 23 +- packages/uikit/src/components/List.tsx | 1 + .../uikit/src/components/aside/AsideMenu.tsx | 59 +- .../src/components/create/WalletName.tsx | 8 +- .../wallet-name/WalletNameNotification.tsx | 88 +-- .../settings/wallet-name/emojiIcons.tsx | 515 ------------------ .../components/shared/emoji/EmojisList.tsx | 85 +++ .../components/shared/emoji/WalletEmoji.tsx | 44 ++ .../components/shared/emoji/emojiIcons.tsx | 515 ++++++++++++++++++ .../components/shared/wallet/WalletEmoji.tsx | 31 -- packages/uikit/src/pages/import/Create.tsx | 2 +- packages/uikit/src/pages/import/Import.tsx | 4 +- packages/uikit/src/pages/settings/Account.tsx | 2 + 18 files changed, 721 insertions(+), 676 deletions(-) rename packages/{uikit/src/components/settings/wallet-name => core/src/utils}/emojis.ts (100%) delete mode 100644 packages/uikit/src/components/settings/wallet-name/emojiIcons.tsx create mode 100644 packages/uikit/src/components/shared/emoji/EmojisList.tsx create mode 100644 packages/uikit/src/components/shared/emoji/WalletEmoji.tsx create mode 100644 packages/uikit/src/components/shared/emoji/emojiIcons.tsx delete mode 100644 packages/uikit/src/components/shared/wallet/WalletEmoji.tsx diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index b56b56b32..bc06e3306 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -56,7 +56,7 @@ export interface WalletState { active: WalletAddress; name?: string; - emoji?: string; + emoji: string; revision: number; diff --git a/packages/core/src/service/wallet/storeService.ts b/packages/core/src/service/wallet/storeService.ts index 45c4bfc53..cbbb80184 100644 --- a/packages/core/src/service/wallet/storeService.ts +++ b/packages/core/src/service/wallet/storeService.ts @@ -6,6 +6,7 @@ import { TonConnectError } from '../../entries/exception'; import { Network } from '../../entries/network'; import { CONNECT_EVENT_ERROR_CODES } from '../../entries/tonConnect'; import { WalletState } from '../../entries/wallet'; +import { emojis } from "../../utils/emojis"; export const getWalletState = async (storage: IStorage, publicKey: string) => { const state = await storage.get(`${AppKey.WALLET}_${publicKey}`); @@ -14,6 +15,8 @@ export const getWalletState = async (storage: IStorage, publicKey: string) => { state.active.friendlyAddress = Address.parse(state.active.friendlyAddress).toString({ testOnly: state.network === Network.TESTNET }); + + state.emoji ||= getFallbackWalletEmoji(state.publicKey); } return state; @@ -48,3 +51,8 @@ export const getCurrentWallet = async (storage: IStorage) => { return wallet; }; + +export function getFallbackWalletEmoji(publicKey: string) { + const index = Number('0x' + publicKey.slice(-6)) % emojis.length; + return emojis[index]; +} diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index 6bf554206..6cc15f78d 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -8,7 +8,7 @@ import { WalletAddress, WalletState, WalletVersion, WalletVersions } from '../en import { WalletApi } from '../tonApiV2'; import { encrypt } from './cryptoService'; import { walletContract } from './wallet/contractService'; -import { setWalletState } from './wallet/storeService'; +import { getFallbackWalletEmoji, setWalletState } from './wallet/storeService'; export const createNewWalletState = async (api: APIConfig, mnemonic: string[], name?: string) => { const keyPair = await mnemonicToPrivateKey(mnemonic); @@ -21,7 +21,8 @@ export const createNewWalletState = async (api: APIConfig, mnemonic: string[], n publicKey, active, revision: 0, - name + name, + emoji: getFallbackWalletEmoji(publicKey) }; // state.tron = await getTronWallet(api.tronApi, mnemonic, state).catch(() => undefined); @@ -30,7 +31,7 @@ export const createNewWalletState = async (api: APIConfig, mnemonic: string[], n }; export const encryptWalletMnemonic = async (mnemonic: string[], password: string) => { - return await encrypt(mnemonic.join(' '), password); + return encrypt(mnemonic.join(' '), password); }; const versionMap: Record = { diff --git a/packages/uikit/src/components/settings/wallet-name/emojis.ts b/packages/core/src/utils/emojis.ts similarity index 100% rename from packages/uikit/src/components/settings/wallet-name/emojis.ts rename to packages/core/src/utils/emojis.ts diff --git a/packages/uikit/src/components/Header.tsx b/packages/uikit/src/components/Header.tsx index b1bb8df25..ceb5ef1a7 100644 --- a/packages/uikit/src/components/Header.tsx +++ b/packages/uikit/src/components/Header.tsx @@ -16,6 +16,7 @@ import { H1, H3, Label1, Label2 } from './Text'; import { ScanButton } from './connect/ScanButton'; import { ImportNotification } from './create/ImportNotification'; import { SkeletonText } from './shared/Skeleton'; +import { WalletEmoji } from './shared/emoji/WalletEmoji'; const Block = styled.div<{ center?: boolean; @@ -134,6 +135,7 @@ const WalletRow: FC<{ }} > + {wallet && } = ({ showQrScan = true }) => { )} > + <WalletEmoji emoji={wallet.emoji} /> <TitleName> {wallet.name ? wallet.name : t('wallet_title')}</TitleName> <DownIconWrapper> diff --git a/packages/uikit/src/components/Icon.tsx b/packages/uikit/src/components/Icon.tsx index b93707b9b..a3b7c6e02 100644 --- a/packages/uikit/src/components/Icon.tsx +++ b/packages/uikit/src/components/Icon.tsx @@ -1,5 +1,5 @@ import React, { FC, Suspense, useContext } from 'react'; -import styled from 'styled-components'; +import styled, { useTheme } from 'styled-components'; const TonkeeperLottieIcon = React.lazy(() => import('./lottie/TonkeeperLottie')); @@ -855,3 +855,24 @@ export const TelegramIcon: FC<{ className?: string }> = ({ className }) => { </svg> ); }; + +export const StatsIcon: FC<{ className?: string }> = ({ className }) => { + const theme = useTheme(); + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="16" + height="16" + viewBox="0 0 16 16" + color={theme.iconSecondary} + className={className} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M13.25 2C12.8358 2 12.5 2.33579 12.5 2.75V13.25C12.5 13.6642 12.8358 14 13.25 14C13.6642 14 14 13.6642 14 13.25V2.75C14 2.33579 13.6642 2 13.25 2ZM2.75 4C2.33579 4 2 4.33579 2 4.75V13.25C2 13.6642 2.33579 14 2.75 14C3.16421 14 3.5 13.6642 3.5 13.25V4.75C3.5 4.33579 3.16421 4 2.75 4ZM6.25 8C5.83579 8 5.5 8.33579 5.5 8.75V13.25C5.5 13.6642 5.83579 14 6.25 14C6.66421 14 7 13.6642 7 13.25V8.75C7 8.33579 6.66421 8 6.25 8ZM9 6.75C9 6.33579 9.33579 6 9.75 6C10.1642 6 10.5 6.33579 10.5 6.75V13.25C10.5 13.6642 10.1642 14 9.75 14C9.33579 14 9 13.6642 9 13.25V6.75Z" + fill="currentColor" + /> + </svg> + ); +}; diff --git a/packages/uikit/src/components/List.tsx b/packages/uikit/src/components/List.tsx index 63e9d6109..77d7e919d 100644 --- a/packages/uikit/src/components/List.tsx +++ b/packages/uikit/src/components/List.tsx @@ -70,6 +70,7 @@ export const ListItemPayload = styled.div` justify-content: space-between; padding: 1rem 1rem 1rem 0; box-sizing: border-box; + gap: 10px; width: 100%; `; diff --git a/packages/uikit/src/components/aside/AsideMenu.tsx b/packages/uikit/src/components/aside/AsideMenu.tsx index a52e5f0ea..a2e9afbcc 100644 --- a/packages/uikit/src/components/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/aside/AsideMenu.tsx @@ -1,4 +1,3 @@ -import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { FC, useCallback, useMemo, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; @@ -8,10 +7,11 @@ import { scrollToTop } from '../../libs/common'; import { AppProRoute, AppRoute } from '../../libs/routes'; import { useMutateActiveWallet } from '../../state/account'; import { useWalletState } from '../../state/wallet'; -import { PlusIcon, SlidersIcon } from '../Icon'; -import { Body2, Body3 } from '../Text'; +import { PlusIcon, SlidersIcon, StatsIcon } from '../Icon'; +import { Body2 } from '../Text'; import { ImportNotification } from '../create/ImportNotification'; import { SubscriptionInfo } from './SubscriptionInfo'; +import { WalletEmoji } from '../shared/emoji/WalletEmoji'; const AsideContainer = styled.div` height: 100%; @@ -21,7 +21,7 @@ const AsideContainer = styled.div` background: ${p => p.theme.backgroundContent}; display: flex; flex-direction: column; - padding: 0.5rem 0; + padding: 0.5rem; `; const IconWrapper = styled.div` @@ -33,20 +33,18 @@ const IconWrapper = styled.div` } `; -const AsideMenuCard = styled.button<{ isSelected: boolean; padding: 's' | 'm' }>` +const AsideMenuCard = styled.button<{ isSelected: boolean }>` background: ${p => (p.isSelected ? p.theme.backgroundContentTint : p.theme.backgroundContent)}; - ${p => (p.padding === 's' ? 'padding: 6px 16px' : 'padding: 8px 16px')}; + border-radius: ${p => p.theme.corner2xSmall}; + padding: 6px 10px; width: 100%; + height: 36px; display: flex; - flex-direction: column; - - & > ${Body3} { - color: ${props => props.theme.textSecondary}; - } + align-items: center; + gap: 10px; & > ${Body2} { - text-align: left; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 1; @@ -69,13 +67,6 @@ const AsideMenuBottom = styled.div` justify-content: flex-end; `; -const AsideMenuItemIcon = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; -`; - export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = ({ publicKey, isSelected @@ -102,13 +93,12 @@ export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = return null; } - const address = formatAddress(wallet.active.rawAddress, wallet.network); const name = wallet.name ? wallet.name : t('wallet_title'); return ( - <AsideMenuCard isSelected={isSelected} padding="s" onClick={onClick}> + <AsideMenuCard isSelected={isSelected} onClick={onClick}> + <WalletEmoji emojiSize="16px" containerSize="16px" emoji={wallet.emoji} /> <Body2>{name}</Body2> - <Body3>{toShortValue(address)}</Body3> </AsideMenuCard> ); }; @@ -148,9 +138,9 @@ export const AsideMenu: FC<{ className?: string }> = ({ className }) => { {proFeatures && ( <AsideMenuCard isSelected={activeRoute === AppProRoute.dashboard} - padding="m" onClick={() => handleNavigateClick(AppProRoute.dashboard)} > + <StatsIcon /> <Body2>{t('aside_dashboard')}</Body2> </AsideMenuCard> )} @@ -166,25 +156,20 @@ export const AsideMenu: FC<{ className?: string }> = ({ className }) => { /> ))} <AsideMenuBottom> - <AsideMenuCard padding="m" isSelected={false} onClick={() => setIsOpenImport(true)}> - <AsideMenuItemIcon> - <Body2>{t('aside_add_wallet')}</Body2> - <IconWrapper> - <PlusIcon /> - </IconWrapper> - </AsideMenuItemIcon> + <AsideMenuCard isSelected={false} onClick={() => setIsOpenImport(true)}> + <IconWrapper> + <PlusIcon /> + </IconWrapper> + <Body2>{t('aside_add_wallet')}</Body2> </AsideMenuCard> <AsideMenuCard - padding="m" onClick={() => handleNavigateClick(AppRoute.settings)} isSelected={activeRoute === AppRoute.settings} > - <AsideMenuItemIcon> - <Body2>{t('aside_settings')}</Body2> - <IconWrapper> - <SlidersIcon /> - </IconWrapper> - </AsideMenuItemIcon> + <IconWrapper> + <SlidersIcon /> + </IconWrapper> + <Body2>{t('aside_settings')}</Body2> </AsideMenuCard> <SubscriptionInfo /> </AsideMenuBottom> diff --git a/packages/uikit/src/components/create/WalletName.tsx b/packages/uikit/src/components/create/WalletName.tsx index 7a44f3dc9..6443a8e48 100644 --- a/packages/uikit/src/components/create/WalletName.tsx +++ b/packages/uikit/src/components/create/WalletName.tsx @@ -11,6 +11,8 @@ import { CenterContainer } from '../Layout'; import { Body2, H2 } from '../Text'; import { Button } from '../fields/Button'; import { Input } from '../fields/Input'; +import { EmojisList } from '../shared/emoji/EmojisList'; +import { WalletEmoji } from '../shared/emoji/WalletEmoji'; const Block = styled.form` display: flex; @@ -50,7 +52,8 @@ const useUpdateNameMutation = (account: AccountState) => { export const UpdateWalletName: FC<{ account: AccountState; onUpdate: (account: AccountState) => void; -}> = ({ account, onUpdate }) => { + walletEmoji: string; +}> = ({ account, onUpdate, walletEmoji }) => { const { t } = useTranslation(); const ref = useRef<HTMLInputElement | null>(null); @@ -64,6 +67,7 @@ export const UpdateWalletName: FC<{ }, [ref.current]); const [name, setName] = useState(''); + const [emoji, setEmoji] = useState(walletEmoji); const onSubmit: React.FormEventHandler<HTMLFormElement> = async e => { e.preventDefault(); @@ -90,7 +94,9 @@ export const UpdateWalletName: FC<{ label={t('Wallet_name')} disabled={isLoading} isValid={!isError} + rightElement={emoji ? <WalletEmoji emoji={emoji} /> : null} /> + <EmojisList keepShortListForMS={500} onClick={setEmoji} /> <Button size="large" diff --git a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx index 84553bb3a..131ec7254 100644 --- a/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx +++ b/packages/uikit/src/components/settings/wallet-name/WalletNameNotification.tsx @@ -1,94 +1,13 @@ import { WalletState } from '@tonkeeper/core/dist/entries/wallet'; import { formatAddress } from '@tonkeeper/core/dist/utils/common'; -import React, { FC, useCallback, useEffect, useState } from 'react'; +import React, { FC, useCallback, useState } from 'react'; import { useTranslation } from '../../../hooks/translation'; import { useMutateRenameWallet } from '../../../state/wallet'; import { Notification, NotificationBlock } from '../../Notification'; import { Button } from '../../fields/Button'; import { Input } from '../../fields/Input'; -import styled from 'styled-components'; -import { emojis } from './emojis'; -import { emojiIcons } from './emojiIcons'; -import { WalletEmoji } from '../../shared/wallet/WalletEmoji'; - -const EmojisListScroll = styled.div` - max-height: 240px; - display: flex; - flex-wrap: wrap; - align-items: center; - overflow: auto; - position: relative; - - &::-webkit-scrollbar { - display: none; - width: 0; - background: transparent; - height: 0; - } - - -ms-overflow-style: none; - scrollbar-width: none; -`; - -const Shadow = styled.div` - position: sticky; - width: 100%; - height: 16px; -`; - -const ShadowBottom = styled(Shadow)` - bottom: -1px; - background: ${props => props.theme.gradientBackgroundBottom}; -`; - -const ShadowTop = styled(Shadow)` - top: 0; - background: ${props => props.theme.gradientBackgroundTop}; -`; - -const EmojiWrapper = styled.div` - height: 32px; - width: 32px; - line-height: 24px; - font-size: 24px; - display: flex; - align-items: center; - justify-content: center; -`; - -const EmojiButton = styled(EmojiWrapper)` - cursor: pointer; -`; - -const shortEmojisList = emojis.slice(0, 150); - -const EmojisList: FC<{ onClick: (emoji: string) => void; keepShortListForMS?: number }> = - React.memo(({ onClick, keepShortListForMS }) => { - const [emojisList, setEmojisList] = useState(keepShortListForMS ? shortEmojisList : emojis); - - useEffect(() => { - if (keepShortListForMS) { - setTimeout(() => setEmojisList(emojis), keepShortListForMS); - } - }, []); - - return ( - <EmojisListScroll> - <ShadowTop /> - {emojiIcons.map(item => ( - <EmojiButton key={item.name} onClick={() => onClick(item.name)}> - <item.icon /> - </EmojiButton> - ))} - {emojisList.map(emoji => ( - <EmojiButton key={emoji} onClick={() => onClick(emoji)}> - {emoji} - </EmojiButton> - ))} - <ShadowBottom /> - </EmojisListScroll> - ); - }); +import { WalletEmoji } from '../../shared/emoji/WalletEmoji'; +import { EmojisList } from '../../shared/emoji/EmojisList'; const RenameWalletContent: FC<{ wallet: WalletState; @@ -125,7 +44,6 @@ const RenameWalletContent: FC<{ <Button size="large" fullWidth - marginTop primary loading={isLoading} disabled={isLoading} diff --git a/packages/uikit/src/components/settings/wallet-name/emojiIcons.tsx b/packages/uikit/src/components/settings/wallet-name/emojiIcons.tsx deleted file mode 100644 index 7eee21d27..000000000 --- a/packages/uikit/src/components/settings/wallet-name/emojiIcons.tsx +++ /dev/null @@ -1,515 +0,0 @@ -import { useTheme } from 'styled-components'; - -export const WalletIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - fillRule="evenodd" - clipRule="evenodd" - d="M5.82698 8.63803C5.57654 9.12955 5.51792 9.73742 5.50419 10.75H5.5V11.8V16V20.2C5.5 21.8802 5.5 22.7202 5.82698 23.362C6.1146 23.9265 6.57354 24.3854 7.13803 24.673C7.77976 25 8.61984 25 10.3 25H21.7C23.3802 25 24.2202 25 24.862 24.673C25.4265 24.3854 25.8854 23.9265 26.173 23.362C26.5 22.7202 26.5 21.8802 26.5 20.2V15.55C26.5 13.8698 26.5 13.0298 26.173 12.388C25.8854 11.8235 25.4265 11.3646 24.862 11.077C24.5049 10.8951 24.0865 10.8144 23.4962 10.7786C23.4832 9.74882 23.4258 9.13415 23.173 8.63803C22.8854 8.07354 22.4265 7.6146 21.862 7.32698C21.2202 7 20.3802 7 18.7 7H10.3C8.61984 7 7.77976 7 7.13803 7.32698C6.57354 7.6146 6.1146 8.07354 5.82698 8.63803ZM21.625 10.75H7.375V10.675C7.375 10.0449 7.375 9.72991 7.49762 9.48926C7.60548 9.27758 7.77758 9.10548 7.98926 8.99762C8.22991 8.875 8.54494 8.875 9.175 8.875H19.825C20.4551 8.875 20.7701 8.875 21.0107 8.99762C21.2224 9.10548 21.3945 9.27758 21.5024 9.48926C21.625 9.72991 21.625 10.0449 21.625 10.675V10.75ZM23.125 16.75C22.5037 16.75 22 17.2537 22 17.875C22 18.4963 22.5037 19 23.125 19C23.7463 19 24.25 18.4963 24.25 17.875C24.25 17.2537 23.7463 16.75 23.125 16.75Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const LeafIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M5.79387 7.63695C5.57347 8.60707 5.5 9.91303 5.5 10.8085C5.5 18.1343 9.82234 22.9601 16.4589 22.9601C20.9771 22.9601 22.8506 20.199 23.2056 19.6766L21.8709 19.5647C23.1445 20.908 23.7567 22.4378 24.3689 24.3656C24.5159 24.8383 24.8342 25 25.177 25C25.8873 25 26.4995 24.378 26.4995 23.4951C26.4995 22.1766 24.5281 19.8383 23.5239 18.8806C19.2629 14.9006 12.6631 17.2637 11.1325 12.8235C11.0223 12.4876 11.3774 12.1891 11.708 12.5249C15.063 15.9329 19.3976 13.0598 23.5239 16.9279C23.8791 17.2513 24.2954 17.0771 24.3566 16.704C24.4055 16.4428 24.4302 15.9951 24.4302 15.5722C24.4302 10.7588 21.1118 8.40808 16.4956 8.40808C14.9283 8.40808 13.0671 8.8434 11.6468 8.8434C10.2019 8.8434 8.47543 8.76877 7.04282 7.27624C6.61426 6.84094 5.96529 6.89069 5.79387 7.63695Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const LockIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M11.0443 26.5H20.9557C22.6473 26.5 23.5 25.6277 23.5 23.7848V16.3706C23.5 14.5417 22.6473 13.6695 20.9557 13.6695H11.0443C9.35274 13.6695 8.5 14.5417 8.5 16.3706V23.7848C8.5 25.6277 9.35274 26.5 11.0443 26.5ZM10.4012 14.6402H12.3723V10.701C12.3723 7.9717 14.1197 6.6352 15.993 6.6352C17.8802 6.6352 19.6416 7.9717 19.6416 10.701V14.6402H21.6128V10.9824C21.6128 6.67737 18.8029 4.75 15.993 4.75C13.1971 4.75 10.4012 6.67737 10.4012 10.9824V14.6402Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const KeyIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M23.8738 8.12667C21.3626 5.61545 17.3466 5.62411 14.8424 8.12832C12.9464 10.0244 12.4049 12.8099 13.4795 15.2439L6.53918 22.1842C6.35143 22.372 6.27959 22.5685 6.27007 22.8451L6.24707 25.2191C6.25557 25.4421 6.47795 25.754 6.78138 25.7534L11.2614 25.7438C11.5827 25.743 11.8063 25.5016 11.807 25.1982L11.795 22.4852L15.5432 22.4771C15.8377 22.4854 16.0702 22.2529 16.062 21.9227L16.0968 18.219C19.1193 19.5065 21.9403 19.0899 23.8721 17.1581C26.3674 14.6628 26.3761 10.629 23.8738 8.12667ZM19.6882 12.3123C19.0203 11.6444 19.0405 10.5734 19.7023 9.9116C20.3731 9.24083 21.4261 9.23857 22.094 9.90646C22.753 10.5654 22.7685 11.6363 22.0978 12.3071C21.4359 12.9689 20.3472 12.9713 19.6882 12.3123Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const InboxIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M8.38679 23.5H23.6132C25.549 23.5 26.5 22.5504 26.5 20.6511V15.7334C26.5 14.8516 26.3641 14.4446 25.9566 13.8906L23.0472 9.92252C22.0169 8.50938 21.4735 8.125 19.7981 8.125H12.2019C10.5377 8.125 9.98302 8.50938 8.95282 9.92252L6.05472 13.8906C5.64717 14.4446 5.5 14.8516 5.5 15.7334V20.6511C5.5 22.5504 6.46226 23.5 8.38679 23.5ZM16.0057 18.2318C14.6811 18.2318 13.8887 17.0674 13.8887 16.0612V16.0273C13.8887 15.5977 13.6283 15.1794 13.0736 15.1794H7.63962C7.31132 15.1794 7.24339 14.9081 7.40189 14.682L10.5377 10.3182C10.9453 9.74164 11.4774 9.54945 12.1 9.54945H19.9C20.5226 9.54945 21.066 9.74164 21.4735 10.3182L24.5981 14.682C24.7452 14.9081 24.6887 15.1794 24.3603 15.1794H18.9264C18.383 15.1794 18.1226 15.5977 18.1226 16.0273V16.0612C18.1226 17.0674 17.3189 18.2318 16.0057 18.2318Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const SnowflakeIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M15.9979 26.5C16.5242 26.5 16.8268 26.1451 16.8268 25.6327V23.6352L18.4057 24.5156C18.8399 24.7653 19.2873 24.7259 19.5372 24.2922C19.7346 23.9242 19.6162 23.4774 19.1688 23.2147L16.8268 21.9005V20.7703L16.5768 17.0119L19.7346 19.1014L20.7083 19.6664L20.682 22.3472C20.682 22.8598 20.9977 23.2147 21.4188 23.2147C21.9319 23.2147 22.1819 22.8466 22.1819 22.3472L22.1951 20.5075L23.9582 21.5325C24.3924 21.7953 24.8397 21.6902 25.0898 21.2565C25.3397 20.8097 25.2082 20.3498 24.774 20.1001L23.024 19.1014L24.6029 18.1683C25.024 17.9186 25.2082 17.5112 24.9712 17.0907C24.7476 16.7096 24.274 16.6176 23.8398 16.8673L21.5372 18.234L20.5504 17.6689L17.1557 16L20.5504 14.331L21.524 13.7659L23.8398 15.1326C24.274 15.3824 24.7607 15.2904 24.9712 14.9224C25.2082 14.4887 25.024 14.0813 24.6029 13.8317L23.024 12.8986L24.774 11.8999C25.2213 11.6371 25.3529 11.2034 25.0898 10.7434C24.8528 10.3229 24.4055 10.2178 23.9582 10.4675L22.1951 11.4662L22.1819 9.63955C22.1819 9.1533 21.9319 8.78538 21.4319 8.78538C20.9977 8.78538 20.682 9.14016 20.682 9.63955L20.7083 12.3204L19.7346 12.8986L16.5768 14.9881L16.8268 11.2165V10.0995L19.1688 8.78538C19.6162 8.52253 19.7346 8.07571 19.5372 7.70776C19.2873 7.27409 18.8399 7.23466 18.4057 7.4712L16.8268 8.36485V6.36734C16.8268 5.85483 16.5242 5.5 15.9979 5.5C15.511 5.5 15.1821 5.85483 15.1821 6.36734V8.36485L13.5769 7.4712C13.1427 7.23466 12.7216 7.27409 12.4585 7.70776C12.248 8.07571 12.3927 8.52253 12.8269 8.7722L15.1821 10.0995V11.2165L15.4189 14.9881L12.2743 12.8986L11.2875 12.3204L11.3138 9.63955C11.3138 9.14016 10.998 8.78538 10.577 8.78538C10.077 8.78538 9.81382 9.1533 9.81382 9.63955L9.80069 11.4662L8.05074 10.4675C7.61654 10.2309 7.14286 10.3229 6.90603 10.7434C6.64288 11.2034 6.78761 11.6502 7.22181 11.8999L8.97174 12.8986L7.39286 13.8317C6.97182 14.0813 6.78761 14.4624 7.02444 14.9224C7.23496 15.2904 7.70863 15.3824 8.15599 15.1326L10.4717 13.7791L11.4454 14.331L14.84 16L11.4454 17.6689L10.4717 18.234L8.15599 16.8673C7.7218 16.6176 7.24812 16.7096 7.05076 17.0776C6.80077 17.5375 6.97182 17.9186 7.39286 18.1683L8.97174 19.1014L7.22181 20.1001C6.78761 20.3498 6.65603 20.8229 6.90603 21.2565C7.15602 21.6902 7.61654 21.769 8.05074 21.5325L9.80069 20.5338L9.81382 22.3472C9.81382 22.8466 10.077 23.2147 10.577 23.2147C10.998 23.2147 11.3138 22.8598 11.3138 22.3472L11.3006 19.6664L12.2743 19.1145L15.4189 17.0119L15.1821 20.7703V21.9005L12.8269 23.2147C12.3927 23.4774 12.248 23.9242 12.4585 24.2922C12.7216 24.7259 13.1427 24.7653 13.5769 24.5156L15.1821 23.6352V25.6327C15.1821 26.1451 15.511 26.5 15.9979 26.5Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const SparklesIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - fillRule="evenodd" - clipRule="evenodd" - d="M15.4158 9.58939C15.557 9.58939 15.634 9.5014 15.6596 9.38827L15.6768 9.30148C15.9801 7.76575 15.9861 7.7357 17.6746 7.41481C17.8029 7.38966 17.8928 7.31425 17.8928 7.17598C17.8928 7.03772 17.8029 6.96229 17.6746 6.93716C15.9861 6.61628 15.9801 6.5862 15.6768 5.05049L15.6596 4.96369C15.634 4.83799 15.557 4.75 15.4158 4.75C15.2875 4.75 15.2104 4.83799 15.1848 4.96369L15.1597 5.08576C14.8519 6.58669 14.8453 6.61875 13.1698 6.93716C13.0415 6.96229 12.9517 7.03772 12.9517 7.17598C12.9517 7.31425 13.0415 7.38966 13.1698 7.41481C14.8453 7.73322 14.8519 7.76527 15.1597 9.26621L15.1848 9.38827C15.2104 9.5014 15.2875 9.58939 15.4158 9.58939ZM10.6035 16.4147C10.8089 16.4147 10.95 16.2764 10.9757 16.0753C11.321 13.4578 11.4369 13.4358 14.1925 12.9135L14.2227 12.9077C14.4152 12.8826 14.5692 12.7443 14.5692 12.5432C14.5692 12.3295 14.4152 12.1912 14.2227 12.1661C11.4377 11.8141 11.3094 11.6884 10.9757 9.01107C10.95 8.79738 10.8089 8.65912 10.6035 8.65912C10.3853 8.65912 10.2442 8.79738 10.2185 9.02364C9.91476 11.6143 9.72334 11.6502 7.08418 12.145L6.97152 12.1661C6.77901 12.2038 6.625 12.3295 6.625 12.5432C6.625 12.7569 6.77901 12.8826 7.02285 12.9077C9.74366 13.3225 9.9105 13.4231 10.2185 16.0502C10.2442 16.2764 10.3853 16.4147 10.6035 16.4147ZM17.957 26.7724C17.9057 27.0615 17.6875 27.2501 17.418 27.2501C17.1357 27.2501 16.9175 27.0615 16.879 26.7724C16.0961 21.5307 15.2876 20.7011 9.9743 19.9972C9.66628 19.9721 9.46094 19.7458 9.46094 19.4693C9.46094 19.1802 9.66628 18.9665 9.9743 18.9288C15.3004 18.3128 16.1218 17.4078 16.879 12.1662C16.9175 11.8645 17.1357 11.676 17.418 11.676C17.6875 11.676 17.9057 11.8645 17.957 12.1662C18.7014 17.4078 19.5228 18.3128 24.849 18.9288C25.1568 18.9665 25.3751 19.1802 25.3751 19.4693C25.3751 19.7458 25.1568 19.9721 24.849 19.9972C19.5228 20.6131 18.7014 21.5307 17.957 26.7724Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const SunIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - fillRule="evenodd" - clipRule="evenodd" - d="M16.9557 8.43851C16.9557 8.95491 16.5241 9.39754 16.0062 9.39754C15.4759 9.39754 15.0443 8.95491 15.0443 8.43851V6.4467C15.0443 5.93033 15.4759 5.5 16.0062 5.5C16.5241 5.5 16.9557 5.93033 16.9557 6.4467V8.43851ZM22.0362 11.3402C21.6539 11.7213 21.0373 11.709 20.6673 11.3402C20.3097 10.9713 20.3097 10.3688 20.6673 10L22.0977 8.57376C22.4677 8.20492 23.0842 8.20492 23.4542 8.57376C23.8119 8.94262 23.8119 9.56968 23.4542 9.93851L22.0362 11.3402ZM23.5652 16.959C23.0473 16.959 22.6033 16.5164 22.6033 16C22.6033 15.4836 23.0473 15.041 23.5652 15.041H25.5381C26.0683 15.041 26.5 15.4836 26.5 16C26.5 16.5164 26.0683 16.959 25.5381 16.959H23.5652ZM20.6673 22.0123C20.3097 21.6434 20.3097 21.041 20.6673 20.6721C21.0373 20.3033 21.6539 20.3033 22.0237 20.6721L23.4542 22.0737C23.8119 22.4426 23.8119 23.0573 23.4542 23.4262C23.0842 23.7951 22.4677 23.8074 22.0977 23.4385L20.6673 22.0123ZM15.0443 23.5615C15.0443 23.045 15.4759 22.6025 16.0062 22.6025C16.5241 22.6025 16.9557 23.045 16.9557 23.5615V25.5533C16.9557 26.0697 16.5241 26.4999 16.0062 26.4999C15.4759 26.4999 15.0443 26.0697 15.0443 25.5533V23.5615ZM9.96388 20.6721C10.3338 20.3033 10.9627 20.3033 11.3326 20.6721C11.6903 21.041 11.6903 21.6434 11.3203 22.0123L9.90224 23.4262C9.53228 23.7951 8.91573 23.7828 8.54581 23.4139C8.18821 23.045 8.18821 22.4303 8.55813 22.0614L9.96388 20.6721ZM8.43481 15.041C8.96506 15.041 9.39665 15.4836 9.39665 16C9.39665 16.5164 8.96506 16.959 8.43481 16.959H6.46182C5.93159 16.959 5.5 16.5164 5.5 16C5.5 15.4836 5.93159 15.041 6.46182 15.041H8.43481ZM11.3326 10C11.6903 10.3566 11.6903 10.9836 11.3203 11.3402C10.9504 11.709 10.3338 11.709 9.96388 11.3402L8.55813 9.92623C8.20053 9.56968 8.20053 8.94262 8.57046 8.57376C8.92808 8.20492 9.55695 8.21721 9.91455 8.57376L11.3326 10ZM21.0255 16C21.0255 18.7541 18.7442 21.0164 16.0067 21.0164C13.2569 21.0164 10.9756 18.7541 10.9756 16C10.9756 13.2459 13.2569 10.9836 16.0067 10.9836C18.7442 10.9836 21.0255 13.2459 21.0255 16Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const HareIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M16.5439 23.5858C17.0784 23.5858 17.5172 23.3355 18.5286 23.3355C19.5306 23.3355 19.7882 23.5858 20.523 23.5858C21.6298 23.5858 22.2024 23.1238 22.2024 22.2958C22.2024 20.9288 20.8092 20.0528 18.5191 20.0528C17.3932 20.0528 16.897 20.1491 16.2672 20.3127L15.4466 18.4259C14.6642 16.6739 13.3951 15.4898 11.5535 15.4898H10.6661C10.3702 15.4898 10.1603 15.355 10.1603 15.047C10.1603 14.5657 10.7901 14.4405 11.4867 14.4405C13.7004 14.4405 15.3321 15.6727 16.334 18.1371L16.8397 19.3886C17.3455 19.2827 17.8989 19.2346 18.4428 19.2346C18.8912 19.2346 19.3207 19.2635 19.75 19.3212C20.1985 18.9843 20.7233 18.6473 21.5057 18.3874C22.3456 18.9169 23.2425 19.2538 24.2157 19.2538C26.3149 19.2538 27.25 18.8014 27.25 16.7702C27.25 14.4405 25.628 12.6788 23.2806 12.6307L21.2481 9.95451C20.1031 8.45275 18.7576 7.75 17.4409 7.75C16.3436 7.75 14.6737 8.39499 14.6737 9.2999C14.6737 9.90636 15.9237 10.792 16.8493 11.3792L21.1432 14.0747C20.8855 14.3057 20.6089 14.4694 20.2367 14.4694C19.6356 14.4694 18.9868 14.0747 18.0803 13.5452C15.7615 12.1975 13.9771 10.9461 11.5725 10.9461C9.07255 10.9461 7.17368 12.3997 6.16222 15.2492C5.36069 15.2492 4.75 15.7882 4.75 16.568C4.75 17.444 5.44657 18.0024 6.38168 18.0024C7.48856 19.5522 9.30154 20.1587 11.4008 20.1587C11.5153 20.1587 11.6298 20.1491 11.7443 20.1491L14.7977 22.7675C15.5897 23.4895 15.9714 23.5858 16.5439 23.5858ZM11.1909 24.25C12.6603 24.25 13.5287 23.9516 14.2252 23.451L11.916 21.4679C11.8111 21.4775 11.6871 21.4871 11.5344 21.4871C11.1336 21.4871 10.6374 21.3813 9.94086 21.3813C9.10115 21.3813 8.54772 21.8434 8.54772 22.5461C8.54772 23.5762 9.60689 24.25 11.1909 24.25ZM24.0727 16.4428C23.7196 16.4428 23.4524 16.1541 23.4524 15.8075C23.4524 15.4513 23.7291 15.1432 24.0821 15.1432C24.4447 15.1432 24.7119 15.4224 24.7119 15.769C24.7119 16.1252 24.4352 16.4428 24.0727 16.4428Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const FlashIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M9.25 17.1338C9.25 17.5472 9.58422 17.8543 10.0139 17.8543H15.2778L12.5086 25.2952C12.1147 26.3699 13.2367 26.9488 13.9529 26.0747L22.4874 15.5276C22.6545 15.3032 22.75 15.1024 22.75 14.8661C22.75 14.4528 22.4277 14.1457 21.9861 14.1457H16.7221L19.4914 6.70481C19.8972 5.63 18.7633 5.05129 18.0471 5.92528L9.51259 16.4724C9.34548 16.6968 9.25 16.8976 9.25 17.1338Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const BankCardIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M9.17928 21.2322C8.65824 21.2322 8.3071 20.855 8.3071 20.3196V18.5553C8.3071 18.0199 8.65824 17.6427 9.17928 17.6427H11.354C11.8751 17.6427 12.2262 18.0199 12.2262 18.5553V20.3196C12.2262 20.855 11.8751 21.2322 11.354 21.2322H9.17928ZM5.49805 13.9192V11.7289H26.498V13.9192H5.49805ZM8.38639 24.25H23.6097C25.5465 24.25 26.498 23.24 26.498 21.1957V10.8042C26.498 8.75994 25.5465 7.75 23.6097 7.75H8.38639C6.46082 7.75 5.49805 8.75994 5.49805 10.8042V21.1957C5.49805 23.24 6.46082 24.25 8.38639 24.25Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const GearIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M15.9941 25.033C16.2078 25.033 16.4214 25.0211 16.6351 25.0093L17.1693 26.0308C17.288 26.2683 17.5373 26.387 17.8104 26.3396C18.0715 26.2802 18.2733 26.0783 18.2971 25.805L18.4751 24.6885C18.8906 24.5816 19.2824 24.4154 19.686 24.261L20.517 24.9974C20.7069 25.1756 21.0037 25.2349 21.253 25.0925C21.4785 24.9498 21.5973 24.6766 21.5261 24.4154L21.3004 23.3226C21.6329 23.0612 21.9772 22.788 22.274 22.503L23.3068 22.9306C23.5915 23.0494 23.8527 22.9662 24.0427 22.7406C24.197 22.5387 24.2207 22.2536 24.0902 22.0279L23.4847 21.0658C23.7222 20.7095 23.924 20.3413 24.1258 19.9374L25.2536 19.9849C25.5148 19.9968 25.776 19.8543 25.8589 19.5811C25.9539 19.3316 25.8471 19.0585 25.6453 18.8922L24.7669 18.1914C24.8737 17.7875 24.9449 17.3599 24.9805 16.9204L26.037 16.576C26.3219 16.481 26.5 16.279 26.5 15.994C26.5 15.7089 26.3219 15.507 26.037 15.412L24.9805 15.0675C24.9449 14.628 24.8737 14.2123 24.7669 13.7966L25.6453 13.0958C25.8471 12.9295 25.9539 12.6682 25.8589 12.4188C25.776 12.1456 25.5148 12.003 25.2536 12.0149L24.1258 12.0505C23.924 11.6467 23.7222 11.2904 23.4847 10.9221L24.0902 9.96004C24.2207 9.74623 24.197 9.46116 24.0427 9.25924C23.8527 9.03356 23.5915 8.9623 23.3186 9.0692L22.274 9.48493C21.9772 9.21173 21.6329 8.92666 21.3004 8.67723L21.5261 7.58447C21.5853 7.2994 21.4785 7.03809 21.253 6.90744C21.0037 6.7649 20.7069 6.80054 20.517 7.00246L19.686 7.72701C19.2824 7.5726 18.8906 7.41818 18.4751 7.2994L18.2971 6.19477C18.2733 5.92158 18.0715 5.71965 17.8104 5.66026C17.5373 5.61275 17.288 5.73153 17.1693 5.95721L16.6351 6.9787C16.4214 6.96683 16.2078 6.95495 15.9941 6.95495C15.7685 6.95495 15.5667 6.96683 15.3412 6.9787L14.807 5.95721C14.6883 5.73153 14.439 5.61275 14.1659 5.66026C13.9048 5.71965 13.7267 5.92158 13.6792 6.19477L13.5011 7.2994C13.0975 7.41818 12.6939 7.5726 12.3022 7.72701L11.4712 7.00246C11.2694 6.80054 10.9845 6.7649 10.7233 6.90744C10.4977 7.03809 10.4028 7.2994 10.4621 7.58447L10.6758 8.67723C10.3553 8.92666 9.99916 9.21173 9.70237 9.48493L8.65771 9.0692C8.39655 8.9623 8.1354 9.03356 7.94546 9.25924C7.79112 9.46116 7.75552 9.74623 7.89796 9.96004L8.49152 10.9221C8.26597 11.2904 8.06416 11.6467 7.85049 12.0505L6.72273 12.0149C6.47343 12.003 6.21227 12.1456 6.12917 12.4188C6.04607 12.6682 6.12917 12.9295 6.33098 13.0958L7.20944 13.7966C7.10261 14.2123 7.03137 14.628 7.00763 15.0675L5.92736 15.412C5.65432 15.4951 5.5 15.7089 5.5 15.994C5.5 16.279 5.65432 16.4928 5.92736 16.576L7.00763 16.9204C7.03137 17.3599 7.10261 17.7875 7.20944 18.1914L6.33098 18.8922C6.12917 19.0585 6.04607 19.3316 6.12917 19.5811C6.21227 19.8543 6.47343 19.9968 6.72273 19.9849L7.85049 19.9374C8.06416 20.3413 8.26597 20.7095 8.49152 21.0658L7.89796 22.0279C7.75552 22.2536 7.79112 22.5387 7.94546 22.7406C8.1354 22.9662 8.39655 23.0494 8.66959 22.9306L9.70237 22.503C9.99916 22.788 10.3553 23.0612 10.6758 23.3226L10.4621 24.4154C10.4147 24.6766 10.4977 24.9498 10.7233 25.0925C10.9845 25.2349 11.2694 25.1756 11.4712 24.9974L12.3022 24.261C12.6939 24.4154 13.0975 24.5816 13.5011 24.6885L13.6792 25.805C13.7267 26.0783 13.9048 26.2802 14.1659 26.3396C14.439 26.387 14.6883 26.2683 14.807 26.0308L15.3412 25.0093C15.5667 25.0211 15.7685 25.033 15.9941 25.033ZM15.9941 23.3226C11.8985 23.3226 8.76456 20.0681 8.76456 16.0059C8.76456 11.9318 11.8985 8.66536 15.9941 8.66536C20.0896 8.66536 23.2355 11.9318 23.2355 16.0059C23.2355 20.0681 20.0896 23.3226 15.9941 23.3226ZM14.249 14.5449L15.4955 13.7609L12.4209 8.48719L11.115 9.21173L14.249 14.5449ZM18.1546 16.7185H24.2802V15.2694H18.1546V16.7185ZM15.4836 18.2745L14.2609 17.455L11.0082 22.7643L12.2903 23.5126L15.4836 18.2745ZM15.9585 18.5715C17.383 18.5715 18.5226 17.4312 18.5226 16.0177C18.5226 14.6043 17.383 13.4521 15.9585 13.4521C14.5458 13.4521 13.418 14.6043 13.418 16.0177C13.418 17.4312 14.5458 18.5715 15.9585 18.5715ZM15.9585 16.9917C15.4124 16.9917 14.9969 16.576 14.9969 16.0177C14.9969 15.4595 15.4124 15.0319 15.9585 15.0319C16.5164 15.0319 16.9438 15.4595 16.9438 16.0177C16.9438 16.576 16.5164 16.9917 15.9585 16.9917Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const HandIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M9.25 19.6033C9.25 23.8703 12.0418 26.5 15.97 26.5C19.0705 26.5 20.8864 25.2719 22.1958 22.9524C23.0111 21.5384 23.6164 19.7646 24.2587 17.6683C24.5552 16.7256 25 15.51 25 14.9642C25 14.4309 24.6047 14.0587 24.0241 14.0587C23.3447 14.0587 22.9741 14.4929 22.4676 15.51L21.0964 18.3133C20.9729 18.5862 20.837 18.7102 20.6888 18.7102C20.5282 18.7102 20.417 18.611 20.417 18.3009V7.90636C20.417 7.29857 19.9353 6.81482 19.33 6.81482C18.737 6.81482 18.2429 7.29857 18.2429 7.90636V15.3736C17.9959 15.2867 17.7241 15.2123 17.4276 15.1627V6.59155C17.4276 5.99616 16.9335 5.5 16.3406 5.5C15.7353 5.5 15.2535 5.99616 15.2535 6.59155V15.0635C14.9694 15.0883 14.6976 15.1255 14.4135 15.1751V7.36061C14.4135 6.7528 13.9317 6.26905 13.3265 6.26905C12.7335 6.26905 12.2394 6.7528 12.2394 7.36061V15.8573C11.9553 16.0062 11.6835 16.1674 11.4118 16.3287V10.412C11.4118 9.81659 10.93 9.32043 10.337 9.32043C9.73176 9.32043 9.25 9.81659 9.25 10.412V19.6033Z" - fill="currentColor" - /> - </svg> - ); -}; - -export const GlassCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.5 16C26.5 21.7379 21.7278 26.5 15.9935 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9805 5.5C21.7148 5.5 26.5 10.2621 26.5 16ZM10.4412 14.803C10.4412 17.236 12.4437 19.2397 14.8752 19.2397C15.7074 19.2397 16.4746 19.0056 17.1378 18.6152L19.7254 21.1784C19.9594 21.4256 20.2195 21.5427 20.5576 21.5427C21.1167 21.5427 21.5198 21.1134 21.5198 20.5148C21.5198 20.2546 21.3898 19.9944 21.2077 19.8122L18.6071 17.197C19.0492 16.5074 19.3093 15.6747 19.3093 14.803C19.3093 12.3439 17.3198 10.3531 14.8752 10.3531C12.4437 10.3531 10.4412 12.3569 10.4412 14.803ZM17.6709 14.803C17.6709 16.3383 16.4096 17.6134 14.8752 17.6134C13.3278 17.6134 12.0666 16.3383 12.0666 14.803C12.0666 13.2676 13.3278 11.9926 14.8752 11.9926C16.4096 11.9926 17.6709 13.2546 17.6709 14.803Z" - fill="white" - /> - </svg> - ); -}; -export const FlashCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.4998 16C26.4998 21.7379 21.7277 26.5 15.9934 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9804 5.5C21.7147 5.5 26.4998 10.2621 26.4998 16ZM17.2807 9.10408L11.5854 16.2472C11.4684 16.3773 11.4033 16.5465 11.4033 16.6896C11.4033 16.9758 11.6374 17.197 11.9235 17.197H15.4473L13.5748 22.2194C13.3018 22.948 14.082 23.3382 14.5631 22.7528L20.2584 15.5966C20.3754 15.4665 20.4404 15.2974 20.4404 15.1673C20.4404 14.868 20.2064 14.6598 19.9203 14.6598H16.3965L18.2689 9.63755C18.542 8.90893 17.7618 8.5186 17.2807 9.10408Z" - fill="white" - /> - </svg> - ); -}; - -export const DollarCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.4998 16C26.4998 21.7379 21.7277 26.5 15.9934 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9804 5.5C21.7147 5.5 26.4998 10.2621 26.4998 16ZM15.4473 10.4702V11.1989C13.8349 11.394 12.7036 12.3959 12.7036 13.8792C12.7036 15.2713 13.6269 16.0911 15.3953 16.5074L15.4473 16.5334V19.4869C14.5761 19.3438 14.108 18.8624 13.9259 18.2379C13.7829 17.8346 13.5358 17.6654 13.1978 17.6654C12.7947 17.6654 12.5086 17.9516 12.5086 18.355C12.5086 18.4721 12.5346 18.5892 12.5476 18.6933C12.8727 19.9163 14.069 20.671 15.4473 20.8141V21.5427C15.4473 21.868 15.6813 22.1022 16.0064 22.1022C16.3185 22.1022 16.5525 21.868 16.5525 21.5427V20.8271C18.1649 20.671 19.4652 19.7342 19.4652 18.0688C19.4652 16.5855 18.503 15.6877 16.6566 15.3754L16.5525 15.3624V12.565C17.2157 12.6952 17.6708 13.0985 17.9049 13.7751C18.0219 14.1264 18.2559 14.3476 18.633 14.3476C19.0361 14.3476 19.3222 14.0613 19.3222 13.658C19.3222 13.5409 19.2962 13.4238 19.2701 13.3067C18.9711 12.1487 17.9049 11.368 16.5525 11.1989V10.4702C16.5525 10.145 16.3185 9.9238 16.0064 9.9238C15.6813 9.9238 15.4473 10.145 15.4473 10.4702ZM16.7736 16.8717C17.7098 17.1319 17.9959 17.5483 17.9959 18.1598C17.9959 18.8364 17.5667 19.3569 16.5525 19.4869V16.8067L16.7736 16.8717ZM15.4473 15.0632L15.3693 15.0502C14.5241 14.7639 14.173 14.3736 14.173 13.8011C14.173 13.2286 14.6281 12.7212 15.4473 12.565V15.0632Z" - fill="white" - /> - </svg> - ); -}; - -export const EuroCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.4998 16C26.4998 21.7379 21.7277 26.5 15.9934 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9804 5.5C21.7147 5.5 26.4998 10.2621 26.4998 16ZM12.1575 14.2695H11.1303C10.8052 14.2695 10.5712 14.5037 10.5712 14.842C10.5712 15.1543 10.8052 15.4275 11.1303 15.4275H11.9885C11.9755 15.6096 11.9625 15.8048 11.9625 16.013C11.9625 16.1951 11.9625 16.3643 11.9755 16.5334H11.1303C10.8052 16.5334 10.5712 16.7676 10.5712 17.1059C10.5712 17.4182 10.8052 17.6784 11.1303 17.6784H12.1445C12.6256 19.7732 14.043 21.2304 16.2795 21.2304C18.0869 21.2304 19.5562 20.4758 20.1413 18.9015C20.2064 18.7193 20.2454 18.5372 20.2454 18.342C20.2454 17.9126 19.9723 17.6264 19.5172 17.6264C19.1271 17.6264 18.8931 17.8215 18.763 18.2379C18.425 19.2788 17.3977 19.7862 16.3315 19.7862C15.0572 19.7862 14.199 18.9145 13.8219 17.6784H16.4355C16.7606 17.6784 16.9946 17.4182 16.9946 17.1059C16.9946 16.7676 16.7606 16.5334 16.4355 16.5334H13.5878C13.5748 16.3643 13.5618 16.1821 13.5618 16.013C13.5618 15.8048 13.5748 15.6096 13.5878 15.4275H16.4355C16.7606 15.4275 16.9946 15.1543 16.9946 14.842C16.9946 14.5037 16.7606 14.2695 16.4355 14.2695H13.8479C14.238 13.0855 15.0832 12.2528 16.3315 12.2528C17.3067 12.2528 18.3209 12.7472 18.672 13.8011C18.815 14.2695 19.0231 14.4126 19.4392 14.4126C19.8813 14.4126 20.1674 14.1264 20.1674 13.697C20.1674 13.5018 20.1284 13.3327 20.0633 13.1505C19.5172 11.6933 17.9959 10.8085 16.2795 10.8085C14.186 10.8085 12.6906 12.1617 12.1575 14.2695Z" - fill="white" - /> - </svg> - ); -}; - -export const SterlingCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.4998 16C26.4998 21.7379 21.7277 26.5 15.9934 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9804 5.5C21.7147 5.5 26.4998 10.2621 26.4998 16ZM13.4188 13.9182C13.4188 14.3085 13.4708 14.6989 13.5618 15.0892H12.6126C12.2355 15.0892 11.9625 15.3364 11.9625 15.7137C11.9625 16.0911 12.2355 16.3513 12.6126 16.3513H13.8739C13.9519 16.6896 14.0039 17.0279 14.0039 17.3271C14.0039 18.4851 13.3018 19.1747 12.5996 19.4089C12.1575 19.539 11.9755 19.7732 11.9755 20.1635C11.9755 20.5929 12.2616 20.8661 12.6777 20.8661H19.1531C19.5692 20.8661 19.8683 20.5929 19.8683 20.1635C19.8683 19.7602 19.5692 19.4739 19.1531 19.4739H14.6411C15.1742 19.0056 15.5253 18.1728 15.5253 17.2621C15.5253 16.9238 15.4993 16.6245 15.4343 16.3513H17.9959C18.386 16.3513 18.659 16.0911 18.659 15.7137C18.659 15.3364 18.386 15.0892 17.9959 15.0892H15.1352C15.0572 14.7119 14.9662 14.3085 14.9662 13.8141C14.9662 12.6821 15.8114 11.9405 17.1377 11.9405C17.9699 11.9405 18.412 12.1487 18.724 12.1487C19.1661 12.1487 19.3742 11.9015 19.3742 11.5241C19.3742 11.1989 19.2051 10.9517 18.7891 10.7825C18.2429 10.5874 17.6448 10.5483 17.0336 10.5483C14.8361 10.5483 13.4188 11.8364 13.4188 13.9182Z" - fill="white" - /> - </svg> - ); -}; - -export const YuanCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.4998 16C26.4998 21.7379 21.7277 26.5 15.9934 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9804 5.5C21.7147 5.5 26.4998 10.2621 26.4998 16ZM12.0405 11.381C12.0405 11.5632 12.0795 11.6933 12.1835 11.8755L14.7321 16.013H13.3148C12.9767 16.013 12.7427 16.2732 12.7427 16.5985C12.7427 16.9238 12.9897 17.171 13.3148 17.171H15.2522V17.9647H13.3148C12.9767 17.9647 12.7427 18.2119 12.7427 18.5372C12.7427 18.8754 12.9897 19.1096 13.3148 19.1096H15.2522V20.5148C15.2522 20.9963 15.5643 21.3215 15.9934 21.3215C16.4355 21.3215 16.7476 21.0093 16.7476 20.5279V19.1096H18.685C19.0231 19.1096 19.2442 18.8754 19.2442 18.5372C19.2442 18.2119 19.0231 17.9647 18.685 17.9647H16.7476V17.171H18.685C19.0231 17.171 19.2442 16.9238 19.2442 16.5985C19.2442 16.2732 19.0231 16.013 18.685 16.013H17.2807L19.8293 11.8885C19.9333 11.7063 19.9723 11.5762 19.9723 11.394C19.9723 11.0037 19.6472 10.6914 19.2181 10.6914C18.9321 10.6914 18.711 10.8215 18.542 11.0818L16.0064 15.4275L13.4708 11.0687C13.3018 10.7955 13.0807 10.6784 12.7947 10.6784C12.3656 10.6784 12.0405 10.9907 12.0405 11.381Z" - fill="white" - /> - </svg> - ); -}; - -export const RubleCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.4998 16C26.4998 21.7379 21.7277 26.5 15.9934 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9804 5.5C21.7147 5.5 26.4998 10.2621 26.4998 16ZM14.212 11.0818C13.7309 11.0818 13.4318 11.394 13.4318 11.8625V16.4033H12.5086C12.1575 16.4033 11.9365 16.6505 11.9365 16.9758C11.9365 17.3141 12.1575 17.5613 12.5086 17.5613H13.4318V18.5632H12.5216C12.1705 18.5632 11.9495 18.8104 11.9495 19.1357C11.9495 19.4739 12.1705 19.7082 12.5216 19.7082H13.4318V20.6319C13.4318 21.0873 13.7569 21.4126 14.225 21.4126C14.6671 21.4126 14.9792 21.0873 14.9792 20.6319V19.7082H17.4367C17.7748 19.7082 18.0089 19.4739 18.0089 19.1357C18.0089 18.8104 17.7748 18.5632 17.4367 18.5632H14.9792V17.5613H16.7476C18.75 17.5613 20.0373 16.1951 20.0373 14.3216C20.0373 12.4609 18.763 11.0818 16.7606 11.0818H14.212ZM18.464 14.3346C18.464 15.4665 17.8008 16.1951 16.4615 16.1951L14.9922 16.1691V12.5H16.4615C17.8138 12.5 18.464 13.2026 18.464 14.3346Z" - fill="white" - /> - </svg> - ); -}; - -export const RupeeCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.5 16C26.5 21.7379 21.7278 26.5 15.9935 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9805 5.5C21.7148 5.5 26.5 10.2621 26.5 16ZM16.8257 11.0948C16.4876 11.0948 16.1365 11.0948 15.7984 11.0948H12.9378C12.5607 11.0948 12.2486 11.4071 12.2486 11.7974C12.2486 12.1747 12.5607 12.513 12.9378 12.513H14.7192C15.6684 12.513 16.3056 12.9033 16.5266 13.6189H12.9248C12.6127 13.6189 12.3526 13.8531 12.3526 14.1914C12.3526 14.5167 12.6127 14.7769 12.9248 14.7769H16.5656C16.3576 15.5446 15.7204 15.9349 14.7192 15.9349H13.2108C12.7037 15.9349 12.3396 16.2472 12.3396 16.7546C12.3396 17.158 12.5477 17.3531 12.7687 17.5743L16.5136 21.0873C16.7737 21.3475 16.9687 21.4126 17.2288 21.4126C17.6189 21.4126 17.9439 21.1654 17.9439 20.723C17.9439 20.4888 17.7879 20.2806 17.6319 20.1115L14.6672 17.3401H14.8492C16.9167 17.3401 17.9439 16.0911 18.165 14.7769H19.1922C19.5303 14.7769 19.7514 14.5167 19.7514 14.1914C19.7514 13.8531 19.5303 13.6189 19.1922 13.6189H18.191C18.1 13.0855 17.9179 12.6301 17.6059 12.2528H19.1272C19.4653 12.2528 19.6994 11.9795 19.6994 11.6673C19.6994 11.329 19.4653 11.0948 19.1272 11.0948H16.8257Z" - fill="white" - /> - </svg> - ); -}; - -export const HashCircleIcon = () => { - const theme = useTheme(); - return ( - <svg - width="32" - height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - color={theme.iconPrimary} - > - <path - d="M26.5 16C26.5 21.7379 21.7278 26.5 15.9935 26.5C10.2721 26.5 5.5 21.7379 5.5 16C5.5 10.2621 10.2591 5.5 15.9805 5.5C21.7148 5.5 26.5 10.2621 26.5 16ZM17.9049 11.2119L17.4628 13.3197H15.5904L15.9675 11.4461C16.0715 10.9647 15.7334 10.5353 15.2393 10.5353C14.7712 10.5353 14.4721 10.7825 14.3811 11.2119L13.939 13.3197H12.8988C12.4306 13.3197 12.0926 13.671 12.0926 14.1264C12.0926 14.5297 12.3786 14.829 12.7687 14.829H13.6139L13.1978 16.8717H12.1446C11.6765 16.8717 11.3384 17.223 11.3384 17.6784C11.3384 18.0948 11.6375 18.381 12.0276 18.381H12.8858L12.4567 20.3717C12.3786 20.8661 12.7297 21.2825 13.2108 21.2825C13.6789 21.2825 13.965 21.0353 14.056 20.5929L14.5241 18.394H16.3966L15.9935 20.3717C15.8895 20.8661 16.2275 21.2825 16.7087 21.2825C17.1898 21.2825 17.5019 21.0353 17.5929 20.5929L18.048 18.381H19.1012C19.5563 18.381 19.8944 18.0297 19.8944 17.5743C19.8944 17.184 19.6083 16.8717 19.2183 16.8717H18.3731L18.8022 14.829H19.8424C20.3105 14.829 20.6486 14.4777 20.6486 14.0223C20.6486 13.6189 20.3625 13.3197 19.9724 13.3197H19.1142L19.5173 11.4461C19.5953 10.9517 19.2443 10.5353 18.7631 10.5353C18.295 10.5353 17.996 10.7825 17.9049 11.2119ZM16.7867 17.0279H14.7322L15.2133 14.6989H17.2808L16.7867 17.0279Z" - fill="white" - /> - </svg> - ); -}; - -export const emojiIcons = [ - { - name: 'custom:wallet', - icon: WalletIcon - }, - { - name: 'custom:leaf', - icon: LeafIcon - }, - { - name: 'custom:lock', - icon: LockIcon - }, - { - name: 'custom:key', - icon: KeyIcon - }, - { - name: 'custom:inbox', - icon: InboxIcon - }, - { - name: 'custom:snowflake', - icon: SnowflakeIcon - }, - { - name: 'custom:sparkles', - icon: SparklesIcon - }, - { - name: 'custom:sun', - icon: SunIcon - }, - { - name: 'custom:hare', - icon: HareIcon - }, - { - name: 'custom:flash', - icon: FlashIcon - }, - { - name: 'custom:bankcard', - icon: BankCardIcon - }, - { - name: 'custom:gear', - icon: GearIcon - }, - { - name: 'custom:hand', - icon: HandIcon - }, - { - name: 'custom:glass_circle', - icon: GlassCircleIcon - }, - { - name: 'custom:flash_circle', - icon: FlashCircleIcon - }, - { - name: 'custom:dollar_circle', - icon: DollarCircleIcon - }, - { - name: 'custom:euro_circle', - icon: EuroCircleIcon - }, - { - name: 'custom:sterling_circle', - icon: SterlingCircleIcon - }, - { - name: 'custom:yuan_circle', - icon: YuanCircleIcon - }, - { - name: 'custom:ruble_circle', - icon: RubleCircleIcon - }, - { - name: 'custom:rupee_circle', - icon: RupeeCircleIcon - }, - { - name: 'custom:hash_circle', - icon: HashCircleIcon - } -]; diff --git a/packages/uikit/src/components/shared/emoji/EmojisList.tsx b/packages/uikit/src/components/shared/emoji/EmojisList.tsx new file mode 100644 index 000000000..ea8f91d18 --- /dev/null +++ b/packages/uikit/src/components/shared/emoji/EmojisList.tsx @@ -0,0 +1,85 @@ +import { emojis } from '@tonkeeper/core/dist/utils/emojis'; +import React, { FC, useEffect, useState } from 'react'; +import { emojiIcons } from './emojiIcons'; +import styled from 'styled-components'; + +const EmojisListScroll = styled.div` + max-height: 240px; + display: flex; + flex-wrap: wrap; + align-items: center; + overflow: auto; + position: relative; + + &::-webkit-scrollbar { + display: none; + width: 0; + background: transparent; + height: 0; + } + + -ms-overflow-style: none; + scrollbar-width: none; + + /* optimise large emojis list rendering avoiding styled components */ + > .emoji-button { + height: 32px; + width: 32px; + line-height: 24px; + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + } +`; + +const Shadow = styled.div` + position: sticky; + width: 100%; + height: 16px; +`; + +const ShadowBottom = styled(Shadow)` + bottom: -1px; + background: ${props => props.theme.gradientBackgroundBottom}; +`; + +const ShadowTop = styled(Shadow)` + top: 0; + background: ${props => props.theme.gradientBackgroundTop}; +`; + +const shortEmojisList = emojis.slice(0, 150); + +export const EmojisList: FC<{ onClick: (emoji: string) => void; keepShortListForMS?: number }> = + React.memo(({ onClick, keepShortListForMS }) => { + const [emojisList, setEmojisList] = useState(keepShortListForMS ? shortEmojisList : emojis); + + useEffect(() => { + if (keepShortListForMS) { + setTimeout(() => setEmojisList(emojis), keepShortListForMS); + } + }, []); + + return ( + <EmojisListScroll> + <ShadowTop /> + {emojiIcons.map(item => ( + <div + className="emoji-button" + key={item.name} + onClick={() => onClick(item.name)} + > + <item.icon /> + </div> + ))} + {emojisList.map(emoji => ( + <div className="emoji-button" key={emoji} onClick={() => onClick(emoji)}> + {emoji} + </div> + ))} + <ShadowBottom /> + </EmojisListScroll> + ); + }); diff --git a/packages/uikit/src/components/shared/emoji/WalletEmoji.tsx b/packages/uikit/src/components/shared/emoji/WalletEmoji.tsx new file mode 100644 index 000000000..994d7459e --- /dev/null +++ b/packages/uikit/src/components/shared/emoji/WalletEmoji.tsx @@ -0,0 +1,44 @@ +import { FC } from 'react'; +import { emojiIcons } from './emojiIcons'; +import styled from 'styled-components'; + +const EmojiWrapper = styled.div<{ emojiSize?: string; containerSize?: string }>` + height: ${p => p.containerSize || '32px'}; + width: ${p => p.containerSize || '32px'}; + font-size: ${p => p.emojiSize || '24px'}; + display: flex; + align-items: center; + justify-content: center; + + > svg { + height: ${p => p.emojiSize || '24px'}; + width: ${p => p.emojiSize || '24px'}; + } +`; + +export const WalletEmoji: FC<{ + emoji: string; + emojiSize?: string; + containerSize?: string; + className?: string; +}> = ({ emoji, className, emojiSize, containerSize }) => { + if (emoji.startsWith('custom:')) { + const Emoji = emojiIcons.find(icon => icon.name === emoji); + + if (!Emoji) { + return null; + } + + return ( + <EmojiWrapper emojiSize={emojiSize} containerSize={containerSize} className={className}> + <Emoji.icon /> + </EmojiWrapper> + ); + } + + return ( + <EmojiWrapper emojiSize={emojiSize} containerSize={containerSize} className={className}> + {emoji} + </EmojiWrapper> + ); +}; diff --git a/packages/uikit/src/components/shared/emoji/emojiIcons.tsx b/packages/uikit/src/components/shared/emoji/emojiIcons.tsx new file mode 100644 index 000000000..c84c6126e --- /dev/null +++ b/packages/uikit/src/components/shared/emoji/emojiIcons.tsx @@ -0,0 +1,515 @@ +import { useTheme } from 'styled-components'; + +export const WalletIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M1.82698 4.63803C1.57654 5.12955 1.51792 5.73742 1.50419 6.75H1.5V7.8V12V16.2C1.5 17.8802 1.5 18.7202 1.82698 19.362C2.1146 19.9265 2.57354 20.3854 3.13803 20.673C3.77976 21 4.61984 21 6.3 21H17.7C19.3802 21 20.2202 21 20.862 20.673C21.4265 20.3854 21.8854 19.9265 22.173 19.362C22.5 18.7202 22.5 17.8802 22.5 16.2V11.55C22.5 9.86984 22.5 9.02976 22.173 8.38803C21.8854 7.82354 21.4265 7.3646 20.862 7.07698C20.5049 6.89506 20.0865 6.81436 19.4962 6.77855C19.4832 5.74882 19.4258 5.13415 19.173 4.63803C18.8854 4.07354 18.4265 3.6146 17.862 3.32698C17.2202 3 16.3802 3 14.7 3H6.3C4.61984 3 3.77976 3 3.13803 3.32698C2.57354 3.6146 2.1146 4.07354 1.82698 4.63803ZM17.625 6.75H3.375V6.675C3.375 6.04494 3.375 5.72991 3.49762 5.48926C3.60548 5.27758 3.77758 5.10548 3.98926 4.99762C4.22991 4.875 4.54494 4.875 5.175 4.875H15.825C16.4551 4.875 16.7701 4.875 17.0107 4.99762C17.2224 5.10548 17.3945 5.27758 17.5024 5.48926C17.625 5.72991 17.625 6.04494 17.625 6.675V6.75ZM19.125 12.75C18.5037 12.75 18 13.2537 18 13.875C18 14.4963 18.5037 15 19.125 15C19.7463 15 20.25 14.4963 20.25 13.875C20.25 13.2537 19.7463 12.75 19.125 12.75Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const LeafIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M1.79387 3.63695C1.57347 4.60707 1.5 5.91303 1.5 6.80855C1.5 14.1343 5.82234 18.9601 12.4589 18.9601C16.9771 18.9601 18.8506 16.199 19.2056 15.6766L17.8709 15.5647C19.1445 16.908 19.7567 18.4378 20.3689 20.3656C20.5159 20.8383 20.8342 21 21.177 21C21.8873 21 22.4995 20.378 22.4995 19.4951C22.4995 18.1766 20.5281 15.8383 19.5239 14.8806C15.2629 10.9006 8.66308 13.2637 7.13251 8.82346C7.0223 8.48762 7.37739 8.18912 7.708 8.52494C11.063 11.9329 15.3976 9.05976 19.5239 12.9279C19.8791 13.2513 20.2954 13.0771 20.3566 12.704C20.4055 12.4428 20.4302 11.9951 20.4302 11.5722C20.4302 6.7588 17.1118 4.40808 12.4956 4.40808C10.9283 4.40808 9.06713 4.8434 7.64678 4.8434C6.20191 4.8434 4.47543 4.76877 3.04282 3.27624C2.61426 2.84094 1.96529 2.89069 1.79387 3.63695Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const LockIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M7.04426 22.5H16.9557C18.6473 22.5 19.5 21.6277 19.5 19.7848V12.3706C19.5 10.5417 18.6473 9.66948 16.9557 9.66948H7.04426C5.35274 9.66948 4.5 10.5417 4.5 12.3706V19.7848C4.5 21.6277 5.35274 22.5 7.04426 22.5ZM6.40121 10.6402H8.37231V6.70102C8.37231 3.9717 10.1197 2.6352 11.993 2.6352C13.8802 2.6352 15.6416 3.9717 15.6416 6.70102V10.6402H17.6128V6.98237C17.6128 2.67737 14.8029 0.75 11.993 0.75C9.19709 0.75 6.40121 2.67737 6.40121 6.98237V10.6402Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const KeyIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M19.8738 4.12667C17.3626 1.61545 13.3466 1.62411 10.8424 4.12832C8.94635 6.02436 8.40487 8.80993 9.47946 11.2439L2.53918 18.1842C2.35143 18.372 2.27959 18.5685 2.27007 18.8451L2.24707 21.2191C2.25557 21.4421 2.47795 21.754 2.78138 21.7534L7.26136 21.7438C7.58273 21.743 7.80633 21.5016 7.80696 21.1982L7.79499 18.4852L11.5432 18.4771C11.8377 18.4854 12.0702 18.2529 12.062 17.9227L12.0968 14.219C15.1193 15.5065 17.9403 15.0899 19.8721 13.1581C22.3674 10.6628 22.3761 6.629 19.8738 4.12667ZM15.6882 8.31228C15.0203 7.64439 15.0405 6.57343 15.7023 5.9116C16.3731 5.24083 17.4261 5.23857 18.094 5.90646C18.753 6.56544 18.7685 7.6363 18.0978 8.30708C17.4359 8.96891 16.3472 8.97126 15.6882 8.31228Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const InboxIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M4.38679 19.5H19.6132C21.549 19.5 22.5 18.5504 22.5 16.6511V11.7334C22.5 10.8516 22.3641 10.4446 21.9566 9.89064L19.0472 5.92252C18.0169 4.50938 17.4735 4.125 15.7981 4.125H8.20188C6.53773 4.125 5.98302 4.50938 4.95282 5.92252L2.05472 9.89064C1.64717 10.4446 1.5 10.8516 1.5 11.7334V16.6511C1.5 18.5504 2.46226 19.5 4.38679 19.5ZM12.0057 14.2318C10.6811 14.2318 9.88867 13.0674 9.88867 12.0612V12.0273C9.88867 11.5977 9.6283 11.1794 9.07357 11.1794H3.63962C3.31132 11.1794 3.24339 10.9081 3.40189 10.682L6.53773 6.3182C6.94527 5.74164 7.47735 5.54945 8.1 5.54945H15.9C16.5226 5.54945 17.066 5.74164 17.4735 6.3182L20.5981 10.682C20.7452 10.9081 20.6887 11.1794 20.3603 11.1794H14.9264C14.383 11.1794 14.1226 11.5977 14.1226 12.0273V12.0612C14.1226 13.0674 13.3189 14.2318 12.0057 14.2318Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const SnowflakeIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M11.9979 22.5C12.5242 22.5 12.8268 22.1451 12.8268 21.6327V19.6352L14.4057 20.5156C14.8399 20.7653 15.2873 20.7259 15.5372 20.2922C15.7346 19.9242 15.6162 19.4774 15.1688 19.2147L12.8268 17.9005V16.7703L12.5768 13.0119L15.7346 15.1014L16.7083 15.6664L16.682 18.3472C16.682 18.8598 16.9977 19.2147 17.4188 19.2147C17.9319 19.2147 18.1819 18.8466 18.1819 18.3472L18.1951 16.5075L19.9582 17.5325C20.3924 17.7953 20.8397 17.6902 21.0898 17.2565C21.3397 16.8097 21.2082 16.3498 20.774 16.1001L19.024 15.1014L20.6029 14.1683C21.024 13.9186 21.2082 13.5112 20.9712 13.0907C20.7476 12.7096 20.274 12.6176 19.8398 12.8673L17.5372 14.234L16.5504 13.6689L13.1557 12L16.5504 10.331L17.524 9.76594L19.8398 11.1326C20.274 11.3824 20.7607 11.2904 20.9712 10.9224C21.2082 10.4887 21.024 10.0813 20.6029 9.83167L19.024 8.89863L20.774 7.89987C21.2213 7.63705 21.3529 7.20336 21.0898 6.74341C20.8528 6.32288 20.4055 6.21777 19.9582 6.46747L18.1951 7.4662L18.1819 5.63955C18.1819 5.1533 17.9319 4.78538 17.4319 4.78538C16.9977 4.78538 16.682 5.14016 16.682 5.63955L16.7083 8.32039L15.7346 8.89863L12.5768 10.9881L12.8268 7.21653V6.0995L15.1688 4.78538C15.6162 4.52253 15.7346 4.07571 15.5372 3.70776C15.2873 3.27409 14.8399 3.23466 14.4057 3.4712L12.8268 4.36485V2.36734C12.8268 1.85483 12.5242 1.5 11.9979 1.5C11.511 1.5 11.1821 1.85483 11.1821 2.36734V4.36485L9.5769 3.4712C9.14271 3.23466 8.72163 3.27409 8.4585 3.70776C8.24798 4.07571 8.3927 4.52253 8.82693 4.7722L11.1821 6.0995V7.21653L11.4189 10.9881L8.2743 8.89863L7.28748 8.32039L7.31379 5.63955C7.31379 5.14016 6.99803 4.78538 6.57699 4.78538C6.07699 4.78538 5.81382 5.1533 5.81382 5.63955L5.80069 7.4662L4.05074 6.46747C3.61654 6.2309 3.14286 6.32288 2.90603 6.74341C2.64288 7.20336 2.78761 7.65019 3.22181 7.89987L4.97174 8.89863L3.39286 9.83167C2.97182 10.0813 2.78761 10.4624 3.02444 10.9224C3.23496 11.2904 3.70863 11.3824 4.15599 11.1326L6.47171 9.77907L7.44537 10.331L10.84 12L7.44537 13.6689L6.47171 14.234L4.15599 12.8673C3.7218 12.6176 3.24812 12.7096 3.05076 13.0776C2.80077 13.5375 2.97182 13.9186 3.39286 14.1683L4.97174 15.1014L3.22181 16.1001C2.78761 16.3498 2.65603 16.8229 2.90603 17.2565C3.15602 17.6902 3.61654 17.769 4.05074 17.5325L5.80069 16.5338L5.81382 18.3472C5.81382 18.8466 6.07699 19.2147 6.57699 19.2147C6.99803 19.2147 7.31379 18.8598 7.31379 18.3472L7.30064 15.6664L8.2743 15.1145L11.4189 13.0119L11.1821 16.7703V17.9005L8.82693 19.2147C8.3927 19.4774 8.24798 19.9242 8.4585 20.2922C8.72163 20.7259 9.14271 20.7653 9.5769 20.5156L11.1821 19.6352V21.6327C11.1821 22.1451 11.511 22.5 11.9979 22.5Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const SparklesIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M11.4158 5.58939C11.557 5.58939 11.634 5.5014 11.6596 5.38827L11.6768 5.30148C11.9801 3.76575 11.9861 3.7357 13.6746 3.41481C13.8029 3.38966 13.8928 3.31425 13.8928 3.17598C13.8928 3.03772 13.8029 2.96229 13.6746 2.93716C11.9861 2.61628 11.9801 2.5862 11.6768 1.05049L11.6596 0.963687C11.634 0.837989 11.557 0.75 11.4158 0.75C11.2875 0.75 11.2104 0.837989 11.1848 0.963687L11.1597 1.08576C10.8519 2.58669 10.8453 2.61875 9.16985 2.93716C9.0415 2.96229 8.95166 3.03772 8.95166 3.17598C8.95166 3.31425 9.0415 3.38966 9.16985 3.41481C10.8453 3.73322 10.8519 3.76527 11.1597 5.26621L11.1848 5.38827C11.2104 5.5014 11.2875 5.58939 11.4158 5.58939ZM6.60354 12.4147C6.80888 12.4147 6.95004 12.2764 6.97572 12.0753C7.32098 9.45776 7.4369 9.43579 10.1925 8.91345L10.2227 8.90772C10.4152 8.88259 10.5692 8.74432 10.5692 8.5432C10.5692 8.32952 10.4152 8.19124 10.2227 8.16611C7.43774 7.81414 7.30939 7.68845 6.97572 5.01107C6.95004 4.79738 6.80888 4.65912 6.60354 4.65912C6.38535 4.65912 6.24417 4.79738 6.21851 5.02364C5.91476 7.61435 5.72334 7.65023 3.08418 8.14498L2.97152 8.16611C2.77901 8.20381 2.625 8.32952 2.625 8.5432C2.625 8.75688 2.77901 8.88259 3.02285 8.90772C5.74366 9.32252 5.9105 9.42308 6.21851 12.0502C6.24417 12.2764 6.38535 12.4147 6.60354 12.4147ZM13.957 22.7724C13.9057 23.0615 13.6875 23.2501 13.418 23.2501C13.1357 23.2501 12.9175 23.0615 12.879 22.7724C12.0961 17.5307 11.2876 16.7011 5.9743 15.9972C5.66628 15.9721 5.46094 15.7458 5.46094 15.4693C5.46094 15.1802 5.66628 14.9665 5.9743 14.9288C11.3004 14.3128 12.1218 13.4078 12.879 8.16619C12.9175 7.86452 13.1357 7.67596 13.418 7.67596C13.6875 7.67596 13.9057 7.86452 13.957 8.16619C14.7014 13.4078 15.5228 14.3128 20.849 14.9288C21.1568 14.9665 21.3751 15.1802 21.3751 15.4693C21.3751 15.7458 21.1568 15.9721 20.849 15.9972C15.5228 16.6131 14.7014 17.5307 13.957 22.7724Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const SunIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M12.9557 4.43851C12.9557 4.95491 12.5241 5.39754 12.0062 5.39754C11.4759 5.39754 11.0443 4.95491 11.0443 4.43851V2.4467C11.0443 1.93033 11.4759 1.5 12.0062 1.5C12.5241 1.5 12.9557 1.93033 12.9557 2.4467V4.43851ZM18.0362 7.34015C17.6539 7.7213 17.0373 7.70901 16.6673 7.34015C16.3097 6.97131 16.3097 6.36884 16.6673 6.00001L18.0977 4.57376C18.4677 4.20492 19.0842 4.20492 19.4542 4.57376C19.8119 4.94262 19.8119 5.56968 19.4542 5.93851L18.0362 7.34015ZM19.5652 12.959C19.0473 12.959 18.6033 12.5164 18.6033 12C18.6033 11.4836 19.0473 11.041 19.5652 11.041H21.5381C22.0683 11.041 22.5 11.4836 22.5 12C22.5 12.5164 22.0683 12.959 21.5381 12.959H19.5652ZM16.6673 18.0123C16.3097 17.6434 16.3097 17.041 16.6673 16.6721C17.0373 16.3033 17.6539 16.3033 18.0237 16.6721L19.4542 18.0737C19.8119 18.4426 19.8119 19.0573 19.4542 19.4262C19.0842 19.7951 18.4677 19.8074 18.0977 19.4385L16.6673 18.0123ZM11.0443 19.5615C11.0443 19.045 11.4759 18.6025 12.0062 18.6025C12.5241 18.6025 12.9557 19.045 12.9557 19.5615V21.5533C12.9557 22.0697 12.5241 22.4999 12.0062 22.4999C11.4759 22.4999 11.0443 22.0697 11.0443 21.5533V19.5615ZM5.96388 16.6721C6.33384 16.3033 6.96272 16.3033 7.33264 16.6721C7.69026 17.041 7.69026 17.6434 7.32032 18.0123L5.90224 19.4262C5.53228 19.7951 4.91573 19.7828 4.54581 19.4139C4.18821 19.045 4.18821 18.4303 4.55813 18.0614L5.96388 16.6721ZM4.43481 11.041C4.96506 11.041 5.39665 11.4836 5.39665 12C5.39665 12.5164 4.96506 12.959 4.43481 12.959H2.46182C1.93159 12.959 1.5 12.5164 1.5 12C1.5 11.4836 1.93159 11.041 2.46182 11.041H4.43481ZM7.33264 6.00001C7.69026 6.35656 7.69026 6.9836 7.32032 7.34015C6.95039 7.70901 6.33384 7.70901 5.96388 7.34015L4.55813 5.92623C4.20053 5.56968 4.20053 4.94262 4.57046 4.57376C4.92808 4.20492 5.55695 4.21721 5.91455 4.57376L7.33264 6.00001ZM17.0255 12C17.0255 14.7541 14.7442 17.0164 12.0067 17.0164C9.25685 17.0164 6.97559 14.7541 6.97559 12C6.97559 9.24594 9.25685 6.98364 12.0067 6.98364C14.7442 6.98364 17.0255 9.24594 17.0255 12Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const HareIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M12.5439 19.5858C13.0784 19.5858 13.5172 19.3355 14.5286 19.3355C15.5306 19.3355 15.7882 19.5858 16.523 19.5858C17.6298 19.5858 18.2024 19.1238 18.2024 18.2958C18.2024 16.9288 16.8092 16.0528 14.5191 16.0528C13.3932 16.0528 12.897 16.1491 12.2672 16.3127L11.4466 14.4259C10.6642 12.6739 9.39507 11.4898 7.55347 11.4898H6.66607C6.37025 11.4898 6.16033 11.355 6.16033 11.047C6.16033 10.5657 6.7901 10.4405 7.48667 10.4405C9.70041 10.4405 11.3321 11.6727 12.334 14.1371L12.8397 15.3886C13.3455 15.2827 13.8989 15.2346 14.4428 15.2346C14.8912 15.2346 15.3207 15.2635 15.75 15.3212C16.1985 14.9843 16.7233 14.6473 17.5057 14.3874C18.3456 14.9169 19.2425 15.2538 20.2157 15.2538C22.3149 15.2538 23.25 14.8014 23.25 12.7702C23.25 10.4405 21.628 8.67883 19.2806 8.6307L17.2481 5.95451C16.1031 4.45275 14.7576 3.75 13.4409 3.75C12.3436 3.75 10.6737 4.39499 10.6737 5.2999C10.6737 5.90636 11.9237 6.79201 12.8493 7.37923L17.1432 10.0747C16.8855 10.3057 16.6089 10.4694 16.2367 10.4694C15.6356 10.4694 14.9868 10.0747 14.0803 9.54524C11.7615 8.19751 9.97713 6.94605 7.57254 6.94605C5.07255 6.94605 3.17368 8.39966 2.16222 11.2492C1.36069 11.2492 0.75 11.7882 0.75 12.568C0.75 13.444 1.44657 14.0024 2.38168 14.0024C3.48856 15.5522 5.30154 16.1587 7.40079 16.1587C7.5153 16.1587 7.6298 16.1491 7.7443 16.1491L10.7977 18.7675C11.5897 19.4895 11.9714 19.5858 12.5439 19.5858ZM7.19087 20.25C8.66033 20.25 9.52866 19.9516 10.2252 19.451L7.91605 17.4679C7.81108 17.4775 7.68705 17.4871 7.53437 17.4871C7.13361 17.4871 6.63744 17.3813 5.94086 17.3813C5.10115 17.3813 4.54772 17.8434 4.54772 18.5461C4.54772 19.5762 5.60689 20.25 7.19087 20.25ZM20.0727 12.4428C19.7196 12.4428 19.4524 12.1541 19.4524 11.8075C19.4524 11.4513 19.7291 11.1432 20.0821 11.1432C20.4447 11.1432 20.7119 11.4224 20.7119 11.769C20.7119 12.1252 20.4352 12.4428 20.0727 12.4428Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const FlashIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M5.25 13.1338C5.25 13.5472 5.58422 13.8543 6.01393 13.8543H11.2778L8.5086 21.2952C8.11471 22.3699 9.23674 22.9488 9.95289 22.0747L18.4874 11.5276C18.6545 11.3032 18.75 11.1024 18.75 10.8661C18.75 10.4528 18.4277 10.1457 17.9861 10.1457H12.7221L15.4914 2.70481C15.8972 1.63 14.7633 1.05129 14.0471 1.92528L5.51259 12.4724C5.34548 12.6968 5.25 12.8976 5.25 13.1338Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const BankCardIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M5.17928 17.2322C4.65824 17.2322 4.3071 16.855 4.3071 16.3196V14.5553C4.3071 14.0199 4.65824 13.6427 5.17928 13.6427H7.35403C7.87506 13.6427 8.22618 14.0199 8.22618 14.5553V16.3196C8.22618 16.855 7.87506 17.2322 7.35403 17.2322H5.17928ZM1.49805 9.91922V7.72894H22.498V9.91922H1.49805ZM4.38639 20.25H19.6097C21.5465 20.25 22.498 19.24 22.498 17.1957V6.80418C22.498 4.75994 21.5465 3.75 19.6097 3.75H4.38639C2.46082 3.75 1.49805 4.75994 1.49805 6.80418V17.1957C1.49805 19.24 2.46082 20.25 4.38639 20.25Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const GearIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M11.9941 21.033C12.2078 21.033 12.4214 21.0211 12.6351 21.0093L13.1693 22.0308C13.288 22.2683 13.5373 22.387 13.8104 22.3396C14.0715 22.2802 14.2733 22.0783 14.2971 21.805L14.4751 20.6885C14.8906 20.5816 15.2824 20.4154 15.686 20.261L16.517 20.9974C16.7069 21.1756 17.0037 21.2349 17.253 21.0925C17.4785 20.9498 17.5973 20.6766 17.5261 20.4154L17.3004 19.3226C17.6329 19.0612 17.9772 18.788 18.274 18.503L19.3068 18.9306C19.5915 19.0494 19.8527 18.9662 20.0427 18.7406C20.197 18.5387 20.2207 18.2536 20.0902 18.0279L19.4847 17.0658C19.7222 16.7095 19.924 16.3413 20.1258 15.9374L21.2536 15.9849C21.5148 15.9968 21.776 15.8543 21.8589 15.5811C21.9539 15.3316 21.8471 15.0585 21.6453 14.8922L20.7669 14.1914C20.8737 13.7875 20.9449 13.3599 20.9805 12.9204L22.037 12.576C22.3219 12.481 22.5 12.279 22.5 11.994C22.5 11.7089 22.3219 11.507 22.037 11.412L20.9805 11.0675C20.9449 10.628 20.8737 10.2123 20.7669 9.79658L21.6453 9.09579C21.8471 8.92949 21.9539 8.66818 21.8589 8.41875C21.776 8.14556 21.5148 8.00303 21.2536 8.0149L20.1258 8.05054C19.924 7.64668 19.7222 7.29036 19.4847 6.92214L20.0902 5.96004C20.2207 5.74623 20.197 5.46116 20.0427 5.25924C19.8527 5.03356 19.5915 4.9623 19.3186 5.0692L18.274 5.48493C17.9772 5.21173 17.6329 4.92666 17.3004 4.67723L17.5261 3.58447C17.5853 3.2994 17.4785 3.03809 17.253 2.90744C17.0037 2.7649 16.7069 2.80054 16.517 3.00246L15.686 3.72701C15.2824 3.5726 14.8906 3.41818 14.4751 3.2994L14.2971 2.19477C14.2733 1.92158 14.0715 1.71965 13.8104 1.66026C13.5373 1.61275 13.288 1.73153 13.1693 1.95721L12.6351 2.9787C12.4214 2.96683 12.2078 2.95495 11.9941 2.95495C11.7685 2.95495 11.5667 2.96683 11.3412 2.9787L10.807 1.95721C10.6883 1.73153 10.439 1.61275 10.1659 1.66026C9.90476 1.71965 9.72669 1.92158 9.6792 2.19477L9.50113 3.2994C9.09752 3.41818 8.69391 3.5726 8.30215 3.72701L7.47118 3.00246C7.26937 2.80054 6.98447 2.7649 6.72329 2.90744C6.49774 3.03809 6.40277 3.2994 6.46212 3.58447L6.67581 4.67723C6.35529 4.92666 5.99916 5.21173 5.70237 5.48493L4.65771 5.0692C4.39655 4.9623 4.1354 5.03356 3.94546 5.25924C3.79112 5.46116 3.75552 5.74623 3.89796 5.96004L4.49152 6.92214C4.26597 7.29036 4.06416 7.64668 3.85049 8.05054L2.72273 8.0149C2.47343 8.00303 2.21227 8.14556 2.12917 8.41875C2.04607 8.66818 2.12917 8.92949 2.33098 9.09579L3.20944 9.79658C3.10261 10.2123 3.03137 10.628 3.00763 11.0675L1.92736 11.412C1.65432 11.4951 1.5 11.7089 1.5 11.994C1.5 12.279 1.65432 12.4928 1.92736 12.576L3.00763 12.9204C3.03137 13.3599 3.10261 13.7875 3.20944 14.1914L2.33098 14.8922C2.12917 15.0585 2.04607 15.3316 2.12917 15.5811C2.21227 15.8543 2.47343 15.9968 2.72273 15.9849L3.85049 15.9374C4.06416 16.3413 4.26597 16.7095 4.49152 17.0658L3.89796 18.0279C3.75552 18.2536 3.79112 18.5387 3.94546 18.7406C4.1354 18.9662 4.39655 19.0494 4.66959 18.9306L5.70237 18.503C5.99916 18.788 6.35529 19.0612 6.67581 19.3226L6.46212 20.4154C6.41465 20.6766 6.49774 20.9498 6.72329 21.0925C6.98447 21.2349 7.26937 21.1756 7.47118 20.9974L8.30215 20.261C8.69391 20.4154 9.09752 20.5816 9.50113 20.6885L9.6792 21.805C9.72669 22.0783 9.90476 22.2802 10.1659 22.3396C10.439 22.387 10.6883 22.2683 10.807 22.0308L11.3412 21.0093C11.5667 21.0211 11.7685 21.033 11.9941 21.033ZM11.9941 19.3226C7.89854 19.3226 4.76456 16.0681 4.76456 12.0059C4.76456 7.93175 7.89854 4.66536 11.9941 4.66536C16.0896 4.66536 19.2355 7.93175 19.2355 12.0059C19.2355 16.0681 16.0896 19.3226 11.9941 19.3226ZM10.249 10.5449L11.4955 9.76095L8.42087 4.48719L7.11504 5.21173L10.249 10.5449ZM14.1546 12.7185H20.2802V11.2694H14.1546V12.7185ZM11.4836 14.2745L10.2609 13.455L7.0082 18.7643L8.29028 19.5126L11.4836 14.2745ZM11.9585 14.5715C13.383 14.5715 14.5226 13.4312 14.5226 12.0177C14.5226 10.6043 13.383 9.45211 11.9585 9.45211C10.5458 9.45211 9.41804 10.6043 9.41804 12.0177C9.41804 13.4312 10.5458 14.5715 11.9585 14.5715ZM11.9585 12.9917C11.4124 12.9917 10.9969 12.576 10.9969 12.0177C10.9969 11.4595 11.4124 11.0319 11.9585 11.0319C12.5164 11.0319 12.9438 11.4595 12.9438 12.0177C12.9438 12.576 12.5164 12.9917 11.9585 12.9917Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const HandIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M5.25 15.6033C5.25 19.8703 8.04175 22.5 11.97 22.5C15.0705 22.5 16.8864 21.2719 18.1958 18.9524C19.0111 17.5384 19.6164 15.7646 20.2587 13.6683C20.5552 12.7256 21 11.51 21 10.9642C21 10.4309 20.6047 10.0587 20.0241 10.0587C19.3447 10.0587 18.9741 10.4929 18.4676 11.51L17.0964 14.3133C16.9729 14.5862 16.837 14.7102 16.6888 14.7102C16.5282 14.7102 16.417 14.611 16.417 14.3009V3.90636C16.417 3.29857 15.9353 2.81482 15.33 2.81482C14.737 2.81482 14.2429 3.29857 14.2429 3.90636V11.3736C13.9959 11.2867 13.7241 11.2123 13.4276 11.1627V2.59155C13.4276 1.99616 12.9335 1.5 12.3406 1.5C11.7353 1.5 11.2535 1.99616 11.2535 2.59155V11.0635C10.9694 11.0883 10.6976 11.1255 10.4135 11.1751V3.36061C10.4135 2.7528 9.93174 2.26905 9.32646 2.26905C8.73352 2.26905 8.2394 2.7528 8.2394 3.36061V11.8573C7.95528 12.0062 7.68352 12.1674 7.41175 12.3287V6.41198C7.41175 5.81659 6.92999 5.32043 6.33705 5.32043C5.73176 5.32043 5.25 5.81659 5.25 6.41198V15.6033Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const GlassCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.5 12C22.5 17.7379 17.7278 22.5 11.9935 22.5C6.27214 22.5 1.5 17.7379 1.5 12C1.5 6.26208 6.25914 1.5 11.9805 1.5C17.7148 1.5 22.5 6.26208 22.5 12ZM6.44117 10.803C6.44117 13.236 8.44366 15.2397 10.8752 15.2397C11.7074 15.2397 12.4746 15.0056 13.1378 14.6152L15.7254 17.1784C15.9594 17.4256 16.2195 17.5427 16.5576 17.5427C17.1167 17.5427 17.5198 17.1134 17.5198 16.5148C17.5198 16.2546 17.3898 15.9944 17.2077 15.8122L14.6071 13.197C15.0492 12.5074 15.3093 11.6747 15.3093 10.803C15.3093 8.34386 13.3198 6.35315 10.8752 6.35315C8.44366 6.35315 6.44117 8.35687 6.44117 10.803ZM13.6709 10.803C13.6709 12.3383 12.4096 13.6134 10.8752 13.6134C9.32785 13.6134 8.06655 12.3383 8.06655 10.803C8.06655 9.26764 9.32785 7.99256 10.8752 7.99256C12.4096 7.99256 13.6709 9.25463 13.6709 10.803Z" + fill="currentColor" + /> + </svg> + ); +}; +export const FlashCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.4998 12C22.4998 17.7379 17.7277 22.5 11.9934 22.5C6.2721 22.5 1.5 17.7379 1.5 12C1.5 6.26207 6.25908 1.5 11.9804 1.5C17.7147 1.5 22.4998 6.26207 22.4998 12ZM13.2807 5.10408L7.58539 12.2472C7.46836 12.3773 7.40334 12.5465 7.40334 12.6896C7.40334 12.9758 7.63741 13.197 7.92346 13.197H11.4473L9.57485 18.2194C9.30179 18.948 10.082 19.3382 10.5631 18.7528L16.2584 11.5966C16.3754 11.4665 16.4404 11.2974 16.4404 11.1673C16.4404 10.868 16.2064 10.6598 15.9203 10.6598H12.3965L14.2689 5.63755C14.542 4.90893 13.7618 4.5186 13.2807 5.10408Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const DollarCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.4998 12C22.4998 17.7379 17.7277 22.5 11.9934 22.5C6.2721 22.5 1.5 17.7379 1.5 12C1.5 6.26208 6.25908 1.5 11.9804 1.5C17.7147 1.5 22.4998 6.26208 22.4998 12ZM11.4473 6.47024V7.19887C9.83492 7.39404 8.70364 8.39589 8.70364 9.87917C8.70364 11.2713 9.62686 12.0911 11.3953 12.5074L11.4473 12.5334V15.4869C10.5761 15.3438 10.108 14.8624 9.92595 14.2379C9.78291 13.8346 9.53583 13.6654 9.19776 13.6654C8.79467 13.6654 8.50861 13.9516 8.50861 14.355C8.50861 14.4721 8.53462 14.5892 8.54762 14.6933C8.87269 15.9163 10.069 16.671 11.4473 16.8141V17.5427C11.4473 17.868 11.6813 18.1022 12.0064 18.1022C12.3185 18.1022 12.5525 17.868 12.5525 17.5427V16.8271C14.1649 16.671 15.4652 15.7342 15.4652 14.0688C15.4652 12.5855 14.503 11.6877 12.6566 11.3754L12.5525 11.3624V8.56505C13.2157 8.69517 13.6708 9.0985 13.9049 9.77508C14.0219 10.1264 14.2559 10.3476 14.633 10.3476C15.0361 10.3476 15.3222 10.0613 15.3222 9.65796C15.3222 9.54088 15.2962 9.42377 15.2701 9.30668C14.9711 8.14869 13.9049 7.36802 12.5525 7.19887V6.47024C12.5525 6.14497 12.3185 5.9238 12.0064 5.9238C11.6813 5.9238 11.4473 6.14497 11.4473 6.47024ZM12.7736 12.8717C13.7098 13.1319 13.9959 13.5483 13.9959 14.1598C13.9959 14.8364 13.5667 15.3569 12.5525 15.4869V12.8067L12.7736 12.8717ZM11.4473 11.0632L11.3693 11.0502C10.5241 10.7639 10.173 10.3736 10.173 9.80109C10.173 9.2286 10.6281 8.72118 11.4473 8.56505V11.0632Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const EuroCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.4998 12C22.4998 17.7379 17.7277 22.5 11.9934 22.5C6.2721 22.5 1.5 17.7379 1.5 12C1.5 6.26208 6.25908 1.5 11.9804 1.5C17.7147 1.5 22.4998 6.26208 22.4998 12ZM8.15751 10.2695H7.13029C6.80522 10.2695 6.57115 10.5037 6.57115 10.842C6.57115 11.1543 6.80522 11.4275 7.13029 11.4275H7.98849C7.97548 11.6096 7.96248 11.8048 7.96248 12.013C7.96248 12.1951 7.96248 12.3643 7.97548 12.5334H7.13029C6.80522 12.5334 6.57115 12.7676 6.57115 13.1059C6.57115 13.4182 6.80522 13.6784 7.13029 13.6784H8.14453C8.62565 15.7732 10.043 17.2304 12.2795 17.2304C14.0869 17.2304 15.5562 16.4758 16.1413 14.9015C16.2064 14.7193 16.2454 14.5372 16.2454 14.342C16.2454 13.9126 15.9723 13.6264 15.5172 13.6264C15.1271 13.6264 14.8931 13.8215 14.763 14.2379C14.425 15.2788 13.3977 15.7862 12.3315 15.7862C11.0572 15.7862 10.199 14.9145 9.82189 13.6784H12.4355C12.7606 13.6784 12.9946 13.4182 12.9946 13.1059C12.9946 12.7676 12.7606 12.5334 12.4355 12.5334H9.58785C9.57485 12.3643 9.56185 12.1821 9.56185 12.013C9.56185 11.8048 9.57485 11.6096 9.58785 11.4275H12.4355C12.7606 11.4275 12.9946 11.1543 12.9946 10.842C12.9946 10.5037 12.7606 10.2695 12.4355 10.2695H9.84792C10.238 9.08549 11.0832 8.25278 12.3315 8.25278C13.3067 8.25278 14.3209 8.74721 14.672 9.80109C14.815 10.2695 15.0231 10.4126 15.4392 10.4126C15.8813 10.4126 16.1674 10.1264 16.1674 9.697C16.1674 9.50185 16.1284 9.33269 16.0633 9.15053C15.5172 7.6933 13.9959 6.80854 12.2795 6.80854C10.186 6.80854 8.69064 8.1617 8.15751 10.2695Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const SterlingCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.4998 12C22.4998 17.7379 17.7277 22.5 11.9934 22.5C6.2721 22.5 1.5 17.7379 1.5 12C1.5 6.26208 6.25908 1.5 11.9804 1.5C17.7147 1.5 22.4998 6.26208 22.4998 12ZM9.41883 9.9182C9.41883 10.3085 9.47084 10.6989 9.56185 11.0892H8.61262C8.23553 11.0892 7.96248 11.3364 7.96248 11.7137C7.96248 12.0911 8.23553 12.3513 8.61262 12.3513H9.8739C9.95193 12.6896 10.0039 13.0279 10.0039 13.3271C10.0039 14.4851 9.30179 15.1747 8.59963 15.4089C8.15751 15.539 7.97548 15.7732 7.97548 16.1635C7.97548 16.5929 8.26157 16.8661 8.67766 16.8661H15.1531C15.5692 16.8661 15.8683 16.5929 15.8683 16.1635C15.8683 15.7602 15.5692 15.4739 15.1531 15.4739H10.6411C11.1742 15.0056 11.5253 14.1728 11.5253 13.2621C11.5253 12.9238 11.4993 12.6245 11.4343 12.3513H13.9959C14.386 12.3513 14.659 12.0911 14.659 11.7137C14.659 11.3364 14.386 11.0892 13.9959 11.0892H11.1352C11.0572 10.7119 10.9662 10.3085 10.9662 9.81411C10.9662 8.68214 11.8114 7.9405 13.1377 7.9405C13.9699 7.9405 14.412 8.14869 14.724 8.14869C15.1661 8.14869 15.3742 7.90146 15.3742 7.52414C15.3742 7.19887 15.2051 6.95167 14.7891 6.78251C14.2429 6.58735 13.6448 6.54832 13.0336 6.54832C10.8361 6.54832 9.41883 7.83642 9.41883 9.9182Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const YuanCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.4998 12C22.4998 17.7379 17.7277 22.5 11.9934 22.5C6.2721 22.5 1.5 17.7379 1.5 12C1.5 6.26208 6.25908 1.5 11.9804 1.5C17.7147 1.5 22.4998 6.26208 22.4998 12ZM8.0405 7.38103C8.0405 7.56318 8.07952 7.6933 8.18354 7.87546L10.7321 12.013H9.31479C8.97672 12.013 8.74266 12.2732 8.74266 12.5985C8.74266 12.9238 8.9897 13.171 9.31479 13.171H11.2522V13.9647H9.31479C8.97672 13.9647 8.74266 14.2119 8.74266 14.5372C8.74266 14.8754 8.9897 15.1096 9.31479 15.1096H11.2522V16.5148C11.2522 16.9963 11.5643 17.3215 11.9934 17.3215C12.4355 17.3215 12.7476 17.0093 12.7476 16.5279V15.1096H14.685C15.0231 15.1096 15.2442 14.8754 15.2442 14.5372C15.2442 14.2119 15.0231 13.9647 14.685 13.9647H12.7476V13.171H14.685C15.0231 13.171 15.2442 12.9238 15.2442 12.5985C15.2442 12.2732 15.0231 12.013 14.685 12.013H13.2807L15.8293 7.88847C15.9333 7.7063 15.9723 7.57618 15.9723 7.39404C15.9723 7.00371 15.6472 6.69143 15.2181 6.69143C14.9321 6.69143 14.711 6.82155 14.542 7.08179L12.0064 11.4275L9.47084 7.06875C9.30179 6.79552 9.08072 6.67844 8.79467 6.67844C8.36557 6.67844 8.0405 6.9907 8.0405 7.38103Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const RubleCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.4998 12C22.4998 17.7379 17.7277 22.5 11.9934 22.5C6.2721 22.5 1.5 17.7379 1.5 12C1.5 6.26208 6.25908 1.5 11.9804 1.5C17.7147 1.5 22.4998 6.26208 22.4998 12ZM10.212 7.08179C9.73088 7.08179 9.43183 7.39404 9.43183 7.86245V12.4033H8.50861C8.15751 12.4033 7.93648 12.6505 7.93648 12.9758C7.93648 13.3141 8.15751 13.5613 8.50861 13.5613H9.43183V14.5632H8.52161C8.17054 14.5632 7.94948 14.8104 7.94948 15.1357C7.94948 15.4739 8.17054 15.7082 8.52161 15.7082H9.43183V16.6319C9.43183 17.0873 9.7569 17.4126 10.225 17.4126C10.6671 17.4126 10.9792 17.0873 10.9792 16.6319V15.7082H13.4367C13.7748 15.7082 14.0089 15.4739 14.0089 15.1357C14.0089 14.8104 13.7748 14.5632 13.4367 14.5632H10.9792V13.5613H12.7476C14.75 13.5613 16.0373 12.1951 16.0373 10.3216C16.0373 8.46094 14.763 7.08179 12.7606 7.08179H10.212ZM14.464 10.3346C14.464 11.4665 13.8008 12.1951 12.4615 12.1951L10.9922 12.1691V8.49998H12.4615C13.8138 8.49998 14.464 9.20257 14.464 10.3346Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const RupeeCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.5 12C22.5 17.7379 17.7278 22.5 11.9935 22.5C6.27214 22.5 1.5 17.7379 1.5 12C1.5 6.26208 6.25914 1.5 11.9805 1.5C17.7148 1.5 22.5 6.26208 22.5 12ZM12.8257 7.09479C12.4876 7.09479 12.1365 7.09479 11.7984 7.09479H8.93776C8.56068 7.09479 8.2486 7.40706 8.2486 7.79739C8.2486 8.1747 8.56068 8.513 8.93776 8.513H10.7192C11.6684 8.513 12.3056 8.90333 12.5266 9.61894H8.92476C8.61269 9.61894 8.35263 9.85314 8.35263 10.1914C8.35263 10.5167 8.61269 10.7769 8.92476 10.7769H12.5656C12.3576 11.5446 11.7204 11.9349 10.7192 11.9349H9.21082C8.7037 11.9349 8.33963 12.2472 8.33963 12.7546C8.33963 13.158 8.54768 13.3531 8.76873 13.5743L12.5136 17.0873C12.7737 17.3475 12.9687 17.4126 13.2288 17.4126C13.6189 17.4126 13.9439 17.1654 13.9439 16.723C13.9439 16.4888 13.7879 16.2806 13.6319 16.1115L10.6672 13.3401H10.8492C12.9167 13.3401 13.9439 12.0911 14.165 10.7769H15.1922C15.5303 10.7769 15.7514 10.5167 15.7514 10.1914C15.7514 9.85314 15.5303 9.61894 15.1922 9.61894H14.191C14.1 9.08549 13.9179 8.6301 13.6059 8.25278H15.1272C15.4653 8.25278 15.6994 7.97953 15.6994 7.66727C15.6994 7.32898 15.4653 7.09479 15.1272 7.09479H12.8257Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const HashCircleIcon = () => { + const theme = useTheme(); + return ( + <svg + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + color={theme.iconPrimary} + > + <path + d="M22.5 12C22.5 17.7379 17.7278 22.5 11.9935 22.5C6.27214 22.5 1.5 17.7379 1.5 12C1.5 6.26208 6.25914 1.5 11.9805 1.5C17.7148 1.5 22.5 6.26208 22.5 12ZM13.9049 7.21189L13.4628 9.31968H11.5904L11.9675 7.44608C12.0715 6.96467 11.7334 6.53531 11.2393 6.53531C10.7712 6.53531 10.4721 6.78253 10.3811 7.21189L9.939 9.31968H8.89875C8.43064 9.31968 8.09257 9.67099 8.09257 10.1264C8.09257 10.5297 8.37863 10.829 8.76873 10.829H9.61392L9.19782 12.8717H8.14458C7.67647 12.8717 7.33838 13.223 7.33838 13.6784C7.33838 14.0948 7.63745 14.381 8.02756 14.381H8.88575L8.45665 16.3717C8.37863 16.8661 8.72971 17.2825 9.21082 17.2825C9.67894 17.2825 9.96501 17.0353 10.056 16.5929L10.5241 14.394H12.3966L11.9935 16.3717C11.8895 16.8661 12.2275 17.2825 12.7087 17.2825C13.1898 17.2825 13.5019 17.0353 13.5929 16.5929L14.048 14.381H15.1012C15.5563 14.381 15.8944 14.0297 15.8944 13.5743C15.8944 13.184 15.6083 12.8717 15.2183 12.8717H14.3731L14.8022 10.829H15.8424C16.3105 10.829 16.6486 10.4777 16.6486 10.0223C16.6486 9.61894 16.3625 9.31968 15.9724 9.31968H15.1142L15.5173 7.44608C15.5953 6.95167 15.2443 6.53531 14.7631 6.53531C14.295 6.53531 13.996 6.78253 13.9049 7.21189ZM12.7867 13.0279H10.7322L11.2133 10.6989H13.2808L12.7867 13.0279Z" + fill="currentColor" + /> + </svg> + ); +}; + +export const emojiIcons = [ + { + name: 'custom:wallet', + icon: WalletIcon + }, + { + name: 'custom:leaf', + icon: LeafIcon + }, + { + name: 'custom:lock', + icon: LockIcon + }, + { + name: 'custom:key', + icon: KeyIcon + }, + { + name: 'custom:inbox', + icon: InboxIcon + }, + { + name: 'custom:snowflake', + icon: SnowflakeIcon + }, + { + name: 'custom:sparkles', + icon: SparklesIcon + }, + { + name: 'custom:sun', + icon: SunIcon + }, + { + name: 'custom:hare', + icon: HareIcon + }, + { + name: 'custom:flash', + icon: FlashIcon + }, + { + name: 'custom:bankcard', + icon: BankCardIcon + }, + { + name: 'custom:gear', + icon: GearIcon + }, + { + name: 'custom:hand', + icon: HandIcon + }, + { + name: 'custom:glass_circle', + icon: GlassCircleIcon + }, + { + name: 'custom:flash_circle', + icon: FlashCircleIcon + }, + { + name: 'custom:dollar_circle', + icon: DollarCircleIcon + }, + { + name: 'custom:euro_circle', + icon: EuroCircleIcon + }, + { + name: 'custom:sterling_circle', + icon: SterlingCircleIcon + }, + { + name: 'custom:yuan_circle', + icon: YuanCircleIcon + }, + { + name: 'custom:ruble_circle', + icon: RubleCircleIcon + }, + { + name: 'custom:rupee_circle', + icon: RupeeCircleIcon + }, + { + name: 'custom:hash_circle', + icon: HashCircleIcon + } +]; diff --git a/packages/uikit/src/components/shared/wallet/WalletEmoji.tsx b/packages/uikit/src/components/shared/wallet/WalletEmoji.tsx deleted file mode 100644 index bfb6c764d..000000000 --- a/packages/uikit/src/components/shared/wallet/WalletEmoji.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { FC } from 'react'; -import { emojiIcons } from '../../settings/wallet-name/emojiIcons'; -import styled from 'styled-components'; - -const EmojiWrapper = styled.div` - height: 32px; - width: 32px; - line-height: 24px; - font-size: 24px; - display: flex; - align-items: center; - justify-content: center; -`; - -export const WalletEmoji: FC<{ emoji: string; className?: string }> = ({ emoji, className }) => { - if (emoji.startsWith('custom:')) { - const Emoji = emojiIcons.find(icon => icon.name === emoji); - - if (!Emoji) { - return null; - } - - return ( - <EmojiWrapper className={className}> - <Emoji.icon /> - </EmojiWrapper> - ); - } - - return <EmojiWrapper className={className}>{emoji}</EmojiWrapper>; -}; diff --git a/packages/uikit/src/pages/import/Create.tsx b/packages/uikit/src/pages/import/Create.tsx index 00a8d68fd..9c8a416fb 100644 --- a/packages/uikit/src/pages/import/Create.tsx +++ b/packages/uikit/src/pages/import/Create.tsx @@ -125,7 +125,7 @@ const Create: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => { } if (account && account.publicKeys.length > 1 && wallet && wallet.name == null) { - return <UpdateWalletName account={account} onUpdate={setAccount} />; + return <UpdateWalletName account={account} onUpdate={setAccount} walletEmoji={wallet.emoji} />; } if (sdk.notifications && !passNotifications) { diff --git a/packages/uikit/src/pages/import/Import.tsx b/packages/uikit/src/pages/import/Import.tsx index e1c3f3ec0..59a787f1d 100644 --- a/packages/uikit/src/pages/import/Import.tsx +++ b/packages/uikit/src/pages/import/Import.tsx @@ -62,7 +62,9 @@ const Import: FC<{ listOfAuth: AuthState['kind'][] }> = ({ listOfAuth }) => { } if (account && account.publicKeys.length > 1 && wallet && wallet.name == null) { - return <UpdateWalletName account={account} onUpdate={setAccount} />; + return ( + <UpdateWalletName account={account} onUpdate={setAccount} walletEmoji={wallet.emoji} /> + ); } if (sdk.notifications && !passNotifications) { diff --git a/packages/uikit/src/pages/settings/Account.tsx b/packages/uikit/src/pages/settings/Account.tsx index c3480b6f0..303b82e9a 100644 --- a/packages/uikit/src/pages/settings/Account.tsx +++ b/packages/uikit/src/pages/settings/Account.tsx @@ -30,6 +30,7 @@ import { useTranslation } from '../../hooks/translation'; import { AppRoute, SettingsRoute } from '../../libs/routes'; import { useMutateAccountState } from '../../state/account'; import { useWalletState } from '../../state/wallet'; +import { WalletEmoji } from '../../components/shared/emoji/WalletEmoji'; const Row = styled.div` display: flex; @@ -69,6 +70,7 @@ const WalletRow: FC<{ <Icon {...dragHandleProps}> <ReorderIcon /> </Icon> + <WalletEmoji emoji={wallet.emoji} /> <ColumnText noWrap text={wallet.name ? wallet.name : t('wallet_title')} From a2bdd2ef101998d3241cbbd72ace668037466970 Mon Sep 17 00:00:00 2001 From: siandreev <andreev.sergey.i@yandex.ru> Date: Tue, 12 Mar 2024 12:44:11 +0100 Subject: [PATCH 26/27] fix: aside menu bottom maid sticky. tg -> ton_proof Auth fixes --- apps/desktop/src/app/App.tsx | 2 +- packages/core/src/entries/pro.ts | 2 +- packages/core/src/service/proService.ts | 4 ++-- .../core/src/tonConsoleApi/services/ProServiceService.ts | 4 ++-- packages/uikit/src/components/aside/AsideMenu.tsx | 8 +++++++- packages/uikit/src/components/aside/SubscriptionInfo.tsx | 2 +- .../uikit/src/components/dashboard/DashboardTable.tsx | 2 +- packages/uikit/src/components/settings/ProSettings.tsx | 2 +- packages/uikit/src/state/dashboard/useDashboardData.ts | 2 +- 9 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index c6e9c86da..e61b67cde 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -180,7 +180,7 @@ const WideLayout = styled.div` display: flex; & > *:first-child { - width: 200px; + width: 250px; } `; diff --git a/packages/core/src/entries/pro.ts b/packages/core/src/entries/pro.ts index 676f72f25..fe7f77c33 100644 --- a/packages/core/src/entries/pro.ts +++ b/packages/core/src/entries/pro.ts @@ -1,6 +1,6 @@ export interface ProState { wallet: ProStateWallet; - hasCookie: boolean; + hasWalletAuthCookie: boolean; subscription: ProSubscription; } diff --git a/packages/core/src/service/proService.ts b/packages/core/src/service/proService.ts index 241f564b3..059063f21 100644 --- a/packages/core/src/service/proService.ts +++ b/packages/core/src/service/proService.ts @@ -47,7 +47,7 @@ export const getProState = async (storage: IStorage, wallet: WalletState): Promi } catch (e) { return { subscription: toEmptySubscription(), - hasCookie: false, + hasWalletAuthCookie: false, wallet: { publicKey: wallet.publicKey, rawAddress: wallet.active.rawAddress @@ -115,7 +115,7 @@ export const loadProState = async ( return { subscription, - hasCookie: true, + hasWalletAuthCookie: !!user.pub_key, wallet }; }; diff --git a/packages/core/src/tonConsoleApi/services/ProServiceService.ts b/packages/core/src/tonConsoleApi/services/ProServiceService.ts index 8f22aeeb1..cdfa8d82c 100644 --- a/packages/core/src/tonConsoleApi/services/ProServiceService.ts +++ b/packages/core/src/tonConsoleApi/services/ProServiceService.ts @@ -84,8 +84,8 @@ export class ProServiceService { * @throws ApiError */ public static proServiceGetUserInfo(): CancelablePromise<{ - pub_key: string; - version: string; + pub_key?: string; + version?: string; user_id?: number; tg_id?: number; }> { diff --git a/packages/uikit/src/components/aside/AsideMenu.tsx b/packages/uikit/src/components/aside/AsideMenu.tsx index a2e9afbcc..38db14816 100644 --- a/packages/uikit/src/components/aside/AsideMenu.tsx +++ b/packages/uikit/src/components/aside/AsideMenu.tsx @@ -21,7 +21,7 @@ const AsideContainer = styled.div` background: ${p => p.theme.backgroundContent}; display: flex; flex-direction: column; - padding: 0.5rem; + padding: 0.5rem 0.5rem 0; `; const IconWrapper = styled.div` @@ -40,6 +40,7 @@ const AsideMenuCard = styled.button<{ isSelected: boolean }>` padding: 6px 10px; width: 100%; height: 36px; + min-height: 36px; display: flex; align-items: center; gap: 10px; @@ -65,6 +66,11 @@ const AsideMenuBottom = styled.div` display: flex; flex-direction: column; justify-content: flex-end; + position: sticky; + bottom: 0; + + background: ${p => p.theme.backgroundContent}; + padding: 0.5rem 0; `; export const AsideMenuAccount: FC<{ publicKey: string; isSelected: boolean }> = ({ diff --git a/packages/uikit/src/components/aside/SubscriptionInfo.tsx b/packages/uikit/src/components/aside/SubscriptionInfo.tsx index 76f9e580f..16a50f99d 100644 --- a/packages/uikit/src/components/aside/SubscriptionInfo.tsx +++ b/packages/uikit/src/components/aside/SubscriptionInfo.tsx @@ -71,7 +71,7 @@ export const SubscriptionStatus: FC<{ data: ProState }> = ({ data }) => { export const SubscriptionInfo: FC<{ className?: string }> = ({ className }) => { const { data } = useProState(); - if (!data) { + if (!data || !data.subscription.valid) { return null; } diff --git a/packages/uikit/src/components/dashboard/DashboardTable.tsx b/packages/uikit/src/components/dashboard/DashboardTable.tsx index b371250ff..b8a61035c 100644 --- a/packages/uikit/src/components/dashboard/DashboardTable.tsx +++ b/packages/uikit/src/components/dashboard/DashboardTable.tsx @@ -103,7 +103,7 @@ export const DashboardTable: FC<{ className?: string }> = ({ className }) => { const { data: dashboardData } = useDashboardData(); const { data: wallets, isFetched: isWalletsFetched } = useWalletsState(); const mainnetPubkeys = wallets - ?.filter(w => w?.network === Network.MAINNET) + ?.filter(w => w && w.network !== Network.TESTNET) .map(w => w!.publicKey); const [isResizing, setIsResizing] = useState<boolean>(false); diff --git a/packages/uikit/src/components/settings/ProSettings.tsx b/packages/uikit/src/components/settings/ProSettings.tsx index 65aae9ed2..ee9e2e0fe 100644 --- a/packages/uikit/src/components/settings/ProSettings.tsx +++ b/packages/uikit/src/components/settings/ProSettings.tsx @@ -320,7 +320,7 @@ const PreServiceStatus: FC<{ data: ProState; setReLogin: () => void }> = ({ const ProContent: FC<{ data: ProState }> = ({ data }) => { const [reLogin, setReLogin] = useState(false); - if (!data.hasCookie || reLogin) { + if (!data.hasWalletAuthCookie || reLogin) { return <SelectWallet />; } if (isPaidSubscription(data.subscription)) { diff --git a/packages/uikit/src/state/dashboard/useDashboardData.ts b/packages/uikit/src/state/dashboard/useDashboardData.ts index dc97a5685..3097216ed 100644 --- a/packages/uikit/src/state/dashboard/useDashboardData.ts +++ b/packages/uikit/src/state/dashboard/useDashboardData.ts @@ -22,7 +22,7 @@ export function useDashboardData() { const client = useQueryClient(); const { data: walletsState } = useWalletsState(); - const mainnetWallets = walletsState?.filter(w => w?.network === Network.MAINNET); + const mainnetWallets = walletsState?.filter(w => w && w.network !== Network.TESTNET); const publicKeysMainnet = mainnetWallets?.map(w => w!.publicKey); return useQuery<DashboardCell[][]>( From fcbd946dada786ecd1296fea0cddf31a6d8fbdb7 Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov <nkuz915@gmail.com> Date: Tue, 12 Mar 2024 13:27:28 +0100 Subject: [PATCH 27/27] Remove zoom factor --- apps/desktop/src/electron/mainWindow.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/desktop/src/electron/mainWindow.ts b/apps/desktop/src/electron/mainWindow.ts index 491e0f08a..9acdd43b2 100644 --- a/apps/desktop/src/electron/mainWindow.ts +++ b/apps/desktop/src/electron/mainWindow.ts @@ -44,7 +44,6 @@ export abstract class MainWindow { minHeight: 700, resizable: isDev, webPreferences: { - zoomFactor: process.platform !== 'linux' ? 0.8 : undefined, preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY } });