diff --git a/.husky/pre-commit b/.husky/pre-commit index 2312dc58..5ee7abd8 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -npx lint-staged +pnpm exec lint-staged diff --git a/src/components/subscription/SubscriptionCard.tsx b/src/components/subscription/SubscriptionCard.tsx index d5b24ea1..b8e6dc07 100644 --- a/src/components/subscription/SubscriptionCard.tsx +++ b/src/components/subscription/SubscriptionCard.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, Text, StyleSheet, TouchableOpacity, Alert } from 'react-native'; +import { View, Text, StyleSheet, TouchableOpacity, Alert, Share } from 'react-native'; import { colors, spacing, typography, borderRadius, shadows } from '../../utils/constants'; import { Subscription } from '../../types/subscription'; import { @@ -18,7 +18,6 @@ import { useSettingsStore } from '../../store/settingsStore'; import { currencyService } from '../../services/currencyService'; import { SubscriptionIcon } from './SubscriptionIcon'; - export interface SubscriptionCardProps { subscription: Subscription; onPress: (subscription: Subscription) => void; @@ -28,6 +27,25 @@ export interface SubscriptionCardProps { export const SubscriptionCard: React.FC = React.memo( ({ subscription, onPress, onToggleStatus, onDelete }) => { + const handleShare = async () => { + const billingCycle = + subscription.billingCycle.charAt(0).toUpperCase() + subscription.billingCycle.slice(1); + const price = formatCurrency(subscription.price, subscription.currency); + const deepLink = `subtrackr://subscription/${subscription.id}`; + + const message = + `📋 ${subscription.name}\n` + `💰 ${price} / ${billingCycle}\n` + `🔗 ${deepLink}`; + + try { + const result = await Share.share({ message, title: subscription.name }); + if (result.action === Share.dismissedAction) { + // User dismissed — no action needed + } + } catch { + Alert.alert('Error', 'Unable to open the share sheet. Please try again.'); + } + }; + const handleToggleStatus = () => { if (onToggleStatus) { Alert.alert( @@ -68,7 +86,6 @@ export const SubscriptionCard: React.FC = React.memo( rates ); - return ( = React.memo( ]}> /{formatBillingCycle(subscription.billingCycle)} - @@ -168,6 +184,15 @@ export const SubscriptionCard: React.FC = React.memo( )} + + Share + {onToggleStatus && ( = React.memo( testID={`subscription-toggle-${subscription.id}`} accessibilityRole="button" accessibilityLabel={ - subscription.isActive ? `Pause ${subscription.name}` : `Activate ${subscription.name}` + subscription.isActive + ? `Pause ${subscription.name}` + : `Activate ${subscription.name}` }> {subscription.isActive ? 'Pause' : 'Activate'} @@ -324,6 +351,20 @@ const styles = StyleSheet.create({ justifyContent: 'flex-end', gap: spacing.sm, }, + shareButton: { + paddingVertical: spacing.sm, + paddingHorizontal: spacing.md, + backgroundColor: colors.surface, + borderRadius: borderRadius.md, + borderWidth: 1, + borderColor: colors.primary, + marginRight: 'auto', + }, + shareText: { + ...typography.caption, + color: colors.primary, + fontWeight: '500', + }, deleteButton: { paddingVertical: spacing.sm, paddingHorizontal: spacing.md, diff --git a/src/screens/SubscriptionDetailScreen.tsx b/src/screens/SubscriptionDetailScreen.tsx index 1ed32d5b..38cb67a6 100644 --- a/src/screens/SubscriptionDetailScreen.tsx +++ b/src/screens/SubscriptionDetailScreen.tsx @@ -9,6 +9,7 @@ import { Alert, ActivityIndicator, Switch, + Share, } from 'react-native'; import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -57,6 +58,26 @@ const SubscriptionDetailScreen: React.FC = () => { navigation.navigate('EditSubscription', { id: subscription.id }); }, [subscription, navigation]); + const handleShare = useCallback(async () => { + if (!subscription) return; + const billingCycle = + subscription.billingCycle.charAt(0).toUpperCase() + subscription.billingCycle.slice(1); + const price = formatCurrency(subscription.price, subscription.currency); + const deepLink = `subtrackr://subscription/${subscription.id}`; + + const message = + `📋 ${subscription.name}\n` + `💰 ${price} / ${billingCycle}\n` + `🔗 ${deepLink}`; + + try { + const result = await Share.share({ message, title: subscription.name }); + if (result.action === Share.dismissedAction) { + // User dismissed — no action needed + } + } catch { + Alert.alert('Error', 'Unable to open the share sheet. Please try again.'); + } + }, [subscription]); + const handlePauseResume = useCallback(async () => { if (!subscription) return; try { @@ -122,14 +143,24 @@ const SubscriptionDetailScreen: React.FC = () => { Subscription Details - - Edit - + + + Share + + + Edit + + {/* Main Info Card */} @@ -353,6 +384,20 @@ const styles = StyleSheet.create({ color: colors.primary, fontWeight: '500', }, + headerActions: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.xs, + }, + headerActionButton: { + padding: spacing.sm, + alignItems: 'center', + }, + shareButtonText: { + ...typography.body, + color: colors.primary, + fontWeight: '500', + }, backIcon: { padding: spacing.sm, },