Skip to content
Closed
25 changes: 25 additions & 0 deletions app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,27 @@ PODS:
- Yoga
- react-native-sqlite-storage (6.0.1):
- React-Core
- react-native-webview (13.16.0):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.10.14.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- React-nativeconfig (0.76.9)
- React-NativeModulesApple (0.76.9):
- glog
Expand Down Expand Up @@ -2183,6 +2204,7 @@ DEPENDENCIES:
- react-native-nfc-manager (from `../node_modules/react-native-nfc-manager`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-nativeconfig (from `../node_modules/react-native/ReactCommon`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
Expand Down Expand Up @@ -2355,6 +2377,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-safe-area-context"
react-native-sqlite-storage:
:path: "../node_modules/react-native-sqlite-storage"
react-native-webview:
:path: "../node_modules/react-native-webview"
React-nativeconfig:
:path: "../node_modules/react-native/ReactCommon"
React-NativeModulesApple:
Expand Down Expand Up @@ -2523,6 +2547,7 @@ SPEC CHECKSUMS:
react-native-nfc-manager: 66a00e5ddab9704efebe19d605b1b8afb0bb1bd7
react-native-safe-area-context: 90a89cb349c7f8168a707e6452288c2f665b9fd1
react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed
react-native-webview: 3f45e19f0ffc3701168768a6c37695e0f252410e
React-nativeconfig: 415626a63057638759bcc75e0a96e2e07771a479
React-NativeModulesApple: d33b55553c6957ff94835574636838d78121a1c6
React-perflogger: 72e653eb3aba9122f9e57cf012d22d2486f33358
Expand Down
1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"react-native-svg": "15.12.1",
"react-native-svg-web": "^1.0.9",
"react-native-web": "^0.19.0",
"react-native-webview": "^13.16.0",
"react-qr-barcode-scanner": "^2.1.8",
"socket.io-client": "^4.8.1",
"tamagui": "1.126.14",
Expand Down
91 changes: 91 additions & 0 deletions app/src/components/NavBar/WebViewNavBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { ExternalLink, X } from '@tamagui/lucide-icons';

import { Button, XStack } from '@selfxyz/mobile-sdk-alpha/components';

import { black } from '@/utils/colors';
import { dinot } from '@/utils/fonts';
import { buttonTap } from '@/utils/haptic';

export interface WebViewNavBarProps {
title?: string;
canGoBack?: boolean;
onBackPress: () => void;
onSharePress?: () => void;
onOpenExternalPress?: () => void;
isShareDisabled?: boolean;
isOpenExternalDisabled?: boolean;
}

export const WebViewNavBar: React.FC<WebViewNavBarProps> = ({
title,
onBackPress,
onOpenExternalPress,
isOpenExternalDisabled,
}) => {
const insets = useSafeAreaInsets();

return (
<XStack
paddingHorizontal={20}
paddingVertical={10}
paddingTop={insets.top + 10}
gap={14}
alignItems="center"
backgroundColor="white"
>
{/* Left: Close Button */}
<Button
unstyled
hitSlop={{ top: 20, bottom: 20, left: 20, right: 10 }}
icon={<X size={24} color={black} />}
onPress={() => {
buttonTap();
onBackPress();
}}
/>

{/* Center: Title */}
<XStack flex={1} alignItems="center" justifyContent="center">
<Text style={styles.title} numberOfLines={1}>
{title?.toUpperCase() || 'PAGE TITLE'}
</Text>
</XStack>

{/* Right: Open External Button */}
<Button
unstyled
disabled={isOpenExternalDisabled}
hitSlop={{ top: 20, bottom: 20, left: 10, right: 20 }}
icon={
<ExternalLink
size={24}
color={isOpenExternalDisabled ? black : black}
opacity={isOpenExternalDisabled ? 0.3 : 1}
/>
}
onPress={() => {
buttonTap();
onOpenExternalPress?.();
}}
/>
</XStack>
);
};

const styles = StyleSheet.create({
title: {
fontFamily: dinot,
fontSize: 15,
color: black,
letterSpacing: 0.6,
textAlign: 'center',
textTransform: 'uppercase',
},
});
86 changes: 86 additions & 0 deletions app/src/components/WebViewFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';
import { ArrowLeft, ArrowRight, RotateCcw } from '@tamagui/lucide-icons';

import { Button, XStack, YStack } from '@selfxyz/mobile-sdk-alpha/components';

import { black, slate50, slate400 } from '@/utils/colors';
import { buttonTap } from '@/utils/haptic';

export interface WebViewFooterProps {
canGoBack: boolean;
canGoForward: boolean;
onGoBack: () => void;
onGoForward: () => void;
onReload: () => void;
onOpenInBrowser: () => void;
}

const iconSize = 22;
const buttonSize = 36;

export const WebViewFooter: React.FC<WebViewFooterProps> = ({
canGoBack,
canGoForward,
onGoBack,
onGoForward,
onReload,
onOpenInBrowser: _onOpenInBrowser,
}) => {
const renderIconButton = (
key: string,
icon: React.ReactNode,
onPress: () => void,
disabled?: boolean,
) => (
<Button
key={key}
size="$4"
unstyled
disabled={disabled}
onPress={() => {
buttonTap();
onPress();
}}
backgroundColor={slate50}
borderRadius={buttonSize / 2}
width={buttonSize}
height={buttonSize}
alignItems="center"
justifyContent="center"
opacity={disabled ? 0.5 : 1}
>
{icon}
</Button>
);

return (
<YStack gap={12} paddingVertical={12} width="100%">
<XStack justifyContent="space-between" alignItems="center" width="100%">
{renderIconButton(
'back',
<ArrowLeft size={iconSize} color={canGoBack ? black : slate400} />,
onGoBack,
!canGoBack,
)}
{renderIconButton(
'reload',
<RotateCcw size={iconSize} color={black} />,
onReload,
)}
{renderIconButton(
'forward',
<ArrowRight
size={iconSize}
color={canGoForward ? black : slate400}
/>,
onGoForward,
!canGoForward,
)}
</XStack>
</YStack>
);
};
3 changes: 3 additions & 0 deletions app/src/navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import homeScreens from '@/navigation/home';
import onboardingScreens from '@/navigation/onboarding';
import sharedScreens from '@/navigation/shared';
import verificationScreens from '@/navigation/verification';
import type { WebViewScreenParams } from '@/screens/shared/WebViewScreen';
import analytics from '@/utils/analytics';
import { setupUniversalLinkListenerInNavigation } from '@/utils/deeplinks';

Expand All @@ -38,6 +39,7 @@ export const navigationScreens = {
...sharedScreens,
...devScreens, // allow in production for testing
};

const AppNavigation = createNativeStackNavigator({
id: undefined,
initialRouteName: Platform.OS === 'web' ? 'Home' : 'Splash',
Expand Down Expand Up @@ -69,6 +71,7 @@ export type RootStackParamList = Omit<
AadhaarUploadError: {
errorType: string;
};
WebView: WebViewScreenParams;
};

export type RootStackScreenProps<T extends keyof RootStackParamList> =
Expand Down
31 changes: 27 additions & 4 deletions app/src/navigation/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,42 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import type { NativeStackNavigationOptions } from '@react-navigation/native-stack';
import type { ComponentType } from 'react';
import type {
NativeStackNavigationOptions,
NativeStackScreenProps,
} from '@react-navigation/native-stack';

import type { SharedRoutesParamList } from '@/navigation/types';
import ComingSoonScreen from '@/screens/shared/ComingSoonScreen';
import { WebViewScreen } from '@/screens/shared/WebViewScreen';

const sharedScreens = {
type ScreenName = keyof SharedRoutesParamList;

type ScreenConfig<Name extends ScreenName> = {
screen: ComponentType<NativeStackScreenProps<SharedRoutesParamList, Name>>;
options?: NativeStackNavigationOptions;
initialParams?: SharedRoutesParamList[Name];
};

const sharedScreens: { [K in ScreenName]: ScreenConfig<K> } = {
ComingSoon: {
screen: ComingSoonScreen,
options: {
headerShown: false,
} as NativeStackNavigationOptions,
},
WebView: {
screen: WebViewScreen,
options: {
headerShown: false,
} as NativeStackNavigationOptions,
initialParams: {
countryCode: null,
documentCategory: null,
url: 'https://self.xyz',
title: undefined,
shareTitle: undefined,
shareMessage: undefined,
shareUrl: undefined,
},
},
};
Expand Down
19 changes: 19 additions & 0 deletions app/src/navigation/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import type { DocumentCategory } from '@selfxyz/common/types';

export type SharedRoutesParamList = {
ComingSoon: {
countryCode?: string;
documentCategory?: DocumentCategory;
};
WebView: {
url: string;
title?: string;
shareTitle?: string;
shareMessage?: string;
shareUrl?: string;
};
};
18 changes: 9 additions & 9 deletions app/src/screens/account/settings/SettingsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ import Star from '@/images/icons/star.svg';
import Telegram from '@/images/icons/telegram.svg';
import Web from '@/images/icons/webpage.svg';
import X from '@/images/icons/x.svg';
import type { RootStackParamList } from '@/navigation';
import { useSettingStore } from '@/stores/settingStore';
import { amber500, black, neutral700, slate800, white } from '@/utils/colors';
import { extraYPadding } from '@/utils/constants';
import { impactLight } from '@/utils/haptic';
import { getCountry, getLocales, getTimeZone } from '@/utils/locale';

import { version } from '../../../../package.json';
// Avoid importing RootStackParamList to prevent type cycles; use minimal typing
type MinimalRootStackParamList = Record<string, object | undefined>;

interface MenuButtonProps extends PropsWithChildren {
Icon: React.FC<SvgProps>;
Expand All @@ -52,11 +53,8 @@ interface SocialButtonProps {
}

const emailFeedback = '[email protected]';
type RouteOption =
| keyof RootStackParamList
| 'share'
| 'email_feedback'
| 'ManageDocuments';
// Avoid importing RootStackParamList; we only need string route names plus a few literals
type RouteOption = string | 'share' | 'email_feedback' | 'ManageDocuments';

const storeURL = Platform.OS === 'ios' ? appStoreUrl : playStoreUrl;

Expand Down Expand Up @@ -144,7 +142,7 @@ const SocialButton: React.FC<SocialButtonProps> = ({ Icon, href }) => {
const SettingsScreen: React.FC = () => {
const { isDevMode, setDevModeOn } = useSettingStore();
const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
useNavigation<NativeStackNavigationProp<MinimalRootStackParamList>>();

const screenRoutes = useMemo(() => {
return isDevMode ? [...routes, ...DEBUG_MENU] : routes;
Expand Down Expand Up @@ -230,9 +228,11 @@ ${deviceInfo.map(([k, v]) => `${k}=${v}`).join('; ')}
justifyContent="flex-start"
width="100%"
>
{screenRoutes.map(([Icon, menuText, menuRoute]) => (
{screenRoutes.map(([Icon, menuText, menuRoute], idx) => (
<MenuButton
key={menuRoute}
key={
typeof menuRoute === 'string' ? menuRoute : String(idx)
}
Icon={Icon}
onPress={onMenuPress(menuRoute)}
>
Expand Down
Loading
Loading