Skip to content

Commit

Permalink
Merge branch 'shop-dev' into telegram-dev-01
Browse files Browse the repository at this point in the history
  • Loading branch information
lw committed May 16, 2024
2 parents 657ae6e + cf3314f commit 948677c
Show file tree
Hide file tree
Showing 17 changed files with 949 additions and 114 deletions.
87 changes: 77 additions & 10 deletions packages/extension-koni-ui/src/Popup/Home/Games/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
// Copyright 2019-2022 @subwallet/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { ShopModal } from '@subwallet/extension-koni-ui/components';
import GameAccount from '@subwallet/extension-koni-ui/components/Games/GameAccount';
import GameEnergy from '@subwallet/extension-koni-ui/components/Games/GameEnergy';
import { ShopModalId } from '@subwallet/extension-koni-ui/components/Modal/Shop/ShopModal';
import { BookaSdk } from '@subwallet/extension-koni-ui/connector/booka/sdk';
import { Game } from '@subwallet/extension-koni-ui/connector/booka/types';
import { EnergyConfig, Game, GameInventoryItem, GameItem } from '@subwallet/extension-koni-ui/connector/booka/types';
import { useSetCurrentPage, useTranslation } from '@subwallet/extension-koni-ui/hooks';
import { GameApp } from '@subwallet/extension-koni-ui/Popup/Home/Games/gameSDK';
import { ThemeProps } from '@subwallet/extension-koni-ui/types';
import { Button, Image, Typography } from '@subwallet/react-ui';
import { Button, Icon, Image, ModalContext, Typography } from '@subwallet/react-ui';
import CN from 'classnames';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { ShoppingBag } from 'phosphor-react';
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';

type Props = ThemeProps;
Expand All @@ -28,10 +31,17 @@ function checkComingSoon (game: Game): boolean {
return gameStartTime > Date.now();
}

const shopModalId = ShopModalId;

const Component = ({ className }: Props): React.ReactElement => {
useSetCurrentPage('/home/games');
const gameIframe = useRef<HTMLIFrameElement>(null);
const [gameList, setGameList] = useState<Game[]>(apiSDK.gameList);
const [energyConfig, setEnergyConfig] = useState<EnergyConfig | undefined>(apiSDK.energyConfig);
const [gameItemMap, setGameItemMap] = useState<Record<string, GameItem[]>>(apiSDK.gameItemMap);
const [gameInventoryItemList, setGameInventoryItemList] = useState<GameInventoryItem[]>(apiSDK.gameInventoryItemList);
const [currentGameShopId, setCurrentGameShopId] = useState<number>();
const { activeModal } = useContext(ModalContext);
const [account, setAccount] = useState(apiSDK.account);
const [currentGame, setCurrentGame] = useState<Game | undefined>(undefined);
const { t } = useTranslation();
Expand Down Expand Up @@ -67,6 +77,13 @@ const Component = ({ className }: Props): React.ReactElement => {
};
}, [exitGame]);

const onOpenShop = useCallback((gameId?: number) => {
return () => {
setCurrentGameShopId(gameId);
activeModal(shopModalId);
};
}, [activeModal]);

useEffect(() => {
const accountSub = apiSDK.subscribeAccount().subscribe((data) => {
setAccount(data);
Expand All @@ -76,9 +93,24 @@ const Component = ({ className }: Props): React.ReactElement => {
setGameList(data);
});

const energyConfigSub = apiSDK.subscribeEnergyConfig().subscribe((data) => {
setEnergyConfig(data);
});

const gameItemMapSub = apiSDK.subscribeGameItemMap().subscribe((data) => {
setGameItemMap(data);
});

const gameInventoryItemListSub = apiSDK.subscribeGameInventoryItemList().subscribe((data) => {
setGameInventoryItemList(data);
});

return () => {
accountSub.unsubscribe();
energyConfigSub.unsubscribe();
gameListSub.unsubscribe();
gameItemMapSub.unsubscribe();
gameInventoryItemListSub.unsubscribe();
};
}, []);

Expand All @@ -92,8 +124,21 @@ const Component = ({ className }: Props): React.ReactElement => {
/>
<GameEnergy
energy={account.attributes.energy}
maxEnergy={energyConfig?.maxEnergy}
startTime={account.attributes.lastEnergyUpdated}
/>

<Button
icon={(
<Icon
phosphorIcon={ShoppingBag}
size='md'
/>
)}
onClick={onOpenShop()}
size='xs'
type='ghost'
/>
</div>}
{gameList.map((game) => (<div
className={CN('game-item', { 'coming-soon': checkComingSoon(game) })}
Expand Down Expand Up @@ -131,13 +176,28 @@ const Component = ({ className }: Props): React.ReactElement => {
</Typography.Title>
</div>
<div className={'play-area'}>
<Button
className={'play-button'}
onClick={playGame(game)}
size={'xs'}
>
{t('Open')}
</Button>
<div>
<Button
icon={(
<Icon
phosphorIcon={ShoppingBag}
size='md'
/>
)}
onClick={onOpenShop(game.id)}
size='xs'
type='ghost'
/>

<Button
className={'play-button'}
onClick={playGame(game)}
size={'xs'}
>
{t('Open')}
</Button>
</div>

<Typography.Text
className={'game-energy'}
size={'sm'}
Expand All @@ -156,6 +216,13 @@ const Component = ({ className }: Props): React.ReactElement => {
src={currentGame.url}
/>
</div>}

<ShopModal
energyConfig={energyConfig}
gameId={currentGameShopId}
gameInventoryItemList={gameInventoryItemList}
gameItemMap={gameItemMap}
/>
</div>;
};

Expand Down
103 changes: 103 additions & 0 deletions packages/extension-koni-ui/src/Popup/Home/Mission/TaskCategoryList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2019-2022 @subwallet/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { TaskCategory, TaskCategoryInfo } from '@subwallet/extension-koni-ui/connector/booka/types';
import { useTranslation } from '@subwallet/extension-koni-ui/hooks';
import { ThemeProps } from '@subwallet/extension-koni-ui/types';
import { formatInteger } from '@subwallet/extension-koni-ui/utils';
import { Icon, Image, Typography } from '@subwallet/react-ui';
import { CaretRight } from 'phosphor-react';
import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';

type Props = ThemeProps & {
taskCategoryList: TaskCategory[];
taskCategoryInfoMap: Record<number, TaskCategoryInfo>;
onClickCategoryItem: (categoryId: number) => void;
};

const Component = ({ className, onClickCategoryItem, taskCategoryInfoMap, taskCategoryList }: Props): React.ReactElement => {
const { t } = useTranslation();

const filteredTaskCategoryList = useMemo(() => {
return taskCategoryList.filter((tc) => {
return taskCategoryInfoMap[tc.id] && taskCategoryInfoMap[tc.id].tasks.length;
});
}, [taskCategoryInfoMap, taskCategoryList]);

const onClickItem = useCallback((categoryId: number) => {
return () => {
onClickCategoryItem(categoryId);
};
}, [onClickCategoryItem]);

return (
<div className={className}>
<Typography.Title level={4}>
{t('Categories')}
</Typography.Title>

{
filteredTaskCategoryList.map((tc) => (
<div
className={'task-category-item'}
key={tc.id}
onClick={onClickItem(tc.id)}
>
<Image
className={'task-category-banner'}
src={tc.icon || undefined}
width={40}
></Image>
<div className={'task-category-item-content'}>
<div>{tc.name}</div>

<div>
Min point can earn: {formatInteger(taskCategoryInfoMap[tc.id]?.minPoint || 0)}
</div>
</div>
<div className={'task-category-item-caret-icon'}>
<Icon
customSize={'20px'}
phosphorIcon={CaretRight}
/>
</div>
</div>
))
}
</div>
);
};

export const TaskCategoryList = styled(Component)<ThemeProps>(({ theme: { extendToken, token } }: ThemeProps) => {
return {
'.task-category-banner': {
marginRight: token.marginSM
},

'.task-category-item': {
display: 'flex',
backgroundColor: token.colorBgSecondary,
minHeight: 50,
borderRadius: token.borderRadiusLG,
padding: token.padding,
cursor: 'pointer',
alignItems: 'center'
},

'.task-category-item-content': {
flex: 1
},

'.task-category-item-caret-icon': {
minWidth: 40,
marginRight: -token.marginXS,
display: 'flex',
justifyContent: 'center'
},

'.task-category-item + .task-category-item': {
marginTop: token.marginXS
}
};
});
88 changes: 46 additions & 42 deletions packages/extension-koni-ui/src/Popup/Home/Mission/TaskItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const _TaskItem = ({ className, task }: Props): React.ReactElement => {
useSetCurrentPage('/home/mission');
const [taskLoading, setTaskLoading] = useState<boolean>(false);
const { t } = useTranslation();
const [disabled, setDisabled] = useState<boolean>(false);
const completed = !!task.completedAt;

const finishTask = useCallback(() => {
Expand All @@ -39,56 +38,42 @@ const _TaskItem = ({ className, task }: Props): React.ReactElement => {
setTaskLoading(false);
})
.catch(console.error);

setTimeout(() => {
telegramConnector.openLink(task.url);
task.url && telegramConnector.openLink(task.url);
}, 100);
}, [task.id, task.url]);

const CountDownElement = useCallback(() => {
if (completed) {
return <></>;
}

const { endTime,
isDisabled,
isEnd, isInTimeRange,
isNotStarted,
startTime } = (() => {
const now = Date.now();

if (task.startTime) {
const startTime = new Date(task.startTime).getTime();

if (startTime > now) {
setDisabled(true);

return <CountDown
prefix={t('Begins in ')}
targetTime={startTime}
/>;
}
}

if (task.endTime) {
const endTime = new Date(task.endTime).getTime();

if (endTime > now) {
return <CountDown
prefix={t('Ends in ')}
targetTime={endTime}
/>;
} else {
setDisabled(true);

return <span>{t('Ended')}</span>;
}
}

return <></>;
}, [completed, t, task.endTime, task.startTime]);
const startTime = task.startTime ? new Date(task.startTime).getTime() : undefined;
const endTime = task.endTime ? new Date(task.endTime).getTime() : undefined;
const isNotStarted = !completed && !!startTime && startTime > now;
const isInTimeRange = !completed && !!endTime && endTime > now;
const isEnd = !completed && !!endTime && endTime <= now;

return {
startTime,
endTime,
isNotStarted,
isInTimeRange,
isEnd,
isDisabled: isNotStarted || isEnd
};
})();

return <div
className={CN(className, { disabled: disabled })}
className={CN(className, { disabled: isDisabled })}
key={task.id}
>
<Image
className={'task-banner'}
src={task.icon}
src={task.icon || undefined}
width={40}
></Image>
<div className='task-title'>
Expand All @@ -100,13 +85,32 @@ const _TaskItem = ({ className, task }: Props): React.ReactElement => {
className={'__sub-title'}
size={'sm'}
>
<GamePoint text={`${formatInteger(task.pointReward)}`} />
<CountDownElement />
<GamePoint text={`${formatInteger(task.pointReward || 0)}`} />

{
isNotStarted && !!startTime && (
<CountDown
prefix={t('Begins in ')}
targetTime={startTime}
/>
)
}
{
isInTimeRange && !!endTime && (
<CountDown
prefix={t('Ends in ')}
targetTime={endTime}
/>
)
}
{
isEnd && (<span>{t('Ended')}</span>)
}
</Typography.Text>
</div>
{!completed && <Button
className={'play-button'}
disabled={disabled}
disabled={isDisabled}
loading={taskLoading}
onClick={finishTask}
size={'xs'}
Expand Down
Loading

0 comments on commit 948677c

Please sign in to comment.