Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 47 additions & 12 deletions app/src/components/Mnemonic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React, { useCallback, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';

Check failure on line 5 in app/src/components/Mnemonic.tsx

View workflow job for this annotation

GitHub Actions / build-deps

Run autofix to sort these imports!

Check failure on line 5 in app/src/components/Mnemonic.tsx

View workflow job for this annotation

GitHub Actions / workspace-lint

Run autofix to sort these imports!
import { Button, Text, XStack, YStack } from 'tamagui';
import Clipboard from '@react-native-clipboard/clipboard';

import { useSettingStore } from '@/stores/settingStore';
import useCompactLayout from '@/hooks/useCompactLayout';
import {
black,
slate50,
Expand All @@ -25,23 +26,24 @@
interface WordPill {
index: number;
word: string;
compact: boolean;
}
const WordPill = ({ index, word }: WordPill) => {
const WordPill = ({ index, word, compact }: WordPill) => {
return (
<XStack
key={index}
borderColor={slate300}
backgroundColor={white}
borderWidth="$0.5"
borderRadius="$2"
padding={4}
minWidth={26}
gap={4}
padding={compact ? 3 : 4}
minWidth={compact ? 22 : 26}
gap={compact ? 3 : 4}
>
<Text color={slate300} fontSize={14} fontWeight={500}>
<Text color={slate300} fontSize={compact ? 13 : 14} fontWeight={500}>
{index}
</Text>
<Text color={slate500} fontSize={14} fontWeight={500}>
<Text color={slate500} fontSize={compact ? 13 : 14} fontWeight={500}>
{word}
</Text>
Comment on lines 44 to 48
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix mnemonic numbering regression

Line 44 renders {index}, but the list now passes index={i} (Lines 115-121), so the first pill shows 0 instead of 1. That’s a user-visible correctness bug. Display index + 1 (or pass i + 1) before rendering.

-      <Text color={slate300} fontSize={compact ? 13 : 14} fontWeight={500}>
-        {index}
+      <Text color={slate300} fontSize={compact ? 13 : 14} fontWeight={500}>
+        {index + 1}

Also applies to: 115-121

🤖 Prompt for AI Agents
In app/src/components/Mnemonic.tsx around lines 44 to 48 (and similarly where
the list passes index at lines 115-121), the component renders the zero-based
`index` which displays "0" for the first mnemonic; change the displayed value to
`index + 1` (or alternatively pass `i + 1` from the parent) so numbering starts
at 1, and update the list invocation at lines 115-121 to pass a 1-based index if
you choose that approach.

</XStack>
Expand All @@ -54,6 +56,34 @@
const [revealWords, setRevealWords] = useState(false);
const [copied, setCopied] = useState(false);
const { setHasViewedRecoveryPhrase } = useSettingStore();
const { isCompactWidth, selectResponsiveValues } = useCompactLayout();
const {
containerPaddingHorizontal,
containerPaddingVertical,
containerGap,
buttonPadding,
} = selectResponsiveValues({
containerPaddingHorizontal: {
compact: 18,
regular: 26,
dimension: 'width',
},
containerPaddingVertical: {
compact: 22,
regular: 28,
dimension: 'width',
},
containerGap: { compact: 10, regular: 12, dimension: 'width' },
buttonPadding: { compact: 12, regular: 16, dimension: 'width' },
});
const containerSpacing = useMemo(
() => ({
paddingHorizontal: containerPaddingHorizontal,
paddingVertical: containerPaddingVertical,
gap: containerGap,
}),
[containerGap, containerPaddingHorizontal, containerPaddingVertical],
);
const copyToClipboardOrReveal = useCallback(async () => {
confirmTap();
if (!revealWords) {
Expand All @@ -76,13 +106,18 @@
borderBottomWidth={0}
borderTopLeftRadius="$5"
borderTopRightRadius="$5"
gap={12}
paddingHorizontal={26}
paddingVertical={28}
gap={containerSpacing.gap}
paddingHorizontal={containerSpacing.paddingHorizontal}
paddingVertical={containerSpacing.paddingVertical}
flexWrap="wrap"
>
{(revealWords ? words : REDACTED).map((word, i) => (
<WordPill key={i} word={word} index={i} />
<WordPill

Check warning on line 115 in app/src/components/Mnemonic.tsx

View workflow job for this annotation

GitHub Actions / build-deps

Replace `⏎············key={i}⏎············word={word}⏎············index={i}⏎············compact={isCompactWidth}⏎·········` with `·key={i}·word={word}·index={i}·compact={isCompactWidth}`

Check warning on line 115 in app/src/components/Mnemonic.tsx

View workflow job for this annotation

GitHub Actions / workspace-lint

Replace `⏎············key={i}⏎············word={word}⏎············index={i}⏎············compact={isCompactWidth}⏎·········` with `·key={i}·word={word}·index={i}·compact={isCompactWidth}`
key={i}
word={word}
index={i}
compact={isCompactWidth}
/>
))}
</XStack>
<XStack
Expand All @@ -100,7 +135,7 @@
borderTopWidth={0}
borderBottomLeftRadius="$5"
borderBottomRightRadius="$5"
paddingVertical={16}
paddingVertical={buttonPadding}
onPress={copyToClipboardOrReveal}
width="100%"
textAlign="center"
Expand Down
122 changes: 122 additions & 0 deletions app/src/hooks/useCompactLayout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 { useCallback } from 'react';
import { useWindowDimensions } from 'react-native';

export const DEFAULT_COMPACT_WIDTH = 360;
export const DEFAULT_COMPACT_HEIGHT = 720;

Check failure on line 9 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / build-deps

Expected DEFAULT_COMPACT_HEIGHT before DEFAULT_COMPACT_WIDTH

Check failure on line 9 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / workspace-lint

Expected DEFAULT_COMPACT_HEIGHT before DEFAULT_COMPACT_WIDTH

interface UseCompactLayoutOptions {
compactWidth?: number;
compactHeight?: number;
}

type ResponsiveDimension = 'width' | 'height' | 'any';

interface ResponsivePaddingOptions {
min?: number;
max?: number;
percent?: number;
}

type ResponsiveValueConfig<T> = {
compact: T;
regular: T;
dimension?: ResponsiveDimension;
};

const useCompactLayout = (
options: UseCompactLayoutOptions = {},
): {
width: number;
height: number;
isCompactWidth: boolean;
isCompactHeight: boolean;
isCompact: boolean;
selectResponsiveValue: <T>(
compactValue: T,
regularValue: T,
dimension?: ResponsiveDimension,
) => T;
selectResponsiveValues: <TConfig extends Record<string, ResponsiveValueConfig<unknown>>>(

Check warning on line 43 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / build-deps

Replace `TConfig·extends·Record<string,·ResponsiveValueConfig<unknown>>` with `⏎····TConfig·extends·Record<string,·ResponsiveValueConfig<unknown>>,⏎··`

Check warning on line 43 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / workspace-lint

Replace `TConfig·extends·Record<string,·ResponsiveValueConfig<unknown>>` with `⏎····TConfig·extends·Record<string,·ResponsiveValueConfig<unknown>>,⏎··`
config: TConfig,
) => {
[K in keyof TConfig]: TConfig[K] extends ResponsiveValueConfig<infer TValue>
? TValue
: never;
};
getResponsiveHorizontalPadding: (options?: ResponsivePaddingOptions) => number;

Check warning on line 50 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / build-deps

Replace `options?:·ResponsivePaddingOptions` with `⏎····options?:·ResponsivePaddingOptions,⏎··`

Check warning on line 50 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / workspace-lint

Replace `options?:·ResponsivePaddingOptions` with `⏎····options?:·ResponsivePaddingOptions,⏎··`
} => {
const { width, height } = useWindowDimensions();
const compactWidth = options.compactWidth ?? DEFAULT_COMPACT_WIDTH;
const compactHeight = options.compactHeight ?? DEFAULT_COMPACT_HEIGHT;

const isCompactWidth = width < compactWidth;
const isCompactHeight = height < compactHeight;
const selectResponsiveValue = useCallback(
<T>(
compactValue: T,
regularValue: T,
dimension: ResponsiveDimension = 'any',
): T => {
if (dimension === 'width') {
return isCompactWidth ? compactValue : regularValue;
}

if (dimension === 'height') {
return isCompactHeight ? compactValue : regularValue;
}

return isCompactWidth || isCompactHeight ? compactValue : regularValue;
},
[isCompactHeight, isCompactWidth],
);

const selectResponsiveValues = useCallback(
<TConfig extends Record<string, ResponsiveValueConfig<unknown>>>(
config: TConfig,
) => {
const entries = Object.entries(config) as Array<[

Check warning on line 81 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / build-deps

Delete `[`

Check warning on line 81 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / workspace-lint

Delete `[`
keyof TConfig,

Check warning on line 82 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / build-deps

Replace `keyof·TConfig,⏎········ResponsiveValueConfig<unknown>,` with `[keyof·TConfig,·ResponsiveValueConfig<unknown>]`

Check warning on line 82 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / workspace-lint

Replace `keyof·TConfig,⏎········ResponsiveValueConfig<unknown>,` with `[keyof·TConfig,·ResponsiveValueConfig<unknown>]`
ResponsiveValueConfig<unknown>,
]>;

Check warning on line 84 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / build-deps

Delete `]`

Check warning on line 84 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / workspace-lint

Delete `]`
const result = {} as {
[K in keyof TConfig]: TConfig[K] extends ResponsiveValueConfig<infer TValue>

Check warning on line 86 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / build-deps

Replace `infer·TValue` with `⏎··········infer·TValue⏎········`

Check warning on line 86 in app/src/hooks/useCompactLayout.ts

View workflow job for this annotation

GitHub Actions / workspace-lint

Replace `infer·TValue` with `⏎··········infer·TValue⏎········`
? TValue
: never;
};

entries.forEach(([key, { compact, regular, dimension }]) => {
result[key] = selectResponsiveValue(compact, regular, dimension);
});

return result;
},
[selectResponsiveValue],
);

const getResponsiveHorizontalPadding = useCallback(
(paddingOptions: ResponsivePaddingOptions = {}): number => {
const { min = 16, max, percent = 0.06 } = paddingOptions;
const computed = width * percent;
const withMin = Math.max(min, computed);
return typeof max === 'number' ? Math.min(max, withMin) : withMin;
},
[width],
);

return {
width,
height,
isCompactWidth,
isCompactHeight,
isCompact: isCompactWidth || isCompactHeight,
selectResponsiveValue,
selectResponsiveValues,
getResponsiveHorizontalPadding,
};
};

export default useCompactLayout;
29 changes: 26 additions & 3 deletions app/src/layouts/ExpandableBottomLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';
import React, { useMemo } from 'react';

Check failure on line 5 in app/src/layouts/ExpandableBottomLayout.tsx

View workflow job for this annotation

GitHub Actions / build-deps

Run autofix to sort these imports!

Check failure on line 5 in app/src/layouts/ExpandableBottomLayout.tsx

View workflow job for this annotation

GitHub Actions / workspace-lint

Run autofix to sort these imports!
import {
Dimensions,
PixelRatio,
Expand All @@ -17,6 +17,7 @@

import { black, white } from '@/utils/colors';
import { extraYPadding } from '@/utils/constants';
import useCompactLayout from '@/hooks/useCompactLayout';

// Get the current font scale factor
const fontScale = PixelRatio.getFontScale();
Expand Down Expand Up @@ -57,7 +58,17 @@
...props
}) => {
const { top } = useSafeAreaInsets();
const { roundTop, ...restProps } = props;
const { roundTop, style: incomingStyle, ...restProps } = props;
const { selectResponsiveValue } = useCompactLayout({
compactHeight: 760,
});
const spacingStyle = useMemo(
() => ({
paddingHorizontal: selectResponsiveValue(16, 20, 'width'),
paddingVertical: selectResponsiveValue(16, 20, 'height'),
}),
[selectResponsiveValue],
);
return (
<View
{...restProps}
Expand All @@ -66,7 +77,9 @@
styles.topSection,
roundTop && styles.roundTop,
roundTop ? { marginTop: top } : { paddingTop: top },
spacingStyle,
Comment on lines 77 to +80

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve safe-area padding in top sections

The new responsive padding in TopSection is applied after the safe-area padding (paddingTop: top). Because spacingStyle sets paddingVertical, it overwrites paddingTop, so screens that render ExpandableBottomLayout.TopSection without roundTop no longer include the safe area inset and their content will be rendered under the status bar on devices with a notch. Consider combining the safe-area value into the responsive padding instead of replacing it.

Useful? React with 👍 / 👎.

{ backgroundColor },
incomingStyle,
]}
>
{children}
Expand Down Expand Up @@ -108,6 +121,16 @@
const minBottom = safeAreaBottom + extraYPadding;
const totalBottom =
typeof incomingBottom === 'number' ? minBottom + incomingBottom : minBottom;
const { selectResponsiveValue } = useCompactLayout({
compactHeight: 760,
});
const spacingStyle = useMemo(
() => ({
paddingHorizontal: selectResponsiveValue(16, 20, 'width'),
paddingTop: selectResponsiveValue(18, 30, 'height'),
}),
[selectResponsiveValue],
);

let panelHeight: number | 'auto' = 'auto';
// set bottom section height to 38% of screen height
Expand All @@ -129,7 +152,7 @@
<View
{...props}
height={panelHeight}
style={[styles.bottomSection, style]}
style={[styles.bottomSection, spacingStyle, style]}
paddingBottom={totalBottom}
>
{children}
Expand Down
48 changes: 41 additions & 7 deletions app/src/screens/account/recovery/AccountRecoveryScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import React from 'react';

Check failure on line 5 in app/src/screens/account/recovery/AccountRecoveryScreen.tsx

View workflow job for this annotation

GitHub Actions / build-deps

Run autofix to sort these imports!

Check failure on line 5 in app/src/screens/account/recovery/AccountRecoveryScreen.tsx

View workflow job for this annotation

GitHub Actions / workspace-lint

Run autofix to sort these imports!
import { View, YStack } from 'tamagui';

import {
Expand All @@ -12,10 +12,12 @@
Title,
} from '@selfxyz/mobile-sdk-alpha/components';
import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import useHapticNavigation from '@/hooks/useHapticNavigation';
import RestoreAccountSvg from '@/images/icons/restore_account.svg';
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
import useCompactLayout from '@/hooks/useCompactLayout';
import { black, slate600, white } from '@/utils/colors';

const AccountRecoveryScreen: React.FC = () => {
Expand All @@ -25,6 +27,30 @@
nextScreen: 'SaveRecoveryPhrase',
},
});
const { selectResponsiveValues, getResponsiveHorizontalPadding } = useCompactLayout();

Check warning on line 30 in app/src/screens/account/recovery/AccountRecoveryScreen.tsx

View workflow job for this annotation

GitHub Actions / build-deps

Insert `⏎···`

Check warning on line 30 in app/src/screens/account/recovery/AccountRecoveryScreen.tsx

View workflow job for this annotation

GitHub Actions / workspace-lint

Insert `⏎···`
const { bottom } = useSafeAreaInsets();

const {
iconSize,
iconPadding,
contentGap,
descriptionSize,
titleSize,
buttonStackGap,
buttonPaddingTop,
extraBottomPadding,
} = selectResponsiveValues({
iconSize: { compact: 64, regular: 80 },
iconPadding: { compact: '$4', regular: '$5' },
contentGap: { compact: '$2', regular: '$2.5' },
descriptionSize: { compact: 15, regular: 16 },
titleSize: { compact: 26, regular: 32 },
buttonStackGap: { compact: '$2', regular: '$2.5' },
buttonPaddingTop: { compact: '$4', regular: '$6' },
extraBottomPadding: { compact: 16, regular: 24 },
});
const horizontalPadding = getResponsiveHorizontalPadding({ percent: 0.06 });
const bottomPadding = bottom + extraBottomPadding;

return (
<ExpandableBottomLayout.Layout backgroundColor={black}>
Expand All @@ -33,20 +59,28 @@
borderColor={slate600}
borderWidth="$1"
borderRadius="$10"
padding="$5"
padding={iconPadding}
>
<RestoreAccountSvg height={80} width={80} color={white} />
<RestoreAccountSvg height={iconSize} width={iconSize} color={white} />
</View>
</ExpandableBottomLayout.TopSection>
<ExpandableBottomLayout.BottomSection backgroundColor={white}>
<YStack alignItems="center" gap="$2.5" paddingBottom="$2.5">
<Title>Restore your Self account</Title>
<Description>
<ExpandableBottomLayout.BottomSection
backgroundColor={white}
paddingBottom={bottomPadding}
paddingHorizontal={horizontalPadding}
>
<YStack alignItems="center" gap={contentGap} paddingBottom="$2">
<Title style={{ fontSize: titleSize, textAlign: 'center' }}>
Restore your Self account
</Title>
<Description
style={{ fontSize: descriptionSize, textAlign: 'center' }}
>
By continuing, you certify that this passport belongs to you and is
not stolen or forged.
</Description>

<YStack gap="$2.5" width="100%" paddingTop="$6">
<YStack gap={buttonStackGap} width="100%" paddingTop={buttonPaddingTop}>

Check warning on line 83 in app/src/screens/account/recovery/AccountRecoveryScreen.tsx

View workflow job for this annotation

GitHub Actions / build-deps

Replace `·gap={buttonStackGap}·width="100%"·paddingTop={buttonPaddingTop}` with `⏎············gap={buttonStackGap}⏎············width="100%"⏎············paddingTop={buttonPaddingTop}⏎··········`

Check warning on line 83 in app/src/screens/account/recovery/AccountRecoveryScreen.tsx

View workflow job for this annotation

GitHub Actions / workspace-lint

Replace `·gap={buttonStackGap}·width="100%"·paddingTop={buttonPaddingTop}` with `⏎············gap={buttonStackGap}⏎············width="100%"⏎············paddingTop={buttonPaddingTop}⏎··········`
<PrimaryButton
trackEvent={BackupEvents.ACCOUNT_RECOVERY_STARTED}
onPress={onRestoreAccountPress}
Expand Down
Loading
Loading