diff --git a/app/(tabs)/history.tsx b/app/(tabs)/history.tsx index 052c0da..4b96e0a 100644 --- a/app/(tabs)/history.tsx +++ b/app/(tabs)/history.tsx @@ -77,6 +77,75 @@ const ListFooter = ({ return null; }; +/** + * Non-blocking banner shown when a refresh fails but cached transactions are + * still available. Keeps the list visible and offers an inline retry. + */ +const RefreshErrorBanner = ({ + message, + onRetry, + isRetrying, + colors, + styles, +}: { + message: string; + onRetry: () => void; + isRetrying: boolean; + colors: ThemeColors; + styles: ReturnType; +}) => ( + + {message} + + {isRetrying ? ( + + ) : ( + Retry + )} + + +); + +/** + * Full-screen error shown when a refresh fails and there is no cached activity + * to display. + */ +const ActivityErrorState = ({ + onRetry, + isRetrying, + colors, + styles, +}: { + onRetry: () => void; + isRetrying: boolean; + colors: ThemeColors; + styles: ReturnType; +}) => ( + + Couldn't load activity + + We couldn't refresh your activity right now. Please try again. + + + {isRetrying ? ( + + ) : ( + Retry + )} + + +); + /** * Shown when there are no transactions and the screen is not loading. */ @@ -248,6 +317,15 @@ export default function HistoryScreen() { onRetry={() => { refreshWalletData(); retry(); }} isRetrying={isLoading} /> + {error && transactions.length > 0 && ( + { refreshWalletData(); retry(); }} + isRetrying={isLoading} + colors={colors} + styles={styles} + /> + )} { refreshWalletData(); retry(); }} + isRetrying={isLoading} + colors={colors} + styles={styles} + /> + ) : !isLoading ? ( 0, + }} /> ); @@ -332,6 +424,49 @@ const createStyles = (colors: ThemeColors) => StyleSheet.create({ color: colors.textMuted, fontSize: 13, }, + refreshErrorBanner: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: colors.surface, + borderLeftWidth: 3, + borderLeftColor: colors.error, + borderRadius: RADIUS.md, + paddingHorizontal: SIZES.md, + paddingVertical: SIZES.sm, + marginBottom: SIZES.md, + }, + refreshErrorText: { + flex: 1, + color: colors.textSecondary, + fontSize: 14, + marginRight: SIZES.sm, + }, + refreshErrorRetryButton: { + backgroundColor: colors.error, + borderRadius: RADIUS.md, + paddingHorizontal: SIZES.md, + paddingVertical: SIZES.xs, + minWidth: 64, + alignItems: 'center', + }, + refreshErrorRetryText: { + color: colors.background, + fontSize: 14, + fontWeight: '600', + }, + fullScreenError: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + padding: SIZES.xl, + }, + fullScreenErrorTitle: { + color: colors.textSecondary, + fontSize: 18, + fontWeight: '600', + marginBottom: SIZES.sm, + sectionHeader: { paddingTop: SIZES.sm, paddingBottom: SIZES.xs, diff --git a/src/components/ErrorState.tsx b/src/components/ErrorState.tsx index 4784181..72dd4da 100644 --- a/src/components/ErrorState.tsx +++ b/src/components/ErrorState.tsx @@ -18,6 +18,7 @@ export interface ErrorStateProps { action?: ErrorStateAction; style?: StyleProp; testID?: string; + variant?: 'fullscreen' | 'banner'; } export const ErrorState: React.FC = ({ @@ -27,9 +28,10 @@ export const ErrorState: React.FC = ({ action, style, testID = 'error-state', + variant = 'fullscreen', }) => { const { colors } = useTheme(); - const styles = useMemo(() => createStyles(colors), [colors]); + const styles = useMemo(() => createStyles(colors, variant), [colors, variant]); return ( = ({ testID={testID} > - {icon ?? } + {icon ?? } + + + {title} + {message ? {message} : null} - {title} - {message ? {message} : null} {action ? (