Skip to content

Commit

Permalink
Merge pull request #594 from cocrafts/ltminhthu/first-time-popup
Browse files Browse the repository at this point in the history
[wallet][MS-245] first time user popup for adding pixeverse
  • Loading branch information
tanlethanh authored Aug 9, 2024
2 parents 27e0cca + 997dd7c commit 70197ab
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 4 deletions.
36 changes: 36 additions & 0 deletions apps/wallet/src/modals/FirstTimePopup/BlueCircleBackground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Defs, G, Svg } from 'react-native-svg';

export const BlueCircleBackground = () => {
return (
<Svg width="308" height="174" viewBox="0 0 308 174" fill="none">
<G opacity="0.8" filter="url(#filter0_f_14670_15152)">
<circle cx="154" cy="87" r="120" fill="#17A3E1" />
</G>
<Defs>
<filter
id="filter0_f_14670_15152"
x="0"
y="-67"
width="308"
height="308"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="17"
result="effect1_foregroundBlur_14670_15152"
/>
</filter>
</Defs>
</Svg>
);
};

export default BlueCircleBackground;
92 changes: 92 additions & 0 deletions apps/wallet/src/modals/FirstTimePopup/PixeverseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { FC } from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { Text } from '@walless/gui';
import type { WidgetDocument } from '@walless/store';

interface Props {
widget: WidgetDocument;
}

const PixeverseCard: FC<Props> = ({ widget }) => {
return (
<View style={styles.container}>
<View>
<Image
style={styles.coverImage}
source={{ uri: widget.storeMeta.coverUri }}
/>
<Image
style={styles.iconImage}
source={{ uri: widget.storeMeta.iconUri }}
/>
</View>

<View style={styles.infoContainer}>
<Text style={styles.title}>Pixeverse</Text>

<Text
style={[styles.description]}
numberOfLines={2}
ellipsizeMode="tail"
>
{widget.storeMeta.description}
</Text>

<View style={styles.addButton}>
<Text style={styles.buttonText}>Add</Text>
</View>
</View>
</View>
);
};

export default PixeverseCard;

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
backgroundColor: '#182027',
paddingHorizontal: 12,
paddingVertical: 12,
borderRadius: 12,
gap: 12,
},
coverImage: {
width: 112,
height: 80,
borderRadius: 8,
},
iconImage: {
position: 'absolute',
bottom: 4,
right: 4,
width: 28,
height: 28,
borderRadius: 4,
},
infoContainer: {
flex: 1,
justifyContent: 'space-between',
},
title: {
color: '#ffffff',
fontWeight: '500',
},
description: {
fontSize: 10,
color: '#566674',
},
addButton: {
backgroundColor: '#198CCA',
paddingHorizontal: 16,
paddingVertical: 4,
borderColor: '#17A3E1',
borderWidth: 1,
borderRadius: 8,
width: 'fit-content',
},
buttonText: {
color: '#ffffff',
fontWeight: '500',
},
});
120 changes: 120 additions & 0 deletions apps/wallet/src/modals/FirstTimePopup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import { modalActions, Text } from '@walless/gui';
import { ModalId } from 'modals/types';
import { mockWidgets } from 'state/widget';
import { navigate } from 'utils/navigation';
import { addWidgetToStorage } from 'utils/storage';

import BlueCircleBackground from './BlueCircleBackground';
import PixeverseCard from './PixeverseCard';

const FirstTimePopup = () => {
const pixeverseWidget = mockWidgets.find((item) => item._id === 'pixeverse');

const handleAddPixeverse = () => {
if (!pixeverseWidget) return;

addWidgetToStorage('pixeverse', pixeverseWidget);
navigate('Dashboard', {
screen: 'Explore',
params: {
screen: 'Widget',
params: {
id: 'pixeverse',
},
},
});
modalActions.destroy(ModalId.FirstTimePopup);
};

return (
<View style={styles.container}>
<View style={styles.upperPart}>
<View style={styles.backgroundImage}>
<BlueCircleBackground />
</View>

{pixeverseWidget && <PixeverseCard widget={pixeverseWidget} />}
</View>

<View style={styles.lowerPart}>
<Text style={styles.title}>Welcome to Walless</Text>

<View style={styles.textContainer}>
<Text style={styles.text}>
Lets get you set up with a brand new way of Web3 wallet. From the
Explorer tab, you can find the best of web3: gaming, multi-chain
wallets, trading tools,…
</Text>
<Text style={styles.text}>
To start earning daily tokens, add the in-wallet game PIXEVERSE and
the wallet widget.
</Text>
</View>

<TouchableOpacity onPress={handleAddPixeverse} style={styles.addButton}>
<Text style={styles.buttonText}>Add Pixeverse</Text>
</TouchableOpacity>
</View>
</View>
);
};

const styles = StyleSheet.create({
container: {
maxWidth: 374,
borderRadius: 16,
overflow: 'hidden',
},
upperPart: {
backgroundColor: '#031821',
paddingHorizontal: 52,
paddingVertical: 36,
alignItems: 'center',
},
backgroundImage: {
position: 'absolute',
top: 0,
},
addButton: {
backgroundColor: '#198CCA',
paddingHorizontal: 16,
paddingVertical: 4,
borderColor: '#17A3E1',
borderWidth: 1,
borderRadius: 8,
width: 'fit-content',
},
buttonText: {
color: '#ffffff',
fontWeight: '500',
},
lowerPart: {
gap: 20,
backgroundColor: '#222F37',
paddingHorizontal: 24,
paddingVertical: 24,
alignItems: 'center',
},
title: {
color: '#ffffff',
fontSize: 24,
fontWeight: '500',
},
textContainer: {
gap: 12,
},
text: {
color: '#ffffff',
},
});

export default FirstTimePopup;

export const showFirstTimePopup = () => {
modalActions.show({
id: ModalId.FirstTimePopup,
component: FirstTimePopup,
fullWidth: false,
});
};
1 change: 1 addition & 0 deletions apps/wallet/src/modals/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export enum ModalId {
ReferralLeaderBoard = 'ReferralLeaderBoard',
LoyaltyPartnerQuest = 'LoyaltyPartnerQuest',
LoyaltyHistory = 'LoyaltyHistory',
FirstTimePopup = 'FirstTimePopup',
}
30 changes: 29 additions & 1 deletion apps/wallet/src/stacks/Dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { useEffect } from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { runtime } from '@walless/core';
import type { ShowFirstTimeUserPopupDocument } from '@walless/store';
import { showFirstTimePopup } from 'modals/FirstTimePopup';
import BrowserScreen from 'screens/Dashboard/Browser';
import HomeStack from 'stacks/Home';
import SettingStack from 'stacks/Setting';
import { appState } from 'state/app';
import { mockWidgets } from 'state/widget';
import { noHeaderNavigation } from 'utils/constants';
import { useNotificationPermissionRequest } from 'utils/hooks';
import {
useNotificationPermissionRequest,
useSnapshot,
useWidgets,
} from 'utils/hooks';
import type { DashboardParamList } from 'utils/navigation';
import { storage } from 'utils/storage';

import ExplorerStack from '../Explorer';

Expand All @@ -15,6 +25,24 @@ const Tab = createBottomTabNavigator<DashboardParamList>();

export const DashboardStack = () => {
useNotificationPermissionRequest();
const { showFirstTimePopup: showPopup } = useSnapshot(appState);
const widgets = useWidgets();

useEffect(() => {
const alreadyHavePixeverse = widgets.some(
(widget) => widget._id === mockWidgets[0]._id,
);

if (showPopup && !alreadyHavePixeverse) {
showFirstTimePopup();
storage.put<ShowFirstTimeUserPopupDocument>({
_id: 'showFirstTimeUserPopup',
type: 'ShowFirstTimeUserPopup',
value: false,
});
appState.showFirstTimePopup = false;
}
}, []);

return (
<Tab.Navigator
Expand Down
2 changes: 2 additions & 0 deletions apps/wallet/src/state/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface AppState {
isBottomTabActive: boolean;
isSidebarAvatarActive: boolean;
};
showFirstTimePopup: boolean;
isMobileDisplay: boolean;
initialLinkingURL?: string;
}
Expand All @@ -42,6 +43,7 @@ export const appState = proxy<AppState>({
isBottomTabActive: false,
isSidebarAvatarActive: false,
},
showFirstTimePopup: true,
isMobileDisplay: false,
});

Expand Down
6 changes: 6 additions & 0 deletions apps/wallet/src/state/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
PouchDocument,
PublicKeyDocument,
SettingDocument,
ShowFirstTimeUserPopupDocument,
TokenDocument,
WidgetDocument,
} from '@walless/store';
Expand Down Expand Up @@ -71,6 +72,11 @@ export const bootstrap = async (): Promise<void> => {

export const launchApp = async (): Promise<void> => {
const settings = await storage.safeGet<SettingDocument>('settings');
const showFirstTimeUserPopup =
await storage.safeGet<ShowFirstTimeUserPopupDocument>(
'showFirstTimeUserPopup',
);
appState.showFirstTimePopup = showFirstTimeUserPopup?.value ?? true;

const isSignedIn = settings?.profile?.id;
if (isSignedIn) {
Expand Down
4 changes: 3 additions & 1 deletion apps/wallet/src/utils/auth/logout.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { appActions } from 'state/app';
import { auth } from '../firebase/index.web';
import { storage } from '../storage';

export const whitelist = ['showFirstTimeUserPopup'];

export const logout = async () => {
await signOut(auth());
await engine.clear();
await storage.clearAllDocs();
await storage.clearAllDocs(whitelist);
appActions.cleanupAfterLogOut();
};
4 changes: 4 additions & 0 deletions packages/core/utils/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ export interface ExtensionConfig {
storeMeta: ExtensionStoreMetadata;
networkMeta: ExtensionNetworkMetadata;
}

export interface ShowFirstTimeUserPopup {
value: boolean;
}
7 changes: 6 additions & 1 deletion packages/store/plugins/clear.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
const persistedIds = ['system'];

export async function clearAllDocs(this: PouchDB.Database) {
export async function clearAllDocs(
this: PouchDB.Database,
whitelist: string[] = [],
) {
const { rows } = await this.allDocs();
rows.map(async (row) => {
if (whitelist.includes(row.id)) return;

if (persistedIds.indexOf(row.id) === -1) {
const doc: { _deleted: boolean } = await this.get(row.id);
doc._deleted = true;
Expand Down
Loading

0 comments on commit 70197ab

Please sign in to comment.