Skip to content

Commit

Permalink
feat(ui): add UI for orange ticket edge cases
Browse files Browse the repository at this point in the history
  • Loading branch information
pwltr committed Sep 27, 2024
1 parent d8029a9 commit 3392156
Show file tree
Hide file tree
Showing 11 changed files with 575 additions and 252 deletions.
10 changes: 5 additions & 5 deletions src/navigation/bottom-sheet/BottomSheets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import ConnectionClosed from './ConnectionClosed';
import ForceTransfer from './ForceTransfer';
import LNURLWithdrawNavigation from './LNURLWithdrawNavigation';
import NewTxPrompt from '../../screens/Wallets/NewTxPrompt';
import OrangeTicket from '../../screens/OrangeTicket';
import OrangeTicketNavigation from './OrangeTicketNavigation';
import PINNavigation from './PINNavigation';
import ReceiveNavigation from './ReceiveNavigation';
import SendNavigation from './SendNavigation';
import TreasureHuntNavigation from './TreasureHuntNavigation';
import PubkyAuth from './PubkyAuth.tsx';
// import TreasureHuntNavigation from './TreasureHuntNavigation';
import PubkyAuth from './PubkyAuth';

const BottomSheets = (): JSX.Element => {
const views = useAppSelector(viewControllersSelector);
Expand All @@ -27,11 +27,11 @@ const BottomSheets = (): JSX.Element => {
{views.forceTransfer.isMounted && <ForceTransfer />}
{views.lnurlWithdraw.isMounted && <LNURLWithdrawNavigation />}
{views.newTxPrompt.isMounted && <NewTxPrompt />}
{views.orangeTicket.isMounted && <OrangeTicket />}
{views.orangeTicket.isMounted && <OrangeTicketNavigation />}
{views.PINNavigation.isMounted && <PINNavigation />}
{views.receiveNavigation.isMounted && <ReceiveNavigation />}
{views.sendNavigation.isMounted && <SendNavigation />}
{views.treasureHunt.isMounted && <TreasureHuntNavigation />}
{/* {views.treasureHunt.isMounted && <TreasureHuntNavigation />} */}
{views.pubkyAuth.isMounted && <PubkyAuth />}
</>
);
Expand Down
185 changes: 185 additions & 0 deletions src/navigation/bottom-sheet/OrangeTicketNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React, {
memo,
ReactElement,
useCallback,
useEffect,
useState,
} from 'react';
import { ldk } from '@synonymdev/react-native-ldk';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
NativeStackNavigationOptions,
} from '@react-navigation/native-stack';

import { NavigationContainer } from '../../styles/components';
import Prize from '../../screens/OrangeTicket/Prize';
import UsedCard from '../../screens/OrangeTicket/UsedCard';
import Error from '../../screens/OrangeTicket/Error';

import BottomSheetWrapper from '../../components/BottomSheetWrapper';
import { useAppSelector } from '../../hooks/redux';
import {
useBottomSheetBackPress,
useSnapPoints,
} from '../../hooks/bottomSheet';
import { showToast } from '../../utils/notifications';
import { getNodeId, waitForLdk } from '../../utils/lightning';
import { viewControllerSelector } from '../../store/reselect/ui';
import { __TREASURE_HUNT_HOST__ } from '../../constants/env';

export type OrangeTicketNavigationProp =
NativeStackNavigationProp<OrangeTicketStackParamList>;

export type OrangeTicketStackParamList = {
Prize: { ticketId: string; amount: number };
UsedCard: { amount: number };
Error: { errorCode: number };
};

const Stack = createNativeStackNavigator<OrangeTicketStackParamList>();

const screenOptions: NativeStackNavigationOptions = {
presentation: 'transparentModal',
headerShown: false,
};

const OrangeTicket = (): ReactElement => {
const snapPoints = useSnapPoints('large');
const [isLoading, setIsLoading] = useState(true);
const [amount, setAmount] = useState<number>();
const [errorCode, setErrorCode] = useState<number>();
const orangeTickets = useAppSelector((state) => state.settings.orangeTickets);
const [initialScreen, setInitialScreen] =
useState<keyof OrangeTicketStackParamList>('Prize');
const { isOpen, ticketId } = useAppSelector((state) => {
return viewControllerSelector(state, 'orangeTicket');
});

useBottomSheetBackPress('orangeTicket');

const getPrize = useCallback(async (): Promise<void> => {
const getChest = async (): Promise<any> => {
const response = await fetch(__TREASURE_HUNT_HOST__, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
method: 'getChest',
params: { input: { chestId: ticketId } },
}),
});

const { result } = await response.json();
return result;
};

const openChest = async (): Promise<any> => {
await waitForLdk();

const nodeId = await getNodeId();
const nodePublicKey = nodeId.isOk() ? nodeId.value : '';
const input = { chestId: ticketId, nodePublicKey };
const signResult = await ldk.nodeSign({
message: JSON.stringify(input),
messagePrefix: '',
});
if (signResult.isErr()) {
showToast({
type: 'error',
title: 'Failed to get prize',
description: 'Bitkit could not sign your claim request.',
});
return;
}
const signature = signResult.value;

const response = await fetch(__TREASURE_HUNT_HOST__, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
method: 'openChest',
params: { input, signature },
}),
});

const { result } = await response.json();
return result;
};

if (!ticketId) {
return;
}

const chestResponse = await getChest();
if (chestResponse.error) {
setErrorCode(chestResponse.code);
setInitialScreen('Error');
setIsLoading(false);
return;
}
setAmount(chestResponse.amountSat);

// Check if the ticket has already been used
if (orangeTickets.includes(ticketId)) {
setInitialScreen('UsedCard');
setIsLoading(false);
return;
}

const openResponse = await openChest();
if (openResponse.error) {
if (openResponse.code === 5000) {
setInitialScreen('UsedCard');
} else {
setErrorCode(openResponse.code);
setInitialScreen('Error');
}
}
setIsLoading(false);
setAmount(openResponse.amountSat);

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ticketId]);

useEffect(() => {
if (!isOpen) {
setInitialScreen('Prize');
setIsLoading(true);
return;
}

getPrize();
}, [isOpen, getPrize]);

if (isLoading) {
return <></>;
}

return (
<BottomSheetWrapper view="orangeTicket" snapPoints={snapPoints}>
<NavigationContainer key={isOpen.toString()}>
<Stack.Navigator
initialRouteName={initialScreen}
screenOptions={screenOptions}>
<Stack.Screen
name="Prize"
component={Prize}
initialParams={{ ticketId, amount }}
/>
<Stack.Screen
name="UsedCard"
component={UsedCard}
initialParams={{ amount }}
/>
<Stack.Screen
name="Error"
component={Error}
initialParams={{ errorCode }}
/>
</Stack.Navigator>
</NavigationContainer>
</BottomSheetWrapper>
);
};

export default memo(OrangeTicket);
5 changes: 5 additions & 0 deletions src/navigation/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { ProfileLinkStackParamList } from '../bottom-sheet/ProfileLinkNavig
import type { ReceiveStackParamList } from '../bottom-sheet/ReceiveNavigation';
import type { SendStackParamList } from '../bottom-sheet/SendNavigation';
import type { LNURLWithdrawStackParamList } from '../bottom-sheet/LNURLWithdrawNavigation';
import type { OrangeTicketStackParamList } from '../bottom-sheet/OrangeTicketNavigation';
import type { TreasureHuntStackParamList } from '../bottom-sheet/TreasureHuntNavigation';

// TODO: move all navigation related types here
Expand Down Expand Up @@ -103,6 +104,10 @@ export type SendScreenProps<T extends keyof SendStackParamList> =
export type LNURLWithdrawProps<T extends keyof LNURLWithdrawStackParamList> =
NativeStackScreenProps<LNURLWithdrawStackParamList, T>;

export type OrangeTicketScreenProps<
T extends keyof OrangeTicketStackParamList,
> = NativeStackScreenProps<OrangeTicketStackParamList, T>;

export type TreasureHuntScreenProps<
T extends keyof TreasureHuntStackParamList,
> = NativeStackScreenProps<TreasureHuntStackParamList, T>;
Loading

0 comments on commit 3392156

Please sign in to comment.