Skip to content

Commit

Permalink
feat(ui): add experimental modal for single wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
thekiba committed Jan 8, 2024
1 parent 29cfd4f commit 6c1bf57
Show file tree
Hide file tree
Showing 10 changed files with 449 additions and 6 deletions.
2 changes: 2 additions & 0 deletions packages/ui/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { TonConnectUiContext } from 'src/app/state/ton-connect-ui.context';
import { createI18nContext, I18nContext } from '@solid-primitives/i18n';
import { appState } from 'src/app/state/app.state';
import { defineStylesRoot, fixMobileSafariActiveTransition } from 'src/app/utils/web-api';
import { SingleWalletModal } from 'src/app/views/modals/wallets-modal/single-wallet-modal';

export type AppProps = {
tonConnectUI: TonConnectUI;
Expand All @@ -39,6 +40,7 @@ const App: Component<AppProps> = props => {
</Show>
<Dynamic component={globalStylesTag}>
<WalletsModal />
<SingleWalletModal />
<ActionsModal />
</Dynamic>
</ThemeProvider>
Expand Down
20 changes: 20 additions & 0 deletions packages/ui/src/app/state/modals-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { WalletInfoWithOpenMethod, WalletOpenMethod } from 'src/models/connected
import { LastSelectedWalletInfoStorage } from 'src/storage';
import { ReturnStrategy } from 'src/models';
import { WalletsModalState } from 'src/models/wallets-modal';
import { SingleWalletModalState } from 'src/models/single-wallet-modal';

export type ActionName = 'confirm-transaction' | 'transaction-sent' | 'transaction-canceled';

Expand All @@ -27,6 +28,25 @@ export const [walletsModalState, setWalletsModalState] = createSignal<WalletsMod

export const getWalletsModalIsOpened = createMemo(() => walletsModalState().status === 'opened');

export const [singleWalletModalState, setSingleWalletModalState] =
createSignal<SingleWalletModalState>({
status: 'closed',
closeReason: null
});

export const getSingleWalletModalIsOpened = createMemo(
() => singleWalletModalState().status === 'opened'
);

export const getSingleWalletModalWalletInfo = createMemo(() => {
const state = singleWalletModalState();
if (state.status === 'opened') {
return state.walletInfo;
}

return null;
});

let lastSelectedWalletInfoStorage =
typeof window !== 'undefined' ? new LastSelectedWalletInfoStorage() : undefined;
export const [lastSelectedWalletInfo, _setLastSelectedWalletInfo] = createSignal<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface DesktopConnectionProps {
additionalRequest?: ConnectAdditionalRequest;
wallet: WalletInfoRemote | (WalletInfoRemote & WalletInfoInjectable);
onBackClick: () => void;
backDisabled?: boolean;
}

let openDesktopDeeplinkAttempts = 0;
Expand Down Expand Up @@ -188,7 +189,9 @@ export const DesktopConnectionModal: Component<DesktopConnectionProps> = props =

return (
<DesktopConnectionModalStyled data-tc-wallets-modal-connection-desktop="true">
<StyledIconButton icon="arrow" onClick={() => props.onBackClick()} />
<Show when={!props.backDisabled}>
<StyledIconButton icon="arrow" onClick={() => props.onBackClick()} />
</Show>
<H1Styled>{props.wallet.name}</H1Styled>
<Show when={mode() === 'mobile'}>
<H2Styled
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConnectAdditionalRequest, WalletInfoRemote } from '@tonconnect/sdk';
import { ConnectAdditionalRequest, isTelegramUrl, WalletInfoRemote } from '@tonconnect/sdk';
import { Component, createMemo, createSignal, onCleanup, Show, useContext } from 'solid-js';
import {
BodyStyled,
Expand All @@ -21,12 +21,13 @@ import { setLastSelectedWalletInfo } from 'src/app/state/modals-state';
import { useTheme } from 'solid-styled-components';
import { MobileConnectionQR } from 'src/app/views/modals/wallets-modal/mobile-connection-modal/mobile-connection-qr';
import { Translation } from 'src/app/components/typography/Translation';
import { addReturnStrategy } from 'src/app/utils/url-strategy-helpers';
import { addReturnStrategy, redirectToTelegram } from 'src/app/utils/url-strategy-helpers';

export interface MobileConnectionProps {
additionalRequest?: ConnectAdditionalRequest;
wallet: WalletInfoRemote;
onBackClick: () => void;
backDisabled?: boolean;
}

export const MobileConnectionModal: Component<MobileConnectionProps> = props => {
Expand All @@ -52,7 +53,25 @@ export const MobileConnectionModal: Component<MobileConnectionProps> = props =>
)
);

const onClickTelegram = (): void => {
const alwaysForceRedirect = true;
setLastSelectedWalletInfo({
...props.wallet,
openMethod: 'universal-link'
});
redirectToTelegram(universalLink()!, {
returnStrategy: appState.returnStrategy,
twaReturnUrl: appState.twaReturnUrl,
forceRedirect: alwaysForceRedirect
});
};

const onRetry = (): void => {
const currentUniversalLink = universalLink();
if (isTelegramUrl(currentUniversalLink)) {
return onClickTelegram();
}

setConnectionErrored(false);
setLastSelectedWalletInfo({
...props.wallet,
Expand Down Expand Up @@ -91,7 +110,9 @@ export const MobileConnectionModal: Component<MobileConnectionProps> = props =>

return (
<MobileConnectionModalStyled data-tc-wallets-modal-connection-mobile="true">
<StyledIconButton icon="arrow" onClick={onBack} />
<Show when={!props.backDisabled || showQR()}>
<StyledIconButton icon="arrow" onClick={onBack} />
</Show>
<Show when={showQR()}>
<MobileConnectionQR universalLink={universalLink()} walletInfo={props.wallet} />
</Show>
Expand Down
101 changes: 101 additions & 0 deletions packages/ui/src/app/views/modals/wallets-modal/single-wallet-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ConnectAdditionalRequest } from '@tonconnect/sdk';
import {
Component,
createEffect,
createMemo,
createSignal,
onCleanup,
Show,
useContext
} from 'solid-js';
import { ConnectorContext } from 'src/app/state/connector.context';
import {
getSingleWalletModalIsOpened,
getSingleWalletModalWalletInfo,
setSingleWalletModalState
} from 'src/app/state/modals-state';
import { H1Styled, LoaderContainerStyled, StyledModal } from './style';
import { useI18n } from '@solid-primitives/i18n';
import { appState } from 'src/app/state/app.state';
import { isMobile, updateIsMobile } from 'src/app/hooks/isMobile';
import { LoaderIcon } from 'src/app/components';
import { LoadableReady } from 'src/models/loadable';
import { DesktopConnectionModal } from 'src/app/views/modals/wallets-modal/desktop-connection-modal';
import { InfoModal } from 'src/app/views/modals/wallets-modal/info-modal';
import { MobileConnectionModal } from 'src/app/views/modals/wallets-modal/mobile-connection-modal';
import { Dynamic } from 'solid-js/web';
import { WalletsModalCloseReason } from 'src/models';

export const SingleWalletModal: Component = () => {
const { locale } = useI18n()[1];
createEffect(() => locale(appState.language));

createEffect(() => {
if (getSingleWalletModalIsOpened()) {
updateIsMobile();
}
});

const connector = useContext(ConnectorContext)!;
const [infoTab, setInfoTab] = createSignal(false);

const additionalRequestLoading = (): boolean =>
appState.connectRequestParameters?.state === 'loading';

const additionalRequest = createMemo(() => {
if (additionalRequestLoading()) {
return undefined;
}

return (appState.connectRequestParameters as LoadableReady<ConnectAdditionalRequest>)
?.value;
});

const onClose = (closeReason: WalletsModalCloseReason): void => {
setSingleWalletModalState({ status: 'closed', closeReason: closeReason });
setInfoTab(false);
};

const unsubscribe = connector.onStatusChange(wallet => {
if (wallet) {
onClose('wallet-selected');
}
});

onCleanup(unsubscribe);

return (
<StyledModal
opened={getSingleWalletModalIsOpened()}
enableAndroidBackHandler={appState.enableAndroidBackHandler}
onClose={() => onClose('action-cancelled')}
onClickQuestion={() => setInfoTab(v => !v)}
data-tc-wallets-modal-container="true"
>
<Show when={infoTab()}>
<InfoModal onBackClick={() => setInfoTab(false)} />
</Show>

<Show when={!infoTab()}>
<Show when={additionalRequestLoading()}>
<H1Styled translationKey="walletModal.loading">
Wallets list is loading
</H1Styled>
<LoaderContainerStyled>
<LoaderIcon size="m" />
</LoaderContainerStyled>
</Show>

<Show when={!additionalRequestLoading()}>
<Dynamic
component={isMobile() ? MobileConnectionModal : DesktopConnectionModal}
wallet={getSingleWalletModalWalletInfo()!} // TODO: remove non-null assertion
additionalRequest={additionalRequest()}
onBackClick={() => {}}
backDisabled={true}
/>
</Show>
</Show>
</StyledModal>
);
};
18 changes: 18 additions & 0 deletions packages/ui/src/app/widget-controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import {
lastSelectedWalletInfo,
setAction,
setLastSelectedWalletInfo,
setSingleWalletModalState,
setWalletsModalState
} from 'src/app/state/modals-state';
import { TonConnectUI } from 'src/ton-connect-ui';
import App from './App';
import { WalletInfoWithOpenMethod, WalletOpenMethod } from 'src/models/connected-wallet';
import { WalletsModalCloseReason } from 'src/models';
import { WalletInfoRemote } from '@tonconnect/sdk';

export const widgetController = {
openWalletsModal: (): void =>
Expand All @@ -27,6 +29,22 @@ export const widgetController = {
closeReason: reason
})
),
openSingleWalletModal: (walletInfo: WalletInfoRemote): void => {
void setTimeout(() =>
setSingleWalletModalState({
status: 'opened',
closeReason: null,
walletInfo: walletInfo
})
);
},
closeSingleWalletModal: (reason: WalletsModalCloseReason): void =>
void setTimeout(() =>
setSingleWalletModalState({
status: 'closed',
closeReason: reason
})
),
setAction: (action: Action): void => void setTimeout(() => setAction(action)),
clearAction: (): void => void setTimeout(() => setAction(null)),
getSelectedWalletInfo: ():
Expand Down
Loading

0 comments on commit 6c1bf57

Please sign in to comment.