diff --git a/JoyboyCommunity/package.json b/JoyboyCommunity/package.json
index 4c3481a3..d64e7926 100644
--- a/JoyboyCommunity/package.json
+++ b/JoyboyCommunity/package.json
@@ -23,8 +23,8 @@
"@noble/curves": "^1.4.0",
"@nostr-dev-kit/ndk": "^2.8.2",
"@react-native-async-storage/async-storage": "^1.23.1",
- "@react-native-community/netinfo": "^11.3.2",
- "@react-native-picker/picker": "^2.7.7",
+ "@react-native-community/netinfo": "11.3.1",
+ "@react-native-picker/picker": "2.7.5",
"@react-navigation/bottom-tabs": "^6.5.20",
"@react-navigation/native": "^6.1.17",
"@react-navigation/native-stack": "^6.9.26",
@@ -40,13 +40,13 @@
"buffer": "^6.0.3",
"crypto-es": "^2.1.0",
"events": "^3.3.0",
- "expo": "~51.0.8",
+ "expo": "~51.0.17",
"expo-application": "^5.9.1",
"expo-clipboard": "~6.0.3",
"expo-crypto": "~13.0.2",
- "expo-image-picker": "~15.0.5",
+ "expo-image-picker": "~15.0.7",
"expo-linking": "~6.3.1",
- "expo-secure-store": "~13.0.1",
+ "expo-secure-store": "~13.0.2",
"expo-splash-screen": "~0.27.4",
"expo-status-bar": "~1.12.1",
"fast-text-encoding": "^1.0.6",
@@ -56,7 +56,7 @@
"pbkdf2": "^3.1.2",
"react": "18.2.0",
"react-dom": "18.2.0",
- "react-native": "0.74.1",
+ "react-native": "0.74.3",
"react-native-gesture-handler": "2.16.2",
"react-native-get-random-values": "^1.11.0",
"react-native-keychain": "^8.2.0",
@@ -66,9 +66,9 @@
"react-native-qrcode-svg": "^6.3.1",
"react-native-reanimated": "~3.10.1",
"react-native-redash": "^18.1.3",
- "react-native-safe-area-context": "^4.8.2",
- "react-native-screens": "^3.29.0",
- "react-native-svg": "^15.3.0",
+ "react-native-safe-area-context": "4.10.1",
+ "react-native-screens": "3.31.1",
+ "react-native-svg": "15.2.0",
"react-native-tab-view": "^3.5.2",
"react-native-web": "~0.19.6",
"starknet": "6.9.0",
diff --git a/JoyboyCommunity/src/app/App.tsx b/JoyboyCommunity/src/app/App.tsx
index a39b4914..05fb2701 100644
--- a/JoyboyCommunity/src/app/App.tsx
+++ b/JoyboyCommunity/src/app/App.tsx
@@ -3,15 +3,20 @@ import '@walletconnect/react-native-compat';
import * as Font from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
import {useCallback, useEffect, useState} from 'react';
-import {StatusBar, View} from 'react-native';
+import {View} from 'react-native';
+import {useTips} from '../hooks';
+import {useToast} from '../hooks/modals';
import {Router} from './Router';
-// Keep the splash screen visible while we fetch resources
SplashScreen.preventAutoHideAsync();
export default function App() {
const [appIsReady, setAppIsReady] = useState(false);
+ const [sentTipNotification, setSentTipNotification] = useState(false);
+
+ const tips = useTips();
+ const {showToast} = useToast();
useEffect(() => {
(async () => {
@@ -27,31 +32,41 @@ export default function App() {
} catch (e) {
console.warn(e);
} finally {
- // Tell the application to render
setAppIsReady(true);
}
})();
}, []);
+ useEffect(() => {
+ const interval = setInterval(() => tips.refetch(), 2 * 60 * 1_000);
+ return () => clearInterval(interval);
+ }, [tips]);
+
+ useEffect(() => {
+ if (sentTipNotification) return;
+
+ const hasUnclaimedTip = (tips.data ?? []).some((tip) => !tip.claimed && tip.depositId);
+ if (hasUnclaimedTip) {
+ setSentTipNotification(true);
+ showToast({
+ type: 'info',
+ title: 'You have unclaimed tips',
+ });
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [tips.data]);
+
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
- // This tells the splash screen to hide immediately! If we call this after
- // `setAppIsReady`, then we may see a blank screen while the app is
- // loading its initial state and rendering its first pixels. So instead,
- // we hide the splash screen once we know the root view has already
- // performed layout.
await SplashScreen.hideAsync();
}
}, [appIsReady]);
- if (!appIsReady) {
- return null;
- }
+ if (!appIsReady) return null;
return (
-
-
);
diff --git a/JoyboyCommunity/src/app/Router.tsx b/JoyboyCommunity/src/app/Router.tsx
index cbb1bc66..e15b1117 100644
--- a/JoyboyCommunity/src/app/Router.tsx
+++ b/JoyboyCommunity/src/app/Router.tsx
@@ -1,10 +1,11 @@
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
+import {useEffect, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import {Icon} from '../components';
-import {useTheme} from '../hooks';
+import {useStyles} from '../hooks';
import {CreateAccount} from '../screens/Auth/CreateAccount';
import {Login} from '../screens/Auth/Login';
import {SaveKeys} from '../screens/Auth/SaveKeys';
@@ -15,7 +16,9 @@ import {PostDetail} from '../screens/PostDetail';
import {Profile} from '../screens/Profile';
import {Tips} from '../screens/Tips';
import {useAuth} from '../store/auth';
+import {ThemedStyleSheet} from '../styles';
import {AuthStackParams, HomeBottomStackParams, MainStackParams, RootStackParams} from '../types';
+import {retrievePublicKey} from '../utils/storage';
const RootStack = createNativeStackNavigator();
const AuthStack = createNativeStackNavigator();
@@ -23,22 +26,17 @@ const MainStack = createNativeStackNavigator();
const HomeBottomTabsStack = createBottomTabNavigator();
const HomeBottomTabNavigator: React.FC = () => {
- const theme = useTheme();
+ const styles = useStyles(stylesheet);
+
const {publicKey} = useAuth();
return (
{
tabBarActiveTintColor: 'white',
tabBarInactiveTintColor: '',
tabBarIcon: ({focused}) => (
-
+
{
}}
/>
- (
-
-
- {focused && }
-
- ),
- }}
- />
-
{
tabBarActiveTintColor: 'white',
tabBarInactiveTintColor: 'grey',
tabBarIcon: ({focused}) => (
-
+
{
(
-
+
{
};
const AuthNavigator: React.FC = () => {
+ const [publicKey, setPublicKey] = useState(undefined);
+
+ useEffect(() => {
+ retrievePublicKey().then((key) => {
+ setPublicKey(key);
+ });
+ });
+
+ if (publicKey === undefined) return null;
+
return (
-
+ {publicKey && }
@@ -165,3 +153,21 @@ export const Router: React.FC = () => {
);
};
+
+const stylesheet = ThemedStyleSheet((theme) => ({
+ sceneContainer: {
+ backgroundColor: theme.colors.background,
+ },
+
+ tabBar: {
+ backgroundColor: theme.colors.surface,
+ borderTopColor: theme.colors.divider,
+ borderTopWidth: StyleSheet.hairlineWidth,
+ },
+ tabBarIcon: {
+ flex: 1,
+ gap: 2,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+}));
diff --git a/JoyboyCommunity/src/app/Wrapper.tsx b/JoyboyCommunity/src/app/Wrapper.tsx
index f23b91d3..1968416b 100644
--- a/JoyboyCommunity/src/app/Wrapper.tsx
+++ b/JoyboyCommunity/src/app/Wrapper.tsx
@@ -37,17 +37,17 @@ export const Wrapper: React.FC = () => {
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
diff --git a/JoyboyCommunity/src/assets/feed/feed-bg.png b/JoyboyCommunity/src/assets/feed-background.png
similarity index 100%
rename from JoyboyCommunity/src/assets/feed/feed-bg.png
rename to JoyboyCommunity/src/assets/feed-background.png
diff --git a/JoyboyCommunity/src/assets/feed/images/post.svg b/JoyboyCommunity/src/assets/feed/images/post.svg
deleted file mode 100644
index e16ae4a4..00000000
--- a/JoyboyCommunity/src/assets/feed/images/post.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/JoyboyCommunity/src/assets/feed/images/story-1.png b/JoyboyCommunity/src/assets/feed/images/story-1.png
deleted file mode 100644
index 6936d0af..00000000
Binary files a/JoyboyCommunity/src/assets/feed/images/story-1.png and /dev/null differ
diff --git a/JoyboyCommunity/src/assets/feed/images/story-2.png b/JoyboyCommunity/src/assets/feed/images/story-2.png
deleted file mode 100644
index 6d5f4a3d..00000000
Binary files a/JoyboyCommunity/src/assets/feed/images/story-2.png and /dev/null differ
diff --git a/JoyboyCommunity/src/assets/feed/images/story-3.png b/JoyboyCommunity/src/assets/feed/images/story-3.png
deleted file mode 100644
index 57a35e55..00000000
Binary files a/JoyboyCommunity/src/assets/feed/images/story-3.png and /dev/null differ
diff --git a/JoyboyCommunity/src/assets/feed/images/story-4.png b/JoyboyCommunity/src/assets/feed/images/story-4.png
deleted file mode 100644
index 4f8f95dd..00000000
Binary files a/JoyboyCommunity/src/assets/feed/images/story-4.png and /dev/null differ
diff --git a/JoyboyCommunity/src/assets/feed/images/story-5.png b/JoyboyCommunity/src/assets/feed/images/story-5.png
deleted file mode 100644
index 87decf5a..00000000
Binary files a/JoyboyCommunity/src/assets/feed/images/story-5.png and /dev/null differ
diff --git a/JoyboyCommunity/src/assets/feed/images/story-bg.png b/JoyboyCommunity/src/assets/feed/images/story-bg.png
deleted file mode 100644
index 378b880d..00000000
Binary files a/JoyboyCommunity/src/assets/feed/images/story-bg.png and /dev/null differ
diff --git a/JoyboyCommunity/src/assets/icons.tsx b/JoyboyCommunity/src/assets/icons.tsx
index 0881e91f..1f344a23 100644
--- a/JoyboyCommunity/src/assets/icons.tsx
+++ b/JoyboyCommunity/src/assets/icons.tsx
@@ -510,3 +510,20 @@ export const FlagIcon: React.FC = (props) => (
/>
);
+
+export const CloseIcon: React.FC = (props) => (
+
+);
diff --git a/JoyboyCommunity/src/components/Button/index.tsx b/JoyboyCommunity/src/components/Button/index.tsx
index 9f263f74..c581ce03 100644
--- a/JoyboyCommunity/src/components/Button/index.tsx
+++ b/JoyboyCommunity/src/components/Button/index.tsx
@@ -26,7 +26,7 @@ export const Button: React.FC = ({
style: styleProp,
...pressableProps
}) => {
- const styles = useStyles(stylesheet, variant, block, disabled, small);
+ const styles = useStyles(stylesheet, variant, !!block, !!disabled, !!small);
return (
({
@@ -11,6 +13,8 @@ export default ThemedStyleSheet((theme) => ({
alignItems: 'center',
paddingVertical: Spacing.xxsmall,
paddingHorizontal: Spacing.medium,
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderBottomColor: theme.colors.divider,
},
logoContainer: {
diff --git a/JoyboyCommunity/src/components/IconButton/index.tsx b/JoyboyCommunity/src/components/IconButton/index.tsx
index feb36a53..f4924514 100644
--- a/JoyboyCommunity/src/components/IconButton/index.tsx
+++ b/JoyboyCommunity/src/components/IconButton/index.tsx
@@ -26,7 +26,7 @@ export const IconButton: React.FC = ({
const color = useColor(colorProp);
const backgroundColor = useColor(backgroundColorProp);
- const styles = useStyles(stylesheet, disabled, backgroundColor);
+ const styles = useStyles(stylesheet, !!disabled, backgroundColor);
return (
& MenuSubComponents = ({handle, open, onClose, c
}, [animation, open]);
const animatedMenuStyles = useAnimatedStyle(() => {
- const menuWidth = Math.min(width / 1.6, 320);
+ const menuWidth = Math.min(width / 1.5, 320);
const menuStyle = {
...styles.menu,
diff --git a/JoyboyCommunity/src/components/Menu/styles.ts b/JoyboyCommunity/src/components/Menu/styles.ts
index 7657eccd..d2d2fdc8 100644
--- a/JoyboyCommunity/src/components/Menu/styles.ts
+++ b/JoyboyCommunity/src/components/Menu/styles.ts
@@ -31,6 +31,10 @@ export default ThemedStyleSheet((theme) => ({
backgroundColor: theme.colors.background,
borderRadius: 16,
overflow: 'hidden',
+ shadowColor: theme.colors.shadow,
+ shadowOffset: {width: 0, height: 2},
+ shadowRadius: 4,
+ elevation: 2,
},
menuItem: {
diff --git a/JoyboyCommunity/src/components/PickerContainer/index.tsx b/JoyboyCommunity/src/components/PickerContainer/index.tsx
index 41b50a11..dbd6a102 100644
--- a/JoyboyCommunity/src/components/PickerContainer/index.tsx
+++ b/JoyboyCommunity/src/components/PickerContainer/index.tsx
@@ -6,7 +6,7 @@ import {Modalize} from '../Modalize';
import {Text, TextProps} from '../Text';
export type PickerContainerProps = {
- selectedValue: string;
+ selectedValue?: string;
modalizeTitle: string;
style?: StyleProp;
diff --git a/JoyboyCommunity/src/components/Skeleton/RootScreenContainer.tsx b/JoyboyCommunity/src/components/Skeleton/RootScreenContainer.tsx
index 81aa2499..dd3846b3 100644
--- a/JoyboyCommunity/src/components/Skeleton/RootScreenContainer.tsx
+++ b/JoyboyCommunity/src/components/Skeleton/RootScreenContainer.tsx
@@ -1,9 +1,29 @@
-import {View, ViewProps} from 'react-native';
+import {Platform, View, ViewProps} from 'react-native';
-import {useTheme} from '../../hooks';
+import {WEB_MAX_WIDTH} from '../../constants/misc';
+import {useStyles} from '../../hooks';
+import {ThemedStyleSheet} from '../../styles';
-export const RootScreenContainer: React.FC = ({style, ...props}) => {
- const theme = useTheme();
+export const RootScreenContainer: React.FC = ({style, children, ...props}) => {
+ const styles = useStyles(stylesheet);
- return ;
+ return (
+
+ {children}
+
+ );
};
+
+const stylesheet = ThemedStyleSheet((theme) => ({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ backgroundColor: theme.colors.background,
+ },
+
+ content: {
+ flex: 1,
+ width: '100%',
+ maxWidth: Platform.OS === 'web' ? WEB_MAX_WIDTH : '100%',
+ },
+}));
diff --git a/JoyboyCommunity/src/components/Story/index.tsx b/JoyboyCommunity/src/components/Story/index.tsx
deleted file mode 100644
index 2067601f..00000000
--- a/JoyboyCommunity/src/components/Story/index.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import {Image, ImageSourcePropType, View} from 'react-native';
-
-import {Text} from '../Text';
-import styles from './styles';
-
-export type StoryProps = {
- image: ImageSourcePropType;
- name: string;
-};
-
-export const Story: React.FC = ({image, name}) => {
- return (
-
-
-
-
-
-
-
- {name}
-
-
- );
-};
diff --git a/JoyboyCommunity/src/components/Story/styles.ts b/JoyboyCommunity/src/components/Story/styles.ts
deleted file mode 100644
index 1e0364e9..00000000
--- a/JoyboyCommunity/src/components/Story/styles.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import {StyleSheet} from 'react-native';
-
-import {Spacing} from '../../styles';
-
-export default StyleSheet.create({
- container: {
- alignItems: 'center',
- },
-
- imageContainer: {
- position: 'relative',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- },
- image: {
- position: 'absolute',
- },
-
- name: {
- paddingTop: Spacing.xxsmall,
- },
-});
diff --git a/JoyboyCommunity/src/components/Toast/index.tsx b/JoyboyCommunity/src/components/Toast/index.tsx
index 06b1a23c..50316c13 100644
--- a/JoyboyCommunity/src/components/Toast/index.tsx
+++ b/JoyboyCommunity/src/components/Toast/index.tsx
@@ -2,6 +2,7 @@ import {View} from 'react-native';
import {ErrorIcon, InfoIcon, SuccessIcon} from '../../assets/icons';
import {useStyles, useTheme} from '../../hooks';
+import {IconButton} from '../IconButton';
import {Text} from '../Text';
import stylesheet from './styles';
@@ -10,7 +11,11 @@ export type ToastProps = {
title: string;
};
-export const Toast: React.FC = ({type, title}) => {
+export const Toast: React.FC void}> = ({
+ type,
+ title,
+ onDismiss,
+}) => {
const theme = useTheme();
const styles = useStyles(stylesheet, type);
@@ -37,6 +42,14 @@ export const Toast: React.FC = ({type, title}) => {
{title}
+
+
);
};
diff --git a/JoyboyCommunity/src/components/Toast/styles.ts b/JoyboyCommunity/src/components/Toast/styles.ts
index 6aa99eb1..fe41a77c 100644
--- a/JoyboyCommunity/src/components/Toast/styles.ts
+++ b/JoyboyCommunity/src/components/Toast/styles.ts
@@ -22,7 +22,13 @@ export default ThemedStyleSheet((theme, type: 'success' | 'info' | 'error') => (
backgroundColor: theme.colors.errorLight,
}),
},
+
text: {
flex: 1,
},
+
+ closeIcon: {
+ backgroundColor: theme.colors.transparent,
+ padding: Spacing.none,
+ },
}));
diff --git a/JoyboyCommunity/src/components/index.ts b/JoyboyCommunity/src/components/index.ts
index fc607ea1..5cbae276 100644
--- a/JoyboyCommunity/src/components/index.ts
+++ b/JoyboyCommunity/src/components/index.ts
@@ -15,7 +15,6 @@ export {InputAccessoryView} from './Skeleton/InputAccessoryView';
export {KeyboardFixedView} from './Skeleton/KeyboardFixedView';
export {RootScreenContainer} from './Skeleton/RootScreenContainer';
export {SquareInput} from './SquareInput';
-export {Story} from './Story';
export {Text} from './Text';
export {TextButton} from './TextButton';
export {Toast} from './Toast';
diff --git a/JoyboyCommunity/src/constants/env.ts b/JoyboyCommunity/src/constants/env.ts
index 672e7b39..a701baca 100644
--- a/JoyboyCommunity/src/constants/env.ts
+++ b/JoyboyCommunity/src/constants/env.ts
@@ -1,11 +1,14 @@
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+
import {constants} from 'starknet';
export const NETWORK_NAME = process.env.EXPO_PUBLIC_NETWORK as constants.NetworkName;
-export const PROVIDER_URL = process.env.EXPO_PUBLIC_PROVIDER_URL;
+export const CHAIN_ID = constants.StarknetChainId[NETWORK_NAME];
+export const PROVIDER_URL = process.env.EXPO_PUBLIC_PROVIDER_URL!;
-export const BACKEND_URL = process.env.EXPO_PUBLIC_BACKEND_URL;
+export const BACKEND_URL = process.env.EXPO_PUBLIC_BACKEND_URL!;
-export const WALLET_CONNECT_ID = process.env.EXPO_PUBLIC_WC_ID;
+export const WALLET_CONNECT_ID = process.env.EXPO_PUBLIC_WC_ID!;
if (!Object.keys(constants.NetworkName).includes(NETWORK_NAME)) {
throw new Error(`Invalid network name: ${NETWORK_NAME}`);
diff --git a/JoyboyCommunity/src/constants/misc.ts b/JoyboyCommunity/src/constants/misc.ts
index f666ca60..c2e94e3d 100644
--- a/JoyboyCommunity/src/constants/misc.ts
+++ b/JoyboyCommunity/src/constants/misc.ts
@@ -14,6 +14,9 @@ export enum Entrypoint {
export enum EventKey {
DepositEvent = '0xa1db419bdf20c7726cf74c30394c4300e5645db4e3cacaf897da05faabae03',
TransferEvent = '0x15884c9d44b49803fec52bec32166b4a87f9683725e9c83e8b0bc12306fc10',
+ ClaimEvent = '0x1338111cc170c56fecb176d35cca4c04823f0b8d9c64cfb956c97b236ea6fc6',
}
export const DEFAULT_TIMELOCK = 7 * 24 * 60 * 60 * 1_000; // 7 days
+
+export const WEB_MAX_WIDTH = 520;
diff --git a/JoyboyCommunity/src/context/TipModal.tsx b/JoyboyCommunity/src/context/TipModal.tsx
index f006b95f..7d47c973 100644
--- a/JoyboyCommunity/src/context/TipModal.tsx
+++ b/JoyboyCommunity/src/context/TipModal.tsx
@@ -47,7 +47,14 @@ export const TipModalProvider: React.FC = ({children})
{children}
-
+
{successModal && }
diff --git a/JoyboyCommunity/src/context/Toast/AnimatedToast.tsx b/JoyboyCommunity/src/context/Toast/AnimatedToast.tsx
index 18a28ad5..86eab963 100644
--- a/JoyboyCommunity/src/context/Toast/AnimatedToast.tsx
+++ b/JoyboyCommunity/src/context/Toast/AnimatedToast.tsx
@@ -1,3 +1,4 @@
+import {useEffect} from 'react';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import Animated, {
measure,
@@ -10,17 +11,20 @@ import Animated, {
import {clamp, snapPoint} from 'react-native-redash';
import {Toast} from '../../components';
-import {useToast} from '../../hooks';
-import {ToastConfig} from './ToastContext';
-
-export const AnimatedToast: React.FC<{toast: ToastConfig}> = ({toast}) => {
- const {hideToast} = useToast();
+import type {ToastConfig} from './ToastContext';
+export const AnimatedToast: React.FC<{toast: ToastConfig; hide: () => void}> = ({toast, hide}) => {
const containerRef = useAnimatedRef();
const top = useSharedValue(0);
const translateY = useSharedValue(0);
+ useEffect(() => {
+ setTimeout(onDismiss, toast.timeout ?? 10_000);
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const pan = Gesture.Pan()
.onChange(({translationY}) => {
translateY.value = clamp(translationY, -999, 0);
@@ -33,7 +37,7 @@ export const AnimatedToast: React.FC<{toast: ToastConfig}> = ({toast}) => {
if (snapTo < 0) {
top.value = withTiming(1, {duration: 200}, () => {
- runOnJS(hideToast)(toast.key);
+ runOnJS(hide)();
});
}
@@ -45,10 +49,16 @@ export const AnimatedToast: React.FC<{toast: ToastConfig}> = ({toast}) => {
transform: [{translateY: translateY.value}],
}));
+ const onDismiss = () => {
+ top.value = withTiming(1, {duration: 200}, () => {
+ runOnJS(hide)();
+ });
+ };
+
return (
-
+
);
diff --git a/JoyboyCommunity/src/context/Toast/ToastContext.tsx b/JoyboyCommunity/src/context/Toast/ToastContext.tsx
index d9753dcf..67e4e7b4 100644
--- a/JoyboyCommunity/src/context/Toast/ToastContext.tsx
+++ b/JoyboyCommunity/src/context/Toast/ToastContext.tsx
@@ -9,10 +9,11 @@ import styles from './styles';
export type ToastConfig = ToastProps & {
key: string;
+ timeout?: number;
};
export type ToastContextType = {
- showToast: (toast: ToastProps) => () => void;
+ showToast: (toast: Omit) => () => void;
hideToast: (key: string) => void;
};
@@ -26,7 +27,7 @@ export const ToastProvider: React.FC<{children: React.ReactNode}> = ({children})
}, []);
const showToast = useCallback(
- (toast: ToastProps) => {
+ (toast: Omit) => {
const key = randomUUID();
setToasts((prev) => [...prev, {...toast, key}]);
@@ -46,7 +47,7 @@ export const ToastProvider: React.FC<{children: React.ReactNode}> = ({children})
{toasts.map((toast) => (
-
+ hideToast(toast.key)} />
))}
diff --git a/JoyboyCommunity/src/hooks/api/useApiMutation.ts b/JoyboyCommunity/src/hooks/api/useApiMutation.ts
index 28c8104b..d827438d 100644
--- a/JoyboyCommunity/src/hooks/api/useApiMutation.ts
+++ b/JoyboyCommunity/src/hooks/api/useApiMutation.ts
@@ -26,7 +26,7 @@ export const useApiMutation = <
useEffect(() => {
if (showErrorToast && mutation.error) {
const {response} = mutation.error;
- if (!response || typeof response.data !== 'object' || !('code' in response.data)) {
+ if (!response?.data || typeof response.data !== 'object' || !('code' in response.data)) {
showToast({
type: 'error',
title: 'Request failed with no response, please try again later.',
diff --git a/JoyboyCommunity/src/hooks/index.ts b/JoyboyCommunity/src/hooks/index.ts
index e35c3279..db500bff 100644
--- a/JoyboyCommunity/src/hooks/index.ts
+++ b/JoyboyCommunity/src/hooks/index.ts
@@ -1,10 +1,7 @@
-export * from './api';
-export * from './modals';
export * from './nostr';
-export {useChainId} from './useChainId';
export {useColor} from './useColor';
export {useStyles} from './useStyles';
export {useTheme} from './useTheme';
export {useTips} from './useTips';
-export {useTransaction} from './useTransaction';
export {useWaitConnection} from './useWaitConnection';
+export {useWindowDimensions} from './useWindowDimensions';
diff --git a/JoyboyCommunity/src/hooks/modals/index.ts b/JoyboyCommunity/src/hooks/modals/index.ts
index 71468a67..cff72690 100644
--- a/JoyboyCommunity/src/hooks/modals/index.ts
+++ b/JoyboyCommunity/src/hooks/modals/index.ts
@@ -1,5 +1,6 @@
export {useDialog} from './useDialog';
export {useTipModal} from './useTipModal';
export {useToast} from './useToast';
+export {useTransaction} from './useTransaction';
export {useTransactionModal} from './useTransactionModal';
export {useWalletModal} from './useWalletModal';
diff --git a/JoyboyCommunity/src/hooks/useTransaction.ts b/JoyboyCommunity/src/hooks/modals/useTransaction.ts
similarity index 92%
rename from JoyboyCommunity/src/hooks/useTransaction.ts
rename to JoyboyCommunity/src/hooks/modals/useTransaction.ts
index 99b3ccb5..c7f9938d 100644
--- a/JoyboyCommunity/src/hooks/useTransaction.ts
+++ b/JoyboyCommunity/src/hooks/modals/useTransaction.ts
@@ -1,7 +1,7 @@
import {ContractWriteVariables, useContractWrite} from '@starknet-react/core';
import {GetTransactionReceiptResponse} from 'starknet';
-import {useTransactionModal} from './modals/useTransactionModal';
+import {useTransactionModal} from './useTransactionModal';
export const useTransaction = () => {
const {show: showTransactionModal, hide: hideTransactionModal, shown} = useTransactionModal();
diff --git a/JoyboyCommunity/src/hooks/nostr/useContacts.ts b/JoyboyCommunity/src/hooks/nostr/useContacts.ts
index 23f59984..4c0e47cd 100644
--- a/JoyboyCommunity/src/hooks/nostr/useContacts.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useContacts.ts
@@ -20,7 +20,7 @@ export const useContacts = (options?: UseContactsOptions) => {
search: options?.search,
});
- return contacts.tags.filter((tag) => tag[0] === 'p').map((tag) => tag[1]);
+ return contacts?.tags.filter((tag) => tag[0] === 'p').map((tag) => tag[1]) ?? [];
},
placeholderData: [],
});
diff --git a/JoyboyCommunity/src/hooks/nostr/useEditProfile.ts b/JoyboyCommunity/src/hooks/nostr/useEditProfile.ts
index 3c8cfb03..b6f04ee8 100644
--- a/JoyboyCommunity/src/hooks/nostr/useEditProfile.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useEditProfile.ts
@@ -6,16 +6,12 @@ import {useAuth} from '../../store/auth';
export const useEditProfile = () => {
const {ndk} = useNostrContext();
- const {publicKey, privateKey} = useAuth();
+ const {publicKey} = useAuth();
return useMutation({
mutationKey: ['editProfile'],
mutationFn: async (data: NDKUserProfile) => {
try {
- if (!privateKey) {
- throw new Error('Private key is required');
- }
-
const user = ndk.getUser({pubkey: publicKey});
await user.fetchProfile();
diff --git a/JoyboyCommunity/src/hooks/nostr/useNote.ts b/JoyboyCommunity/src/hooks/nostr/useNote.ts
index ded45527..0261d604 100644
--- a/JoyboyCommunity/src/hooks/nostr/useNote.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useNote.ts
@@ -18,7 +18,7 @@ export const useNote = (options: UseNoteOptions) => {
ids: [options.noteId],
});
- return note;
+ return note ?? undefined;
},
});
};
diff --git a/JoyboyCommunity/src/hooks/nostr/useProfile.ts b/JoyboyCommunity/src/hooks/nostr/useProfile.ts
index 96b68aef..8bd9849a 100644
--- a/JoyboyCommunity/src/hooks/nostr/useProfile.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useProfile.ts
@@ -3,7 +3,7 @@ import {useQuery} from '@tanstack/react-query';
import {useNostrContext} from '../../context/NostrContext';
export type UseProfileOptions = {
- publicKey: string;
+ publicKey?: string;
};
export const useProfile = (options: UseProfileOptions) => {
diff --git a/JoyboyCommunity/src/hooks/nostr/useReact.ts b/JoyboyCommunity/src/hooks/nostr/useReact.ts
index 9ec58ef5..e86cf080 100644
--- a/JoyboyCommunity/src/hooks/nostr/useReact.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useReact.ts
@@ -15,7 +15,7 @@ export const useReact = () => {
event.tags = [
['e', data.event.id],
['p', data.event.pubkey],
- ['k', data.event.kind.toString()],
+ ['k', (data.event.kind ?? 1).toString()],
];
return event.publish();
diff --git a/JoyboyCommunity/src/hooks/nostr/useReactions.ts b/JoyboyCommunity/src/hooks/nostr/useReactions.ts
index 233e4a87..29aff5c2 100644
--- a/JoyboyCommunity/src/hooks/nostr/useReactions.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useReactions.ts
@@ -13,7 +13,7 @@ export const useReactions = (options?: UseReactionsOptions) => {
const {ndk} = useNostrContext();
return useQuery({
- queryKey: ['reactions', options?.authors, options?.search, options?.noteId],
+ queryKey: ['reactions', options?.noteId, options?.authors, options?.search],
queryFn: async () => {
const notes = await ndk.fetchEvents({
kinds: [NDKKind.Reaction],
diff --git a/JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts b/JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts
index 2d3f2820..8538a96f 100644
--- a/JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useReplyNotes.ts
@@ -18,7 +18,7 @@ export const useReplyNotes = (options?: UseReplyNotesOptions) => {
getNextPageParam: (lastPage: any, allPages, lastPageParam) => {
if (!lastPage?.length) return undefined;
- const pageParam = lastPage[lastPage.length - 1].created_at;
+ const pageParam = lastPage[lastPage.length - 1].created_at - 1;
if (!pageParam || pageParam === lastPageParam) return undefined;
return pageParam;
diff --git a/JoyboyCommunity/src/hooks/nostr/useReposts.ts b/JoyboyCommunity/src/hooks/nostr/useReposts.ts
index 249058d7..9b912f2c 100644
--- a/JoyboyCommunity/src/hooks/nostr/useReposts.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useReposts.ts
@@ -17,7 +17,7 @@ export const useReposts = (options?: UseRepostsOptions) => {
getNextPageParam: (lastPage: any, allPages, lastPageParam) => {
if (!lastPage?.length) return undefined;
- const pageParam = lastPage[lastPage.length - 1].created_at;
+ const pageParam = lastPage[lastPage.length - 1].created_at - 1;
if (!pageParam || pageParam === lastPageParam) return undefined;
return pageParam;
diff --git a/JoyboyCommunity/src/hooks/nostr/useRootNotes.ts b/JoyboyCommunity/src/hooks/nostr/useRootNotes.ts
index 42cc9fe0..b88f29c7 100644
--- a/JoyboyCommunity/src/hooks/nostr/useRootNotes.ts
+++ b/JoyboyCommunity/src/hooks/nostr/useRootNotes.ts
@@ -17,25 +17,21 @@ export const useRootNotes = (options?: UseRootNotesOptions) => {
getNextPageParam: (lastPage: any, allPages, lastPageParam) => {
if (!lastPage?.length) return undefined;
- const pageParam = lastPage[lastPage.length - 1].created_at;
+ const pageParam = lastPage[lastPage.length - 1].created_at - 1;
if (!pageParam || pageParam === lastPageParam) return undefined;
return pageParam;
},
queryFn: async ({pageParam}) => {
- try {
- const notes = await ndk.fetchEvents({
- kinds: [NDKKind.Text],
- authors: options?.authors,
- search: options?.search,
- until: pageParam || Math.round(Date.now() / 1000),
- limit: 20,
- });
+ const notes = await ndk.fetchEvents({
+ kinds: [NDKKind.Text],
+ authors: options?.authors,
+ search: options?.search,
+ until: pageParam || Math.round(Date.now() / 1000),
+ limit: 20,
+ });
- return [...notes].filter((note) => note.tags.every((tag) => tag[0] !== 'e'));
- } catch (error) {
- console.log('error', error);
- }
+ return [...notes].filter((note) => note.tags.every((tag) => tag[0] !== 'e'));
},
placeholderData: {pages: [], pageParams: []},
});
diff --git a/JoyboyCommunity/src/hooks/useChainId.ts b/JoyboyCommunity/src/hooks/useChainId.ts
deleted file mode 100644
index 509652f7..00000000
--- a/JoyboyCommunity/src/hooks/useChainId.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import {starknetChainId, useNetwork} from '@starknet-react/core';
-import {useMemo} from 'react';
-
-export const useChainId = () => {
- const {chain} = useNetwork();
-
- return useMemo(() => (chain.id ? starknetChainId(chain.id) : undefined), [chain.id]);
-};
diff --git a/JoyboyCommunity/src/hooks/useColor.ts b/JoyboyCommunity/src/hooks/useColor.ts
index 12a627b4..168393d1 100644
--- a/JoyboyCommunity/src/hooks/useColor.ts
+++ b/JoyboyCommunity/src/hooks/useColor.ts
@@ -1,11 +1,13 @@
-import {ColorProp} from '../styles';
+import {ColorProp, ThemeColorNames} from '../styles';
import {useTheme} from './useTheme';
-export const useColor = (color: ColorProp) => {
+type Color = `#${string}` | `rgb${string}` | 'transparent';
+
+export const useColor = (color: ColorProp): Color => {
const theme = useTheme();
if (color === 'transparent') return 'transparent';
- if (color.startsWith('#') || color.startsWith('rgb')) return color;
+ if (color.startsWith('#') || color.startsWith('rgb')) return color as Color;
- return theme.colors[color] ?? theme.colors.text;
+ return (theme.colors[color as ThemeColorNames] ?? theme.colors.text) as Color;
};
diff --git a/JoyboyCommunity/src/hooks/useTips.ts b/JoyboyCommunity/src/hooks/useTips.ts
index 4e640655..11bb320c 100644
--- a/JoyboyCommunity/src/hooks/useTips.ts
+++ b/JoyboyCommunity/src/hooks/useTips.ts
@@ -1,49 +1,80 @@
-import {useInfiniteQuery} from '@tanstack/react-query';
+import {useQuery} from '@tanstack/react-query';
import {uint256} from 'starknet';
import {ESCROW_ADDRESSES} from '../constants/contracts';
+import {CHAIN_ID} from '../constants/env';
import {EventKey} from '../constants/misc';
import {useAuth} from '../store/auth';
-import {useChainId} from './useChainId';
+import {parseClaimEvent, parseDepositEvent} from '../utils/events';
import {useRpcProvider} from './useRpcProvider';
export const useTips = () => {
- const chainId = useChainId();
const provider = useRpcProvider();
const {publicKey} = useAuth();
- return useInfiniteQuery({
- initialPageParam: undefined as string | undefined,
- queryKey: ['tips', chainId, publicKey],
- getNextPageParam: (
- lastPage: Awaited>,
- allPages,
- lastPageParam,
- ) => {
- if (!lastPage?.continuation_token) return undefined;
+ return useQuery({
+ queryKey: ['tips', CHAIN_ID, publicKey],
+ queryFn: async () => {
+ if (!publicKey) return [];
- const pageParam = lastPage.continuation_token;
-
- if (!pageParam || pageParam === lastPageParam) return undefined;
- return pageParam;
- },
- queryFn: async ({pageParam}) => {
const {low, high} = uint256.bnToUint256(`0x${publicKey}`);
- const tips = await provider.getEvents({
- address: ESCROW_ADDRESSES[chainId],
- keys: [
- [EventKey.DepositEvent, EventKey.TransferEvent],
- [],
- [],
- [low.toString(), high.toString()],
- ],
- chunk_size: 1000,
- continuation_token: pageParam,
- });
-
- return tips;
+ const getTipEvents = async (
+ continuationToken?: string,
+ ): Promise>['events']> => {
+ const tips = await provider.getEvents({
+ address: ESCROW_ADDRESSES[CHAIN_ID],
+ keys: [
+ [EventKey.DepositEvent, EventKey.TransferEvent],
+ [],
+ [],
+ [low.toString(), high.toString()],
+ ],
+ to_block: 'pending',
+ chunk_size: 1000,
+ continuation_token: continuationToken,
+ });
+
+ if (tips.continuation_token) {
+ const next = await getTipEvents(tips.continuation_token);
+ return [...tips.events, ...next];
+ }
+
+ return tips.events;
+ };
+
+ const tipEvents = (await getTipEvents())
+ .map((event) => parseDepositEvent(event))
+ .filter((event): event is NonNullable> => !!event);
+
+ const getClaimEvents = async (
+ continuationToken?: string,
+ ): Promise>['events']> => {
+ const tips = await provider.getEvents({
+ address: ESCROW_ADDRESSES[CHAIN_ID],
+ keys: [[EventKey.ClaimEvent], [], [], [low.toString()], [high.toString()]],
+ to_block: 'pending',
+ chunk_size: 1000,
+ continuation_token: continuationToken,
+ });
+
+ if (tips.continuation_token) {
+ const next = await getClaimEvents(tips.continuation_token);
+ return [...tips.events, ...next];
+ }
+
+ return tips.events;
+ };
+
+ const claimEvents = (await getClaimEvents())
+ .map((event) => parseClaimEvent(event))
+ .filter((event): event is NonNullable> => !!event);
+
+ return tipEvents.map((tip) => ({
+ ...tip,
+ claimed: claimEvents.findIndex((claim) => claim.depositId === tip.depositId) !== -1,
+ }));
},
- placeholderData: {pages: [], pageParams: []},
+ placeholderData: [],
});
};
diff --git a/JoyboyCommunity/src/hooks/useWaitConnection.ts b/JoyboyCommunity/src/hooks/useWaitConnection.ts
index 6bd93088..f662b859 100644
--- a/JoyboyCommunity/src/hooks/useWaitConnection.ts
+++ b/JoyboyCommunity/src/hooks/useWaitConnection.ts
@@ -9,7 +9,7 @@ export const useWaitConnection = () => {
useEffect(() => {
if (account.address && promise.current) {
- promiseResolve.current(account);
+ promiseResolve.current?.(account);
promise.current = undefined;
promiseResolve.current = undefined;
@@ -23,7 +23,7 @@ export const useWaitConnection = () => {
if (promise.current) {
// If a promise is already in progress, resolve it with false
- promiseResolve.current(false);
+ promiseResolve.current?.(false);
promise.current = undefined;
promiseResolve.current = undefined;
diff --git a/JoyboyCommunity/src/hooks/useWindowDimensions.ts b/JoyboyCommunity/src/hooks/useWindowDimensions.ts
new file mode 100644
index 00000000..dc72a67f
--- /dev/null
+++ b/JoyboyCommunity/src/hooks/useWindowDimensions.ts
@@ -0,0 +1,11 @@
+import {Platform, useWindowDimensions as useRNWindowDimensions} from 'react-native';
+
+import {WEB_MAX_WIDTH} from '../constants/misc';
+
+export const useWindowDimensions = () => {
+ const dimensions = useRNWindowDimensions();
+
+ if (Platform.OS === 'web') dimensions.width = WEB_MAX_WIDTH;
+
+ return dimensions;
+};
diff --git a/JoyboyCommunity/src/modules/Post/index.tsx b/JoyboyCommunity/src/modules/Post/index.tsx
index 6a56ad2a..6f133c2f 100644
--- a/JoyboyCommunity/src/modules/Post/index.tsx
+++ b/JoyboyCommunity/src/modules/Post/index.tsx
@@ -1,5 +1,6 @@
import {NDKEvent} from '@nostr-dev-kit/ndk';
import {useNavigation} from '@react-navigation/native';
+import {useQueryClient} from '@tanstack/react-query';
import {useMemo, useState} from 'react';
import {Image, Pressable, View} from 'react-native';
import Animated, {
@@ -13,15 +14,8 @@ import Animated, {
import {CommentIcon, LikeFillIcon, LikeIcon, RepostIcon} from '../../assets/icons';
import {Avatar, IconButton, Menu, Text} from '../../components';
-import {
- useProfile,
- useReact,
- useReactions,
- useReplyNotes,
- useStyles,
- useTheme,
- useTipModal,
-} from '../../hooks';
+import {useProfile, useReact, useReactions, useReplyNotes, useStyles, useTheme} from '../../hooks';
+import {useTipModal} from '../../hooks/modals';
import {useAuth} from '../../store/auth';
import {MainStackNavigationProps} from '../../types';
import {getElapsedTimeStringFull} from '../../utils/timestamp';
@@ -48,6 +42,7 @@ export const Post: React.FC = ({asComment, event}) => {
const userReaction = useReactions({authors: [publicKey], noteId: event?.id});
const comments = useReplyNotes({noteId: event?.id});
const react = useReact();
+ const queryClient = useQueryClient();
const [menuOpen, setMenuOpen] = useState(false);
@@ -81,17 +76,19 @@ export const Post: React.FC = ({asComment, event}) => {
};
const handleNavigateToPostDetails = () => {
+ if (!event?.id) return;
navigation.navigate('PostDetail', {postId: event?.id, post: event});
};
/** @TODO comment in Nostr */
const toggleLike = async () => {
+ if (!event?.id) return;
+
await react.mutateAsync(
{event, type: isLiked ? 'dislike' : 'like'},
{
onSuccess: () => {
- reactions.refetch();
- userReaction.refetch();
+ queryClient.invalidateQueries({queryKey: ['reactions', event?.id]});
scale.value = withSequence(
withTiming(1.5, {duration: 100, easing: Easing.out(Easing.ease)}), // Scale up
@@ -111,8 +108,6 @@ export const Post: React.FC = ({asComment, event}) => {
)}
- {/* TODO: different rendering base on kind =1,6,7 and tags for kind = 1 */}
-
handleProfilePress(event?.pubkey)}>
@@ -143,7 +138,7 @@ export const Post: React.FC = ({asComment, event}) => {
)}
- {getElapsedTimeStringFull(event.created_at * 1000)}
+ {getElapsedTimeStringFull((event?.created_at ?? Date.now()) * 1000)}
@@ -171,7 +166,7 @@ export const Post: React.FC = ({asComment, event}) => {
- {repostedEvent?.content ?? event?.content}
+ {event?.content}
{postSource && (
@@ -206,6 +201,8 @@ export const Post: React.FC = ({asComment, event}) => {
label={profile?.username ? `Tip @${profile.username}` : 'Tip'}
icon="CoinIcon"
onPress={() => {
+ if (!event) return;
+
showTipModal(event);
setMenuOpen(false);
}}
diff --git a/JoyboyCommunity/src/modules/TipModal/index.tsx b/JoyboyCommunity/src/modules/TipModal/index.tsx
index b47501c0..002698ba 100644
--- a/JoyboyCommunity/src/modules/TipModal/index.tsx
+++ b/JoyboyCommunity/src/modules/TipModal/index.tsx
@@ -8,224 +8,225 @@ import {CallData, uint256} from 'starknet';
import {Avatar, Button, Input, Modalize, Picker, Text} from '../../components';
import {ESCROW_ADDRESSES} from '../../constants/contracts';
+import {CHAIN_ID} from '../../constants/env';
import {DEFAULT_TIMELOCK, Entrypoint} from '../../constants/misc';
import {TOKENS, TokenSymbol} from '../../constants/tokens';
-import {
- useChainId,
- useDialog,
- useProfile,
- useStyles,
- useTipModal,
- useTransaction,
- useWaitConnection,
- useWalletModal,
-} from '../../hooks';
+import {useProfile, useStyles, useWaitConnection} from '../../hooks';
+import {useDialog} from '../../hooks/modals/useDialog';
+import {useTransaction} from '../../hooks/modals/useTransaction';
+import {useWalletModal} from '../../hooks/modals/useWalletModal';
import {decimalsScale} from '../../utils/helpers';
+import {TipSuccessModalProps} from '../TipSuccessModal';
import stylesheet from './styles';
export type TipModal = Modalize;
export type TipModalProps = {
event?: NDKEvent;
-};
-export const TipModal = forwardRef(({event}, ref) => {
- const styles = useStyles(stylesheet);
+ show: (event: NDKEvent) => void;
+ hide: () => void;
+ showSuccess: (props: TipSuccessModalProps) => void;
+ hideSuccess: () => void;
+};
- const [token, setToken] = useState(TokenSymbol.ETH);
- const [amount, setAmount] = useState('');
+export const TipModal = forwardRef(
+ ({event, hide: hideTipModal, showSuccess, hideSuccess}, ref) => {
+ const styles = useStyles(stylesheet);
- const {data: profile} = useProfile({publicKey: event?.pubkey});
+ const [token, setToken] = useState(TokenSymbol.ETH);
+ const [amount, setAmount] = useState('');
- const chainId = useChainId();
- const account = useAccount();
- const walletModal = useWalletModal();
- const sendTransaction = useTransaction();
- const waitConnection = useWaitConnection();
+ const {data: profile} = useProfile({publicKey: event?.pubkey});
- const {hide: hideTipModal, showSuccess, hideSuccess} = useTipModal();
- const {showDialog, hideDialog} = useDialog();
+ const account = useAccount();
+ const walletModal = useWalletModal();
+ const sendTransaction = useTransaction();
+ const waitConnection = useWaitConnection();
- const isActive = !!amount && !!token;
+ const {showDialog, hideDialog} = useDialog();
- const onTipPress = async () => {
- if (!account.address) {
- walletModal.show();
+ const isActive = !!amount && !!token;
- const result = await waitConnection();
- if (!result) return;
- }
+ const onTipPress = async () => {
+ if (!account.address) {
+ walletModal.show();
- const amountUint256 = uint256.bnToUint256(
- new Fraction(1, Math.ceil(1 / Number(amount)))
- .multiply(decimalsScale(TOKENS[token][chainId].decimals))
- .toFixed(0),
- );
-
- const approveCallData = CallData.compile([
- ESCROW_ADDRESSES[chainId], // Contract address
- amountUint256, // Amount
- ]);
-
- const depositCallData = CallData.compile([
- amountUint256, // Amount
- TOKENS[token][chainId].address, // Token address
- uint256.bnToUint256(`0x${event.pubkey}`), // Recipient nostr pubkey
- DEFAULT_TIMELOCK, // timelock
- ]);
-
- const receipt = await sendTransaction({
- calls: [
- {
- contractAddress: TOKENS[token][chainId].address,
- entrypoint: Entrypoint.APPROVE,
- calldata: approveCallData,
- },
- {
- contractAddress: ESCROW_ADDRESSES[chainId],
- entrypoint: Entrypoint.DEPOSIT,
- calldata: depositCallData,
- },
- ],
- });
-
- if (receipt?.isSuccess()) {
- hideTipModal();
- showSuccess({
- amount: Number(amount),
- symbol: token,
- user:
- (profile?.nip05 && `@${profile.nip05}`) ??
- profile?.displayName ??
- profile?.name ??
- event?.pubkey,
- hide: hideSuccess,
- });
- } else {
- let description = 'Please Try Again Later.';
- if (receipt.isRejected()) {
- description = receipt.transaction_failure_reason.error_message;
+ const result = await waitConnection();
+ if (!result) return;
}
- showDialog({
- title: 'Failed to send the tip',
- description,
- buttons: [{type: 'secondary', label: 'Close', onPress: () => hideDialog()}],
+ const amountUint256 = uint256.bnToUint256(
+ new Fraction(1, Math.ceil(1 / Number(amount)))
+ .multiply(decimalsScale(TOKENS[token][CHAIN_ID].decimals))
+ .toFixed(0),
+ );
+
+ const approveCallData = CallData.compile([
+ ESCROW_ADDRESSES[CHAIN_ID], // Contract address
+ amountUint256, // Amount
+ ]);
+
+ const depositCallData = CallData.compile([
+ amountUint256, // Amount
+ TOKENS[token][CHAIN_ID].address, // Token address
+ uint256.bnToUint256(`0x${event?.pubkey}`), // Recipient nostr pubkey
+ DEFAULT_TIMELOCK, // timelock
+ ]);
+
+ const receipt = await sendTransaction({
+ calls: [
+ {
+ contractAddress: TOKENS[token][CHAIN_ID].address,
+ entrypoint: Entrypoint.APPROVE,
+ calldata: approveCallData,
+ },
+ {
+ contractAddress: ESCROW_ADDRESSES[CHAIN_ID],
+ entrypoint: Entrypoint.DEPOSIT,
+ calldata: depositCallData,
+ },
+ ],
});
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
- {profile?.displayName ?? profile?.name ?? event?.pubkey}
-
- {profile?.nip05 && (
-
- @{profile?.nip05}
+ if (receipt?.isSuccess()) {
+ hideTipModal();
+ showSuccess({
+ amount: Number(amount),
+ symbol: token,
+ user:
+ (profile?.nip05 && `@${profile.nip05}`) ??
+ profile?.displayName ??
+ profile?.name ??
+ event?.pubkey,
+ hide: hideSuccess,
+ });
+ } else {
+ let description = 'Please Try Again Later.';
+ if (receipt?.isRejected()) {
+ description = receipt.transaction_failure_reason.error_message;
+ }
+
+ showDialog({
+ title: 'Failed to send the tip',
+ description,
+ buttons: [{type: 'secondary', label: 'Close', onPress: () => hideDialog()}],
+ });
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {profile?.displayName ?? profile?.name ?? event?.pubkey}
- )}
+
+ {profile?.nip05 && (
+
+ @{profile?.nip05}
+
+ )}
+
-
-
- 16 likes
+
+ 16 likes
+
-
-
- {event?.content}
-
-
-
-
-
- setToken(itemValue as TokenSymbol)}
+
- {Object.values(TOKENS).map((tkn) => (
-
- ))}
-
+ {event?.content}
+
+
+
+
+
+ setToken(itemValue as TokenSymbol)}
+ >
+ {Object.values(TOKENS).map((tkn) => (
+
+ ))}
+
+
+
+
-
-
+
+
+
+ Sending
+
-
-
-
- Sending
-
+ {amount.length > 0 && token.length > 0 ? (
+
+ {amount} {token}
+
+ ) : (
+
+ ...
+
+ )}
+
- {amount.length > 0 && token.length > 0 ? (
-
- {amount} {token}
+
+
+ to
- ) : (
-
- ...
+
+ {(profile?.nip05 && `@${profile.nip05}`) ??
+ profile?.displayName ??
+ profile?.name ??
+ event?.pubkey}
- )}
+
-
-
- to
-
-
- {(profile?.nip05 && `@${profile.nip05}`) ??
- profile?.displayName ??
- profile?.name ??
- event?.pubkey}
-
+
+
-
-
-
-
-
-
-
- Tip friends and support creators with your favorite tokens.
-
-
-
- );
-});
+
+
+ Tip friends and support creators with your favorite tokens.
+
+
+
+ );
+ },
+);
TipModal.displayName = 'TipModal';
diff --git a/JoyboyCommunity/src/modules/TipSuccessModal/index.tsx b/JoyboyCommunity/src/modules/TipSuccessModal/index.tsx
index 9de529d9..4dccb016 100644
--- a/JoyboyCommunity/src/modules/TipSuccessModal/index.tsx
+++ b/JoyboyCommunity/src/modules/TipSuccessModal/index.tsx
@@ -6,7 +6,7 @@ import {useStyles} from '../../hooks';
import stylesheet from './styles';
export type TipSuccessModalProps = {
- user: string;
+ user?: string;
symbol: string;
amount: number;
hide: () => void;
diff --git a/JoyboyCommunity/src/modules/WalletModal/index.tsx b/JoyboyCommunity/src/modules/WalletModal/index.tsx
index 87e012ba..952f1f5d 100644
--- a/JoyboyCommunity/src/modules/WalletModal/index.tsx
+++ b/JoyboyCommunity/src/modules/WalletModal/index.tsx
@@ -5,7 +5,8 @@ import {SvgXml} from 'react-native-svg';
import {Button, Modal, Text} from '../../components';
import {ARGENT_X_INSTALL_URL, BRAAVOS_INSTALL_URL} from '../../constants/urls';
-import {useDialog, useStyles, useTheme} from '../../hooks';
+import {useStyles, useTheme} from '../../hooks';
+import {useDialog} from '../../hooks/modals/useDialog';
import stylesheet from './styles';
export type WalletModalProps = {
@@ -26,49 +27,51 @@ export const WalletModal: React.FC = ({hide}) => {
- {connectors.map((connector) => (
- {
- if (
- (connector.id === 'argentX' || connector.id === 'braavos') &&
- !globalThis[`starknet_${connector.id}`]
- ) {
- showDialog({
- title: 'Wallet is not available',
- description: `${connector.name} is not available to use. Please install the wallet and try again.`,
- buttons: [
- {
- type: 'secondary',
- label: `Install ${connector.name}`,
- onPress: () => {
- if (connector.id === 'argentX') Linking.openURL(ARGENT_X_INSTALL_URL);
- if (connector.id === 'braavos') Linking.openURL(BRAAVOS_INSTALL_URL);
- hideDialog();
+ {connectors.map((connector) => {
+ const icon = connector.icon[theme.dark ? 'dark' : 'light'];
+
+ return (
+ {
+ if (
+ (connector.id === 'argentX' || connector.id === 'braavos') &&
+ !(globalThis as any)[`starknet_${connector.id}`]
+ ) {
+ showDialog({
+ title: 'Wallet is not available',
+ description: `${connector.name} is not available to use. Please install the wallet and try again.`,
+ buttons: [
+ {
+ type: 'secondary',
+ label: `Install ${connector.name}`,
+ onPress: () => {
+ if (connector.id === 'argentX') Linking.openURL(ARGENT_X_INSTALL_URL);
+ if (connector.id === 'braavos') Linking.openURL(BRAAVOS_INSTALL_URL);
+ hideDialog();
+ },
+ },
+ {
+ type: 'default',
+ label: 'Close',
+ onPress: hideDialog,
},
- },
- {
- type: 'default',
- label: 'Close',
- onPress: hideDialog,
- },
- ],
- });
+ ],
+ });
+ hide();
+ return;
+ }
+ connect({connector});
hide();
- return;
- }
- connect({connector});
- hide();
- }}
- style={styles.connector}
- >
- {Platform.OS !== 'web' && (
-
- )}
+ }}
+ style={styles.connector}
+ >
+ {Platform.OS !== 'web' && icon ? : null}
- {connector.name}
-
- ))}
+ {connector.name}
+
+ );
+ })}
-
- navigation.navigate('Login')}>Login
);
};
diff --git a/JoyboyCommunity/src/screens/Auth/Login.tsx b/JoyboyCommunity/src/screens/Auth/Login.tsx
index ae40bad0..055882d3 100644
--- a/JoyboyCommunity/src/screens/Auth/Login.tsx
+++ b/JoyboyCommunity/src/screens/Auth/Login.tsx
@@ -4,7 +4,8 @@ import {Platform} from 'react-native';
import {LockIcon} from '../../assets/icons';
import {Button, Input, TextButton} from '../../components';
-import {useTheme, useToast} from '../../hooks';
+import {useTheme} from '../../hooks';
+import {useDialog, useToast} from '../../hooks/modals';
import {Auth} from '../../modules/Auth';
import {useAuth} from '../../store/auth';
import {AuthLoginScreenProps} from '../../types';
@@ -19,8 +20,10 @@ export const Login: React.FC = ({navigation}) => {
const theme = useTheme();
const setAuth = useAuth((state) => state.setAuth);
- const [password, setPassword] = useState(null);
+ const [password, setPassword] = useState('');
+
const {showToast} = useToast();
+ const {showDialog, hideDialog} = useDialog();
useEffect(() => {
(async () => {
@@ -34,7 +37,7 @@ export const Login: React.FC = ({navigation}) => {
}, []);
const handleLogin = async () => {
- if (password?.length == 0 || !password) {
+ if (!password) {
showToast({type: 'error', title: 'Password is required'});
return;
}
@@ -57,6 +60,25 @@ export const Login: React.FC = ({navigation}) => {
setAuth(publicKey, privateKeyHex);
};
+ const handleCreateAccount = () => {
+ showDialog({
+ title: 'WARNING',
+ description:
+ 'Creating a new account will delete your current account. Are you sure you want to continue?',
+ buttons: [
+ {type: 'default', label: 'Cancel', onPress: hideDialog},
+ {
+ type: 'primary',
+ label: 'Continue',
+ onPress: () => {
+ navigation.navigate('CreateAccount');
+ hideDialog();
+ },
+ },
+ ],
+ });
+ };
+
return (
= ({navigation}) => {
Login
- navigation.navigate('CreateAccount')}>Create Account
+ Create Account
);
};
diff --git a/JoyboyCommunity/src/screens/Auth/SaveKeys.tsx b/JoyboyCommunity/src/screens/Auth/SaveKeys.tsx
index 77de378b..4f9f6337 100644
--- a/JoyboyCommunity/src/screens/Auth/SaveKeys.tsx
+++ b/JoyboyCommunity/src/screens/Auth/SaveKeys.tsx
@@ -4,7 +4,8 @@ import {TouchableOpacity, View} from 'react-native';
import {CopyIconStack} from '../../assets/icons';
import {InfoIcon} from '../../assets/icons';
import {Button, Input, Text} from '../../components';
-import {useStyles, useTheme, useToast} from '../../hooks';
+import {useStyles, useTheme} from '../../hooks';
+import {useToast} from '../../hooks/modals';
import {Auth} from '../../modules/Auth';
import {useAuth} from '../../store/auth';
import {AuthSaveKeysScreenProps} from '../../types';
diff --git a/JoyboyCommunity/src/screens/CreatePost/index.tsx b/JoyboyCommunity/src/screens/CreatePost/index.tsx
index 4f9a88ee..d68d40b2 100644
--- a/JoyboyCommunity/src/screens/CreatePost/index.tsx
+++ b/JoyboyCommunity/src/screens/CreatePost/index.tsx
@@ -1,21 +1,23 @@
-import {useNavigation} from '@react-navigation/native';
+import {useQueryClient} from '@tanstack/react-query';
import {useState} from 'react';
import {KeyboardAvoidingView, Pressable, TextInput, View} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
import {CopyIcon, GalleryIcon, GifIcon, SendIconContained} from '../../assets/icons';
import {TextButton} from '../../components';
-import {useSendNote, useStyles, useTheme, useToast} from '../../hooks';
+import {useSendNote, useStyles, useTheme} from '../../hooks';
+import {useToast} from '../../hooks/modals';
+import {CreatePostScreenProps} from '../../types';
import stylesheet from './styles';
-export const CreatePost: React.FC = () => {
- const navigation = useNavigation();
-
+export const CreatePost: React.FC = ({navigation}) => {
const theme = useTheme();
const styles = useStyles(stylesheet);
const [note, setNote] = useState();
+
const sendNote = useSendNote();
+ const queryClient = useQueryClient();
const {showToast} = useToast();
const handleSendNote = () => {
@@ -29,6 +31,8 @@ export const CreatePost: React.FC = () => {
{
onSuccess() {
showToast({type: 'success', title: 'Note sent successfully'});
+ queryClient.invalidateQueries({queryKey: ['rootNotes']});
+ navigation.goBack();
},
onError() {
showToast({
diff --git a/JoyboyCommunity/src/screens/EditProfile/index.tsx b/JoyboyCommunity/src/screens/EditProfile/index.tsx
index 64233456..d005e768 100644
--- a/JoyboyCommunity/src/screens/EditProfile/index.tsx
+++ b/JoyboyCommunity/src/screens/EditProfile/index.tsx
@@ -7,7 +7,8 @@ import {ScrollView, TouchableOpacity, View} from 'react-native';
import {CopyIconStack} from '../../assets/icons';
import {Button, SquareInput, Text} from '../../components';
-import {useEditProfile, useProfile, useStyles, useTheme, useToast} from '../../hooks';
+import {useEditProfile, useProfile, useStyles, useTheme} from '../../hooks';
+import {useToast} from '../../hooks/modals';
import {useAuth} from '../../store/auth';
import {EditProfileScreenProps} from '../../types';
import {ProfileHead} from '../Profile/Head';
@@ -80,12 +81,12 @@ export const EditProfile: React.FC = () => {
};
const initialFormValues: FormValues = {
- username: profile.data?.nip05,
- displayName: profile.data?.displayName ?? profile.data?.name,
- bio: profile.data?.about,
- telegram: profile.data?.telegram?.toString(),
- github: profile.data?.github?.toString(),
- twitter: profile.data?.twitter?.toString(),
+ username: profile.data?.nip05 ?? '',
+ displayName: profile.data?.displayName ?? profile.data?.name ?? '',
+ bio: profile.data?.about ?? '',
+ telegram: profile.data?.telegram?.toString() ?? '',
+ github: profile.data?.github?.toString() ?? '',
+ twitter: profile.data?.twitter?.toString() ?? '',
};
const onSubmitPress = () => {
@@ -121,12 +122,12 @@ export const EditProfile: React.FC = () => {
onProfilePhotoUpload={onProfilePhotoUpload}
onCoverPhotoUpload={onCoverPhotoUpload}
profilePhoto={
- (profilePhoto?.uri && {uri: profilePhoto.uri}) ||
- (profile.data?.image && {uri: profile.data?.image})
+ (profilePhoto?.uri ? {uri: profilePhoto.uri} : undefined) ||
+ (profile.data?.image ? {uri: profile.data?.image} : undefined)
}
coverPhoto={
- (coverPhoto?.uri && {uri: coverPhoto.uri}) ||
- (profile.data?.banner && {uri: profile.data?.banner})
+ (coverPhoto?.uri ? {uri: coverPhoto.uri} : undefined) ||
+ (profile.data?.banner ? {uri: profile.data?.banner} : undefined)
}
buttons={