feat(app):copy to clipboard and ui and logic modifications

This commit is contained in:
Atanu Das 2026-08-03 17:41:39 +05:30
parent 1aed769b62
commit 4331243da6
10 changed files with 115 additions and 85 deletions

View File

@ -3,6 +3,7 @@ import { View, Text, TouchableOpacity } from 'react-native';
import { HeaderProps } from './header.props'; import { HeaderProps } from './header.props';
import { getStyles } from './header.styles'; import { getStyles } from './header.styles';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
export const Header: React.FC<HeaderProps> = ({ title, onBack, rightComponent }) => { export const Header: React.FC<HeaderProps> = ({ title, onBack, rightComponent }) => {
const { colors } = useAppTheme(); const { colors } = useAppTheme();
@ -13,7 +14,7 @@ export const Header: React.FC<HeaderProps> = ({ title, onBack, rightComponent })
<View style={styles.backButton}> <View style={styles.backButton}>
{onBack && ( {onBack && (
<TouchableOpacity onPress={onBack} activeOpacity={0.7}> <TouchableOpacity onPress={onBack} activeOpacity={0.7}>
<Text style={styles.backText}>{'<'}</Text> <MaterialIcons name="arrow-back" size={24} color="black" />
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>

View File

@ -3,6 +3,7 @@ import { View, TextInput, Text } from 'react-native';
import { SearchBarProps } from './searchBar.props'; import { SearchBarProps } from './searchBar.props';
import { getStyles } from './searchBar.styles'; import { getStyles } from './searchBar.styles';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const SearchBar: React.FC<SearchBarProps> = ({ export const SearchBar: React.FC<SearchBarProps> = ({
value, value,
@ -16,7 +17,11 @@ export const SearchBar: React.FC<SearchBarProps> = ({
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.icon}>🔍</Text> <MaterialCommunityIcons
name="magnify"
size={24}
color={colors.primary}
/>
<TextInput <TextInput
style={styles.input} style={styles.input}
value={value} value={value}

View File

@ -14,6 +14,7 @@ import { AppStackParamList } from '../../../navigation/appStack';
import { MainTabParamList } from '../../../navigation/mainTabNavigator'; import { MainTabParamList } from '../../../navigation/mainTabNavigator';
import { logoutUser, useAppDispatch, useAppSelector } from '@store'; import { logoutUser, useAppDispatch, useAppSelector } from '@store';
import { getWalletBalanceThunk } from './thunk'; import { getWalletBalanceThunk } from './thunk';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
type AccountNavProp = CompositeNavigationProp< type AccountNavProp = CompositeNavigationProp<
BottomTabNavigationProp<MainTabParamList, 'AccountScreen'>, BottomTabNavigationProp<MainTabParamList, 'AccountScreen'>,
@ -37,21 +38,15 @@ const MENU_SECTIONS: MenuSectionData[] = [
{ {
title: 'Account', title: 'Account',
items: [ items: [
// {
// icon: '📍',
// label: 'My Addresses',
// subLabel: 'Manage delivery addresses',
// tint: '#FDECEA',
// },
{ {
icon: '💳', icon: 'wallet-outline',
label: 'Wallet', label: 'Wallet',
subLabel: 'Cards, UPI & wallets', subLabel: 'Cards, UPI & wallets',
tint: '#FFF4E5', tint: '#FFF4E5',
onPress: navigation => navigation.navigate('WalletScreen'), onPress: navigation => navigation.navigate('WalletScreen'),
}, },
{ {
icon: '🔔', icon: 'bell-outline',
label: 'Notifications', label: 'Notifications',
subLabel: 'Alerts, offers & updates', subLabel: 'Alerts, offers & updates',
tint: '#FFF9DB', tint: '#FFF9DB',
@ -62,20 +57,20 @@ const MENU_SECTIONS: MenuSectionData[] = [
title: 'Support & Legal', title: 'Support & Legal',
items: [ items: [
{ {
icon: '', icon: 'help-circle-outline',
label: 'Help & Support', label: 'Help & Support',
subLabel: 'FAQs and contact us', subLabel: 'FAQs and contact us',
tint: '#EAF4FF', tint: '#EAF4FF',
onPress: navigation => navigation.navigate('HelpSupportScreen'), onPress: navigation => navigation.navigate('HelpSupportScreen'),
}, },
{ {
icon: '📋', icon: 'file-document-outline',
label: 'Terms & Conditions', label: 'Terms & Conditions',
subLabel: 'Our terms of service', subLabel: 'Our terms of service',
tint: '#F1F0FF', tint: '#F1F0FF',
}, },
{ {
icon: '🔒', icon: 'shield-lock-outline',
label: 'Privacy Policy', label: 'Privacy Policy',
subLabel: 'How we handle your data', subLabel: 'How we handle your data',
tint: '#E9F7EF', tint: '#E9F7EF',
@ -130,7 +125,11 @@ export const AccountScreen: React.FC = () => {
<Text style={styles.profileName}>{user?.name || 'User'}</Text> <Text style={styles.profileName}>{user?.name || 'User'}</Text>
<View style={styles.profilePhoneRow}> <View style={styles.profilePhoneRow}>
<Text style={styles.profilePhoneIcon}>📞</Text> <MaterialCommunityIcons
name="phone-outline"
size={20}
color={colors.primary}
/>
<Text style={styles.profilePhone}> <Text style={styles.profilePhone}>
{user?.phone || '+91 98765 43210'} {user?.phone || '+91 98765 43210'}
</Text> </Text>
@ -182,7 +181,11 @@ export const AccountScreen: React.FC = () => {
{ backgroundColor: item.tint }, { backgroundColor: item.tint },
]} ]}
> >
<Text style={styles.menuIcon}>{item.icon}</Text> <MaterialCommunityIcons
name={item.icon}
size={24}
color={colors.primary}
/>
</View> </View>
<View style={styles.menuTextWrap}> <View style={styles.menuTextWrap}>
<Text style={styles.menuLabel}>{item.label}</Text> <Text style={styles.menuLabel}>{item.label}</Text>

View File

@ -43,4 +43,9 @@ export const getStyles = (colors: any) => StyleSheet.create({
fontSize: typography.fontSize.md, fontSize: typography.fontSize.md,
color: colors.textSecondary, color: colors.textSecondary,
}, },
loaderContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
}); });

View File

@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity } from 'react-native'; import { View, Text, FlatList, TouchableOpacity, ActivityIndicator } from 'react-native';
import { import {
useNavigation, useNavigation,
CompositeNavigationProp, CompositeNavigationProp,
useFocusEffect,
} from '@react-navigation/native'; } from '@react-navigation/native';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
@ -54,14 +55,14 @@ export const MyOrdersScreen: React.FC = () => {
const navigation = useNavigation<MyOrdersNavProp>(); const navigation = useNavigation<MyOrdersNavProp>();
const [activeTab, setActiveTab] = useState('All'); const [activeTab, setActiveTab] = useState('All');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { orderHistory } = useAppSelector( const { orderHistory, isLoading } = useAppSelector(
(state: RootState) => state.paymentMethods, (state: RootState) => state.paymentMethods,
); );
// console.log(orderHistory); // console.log(orderHistory);
useEffect(() => { useFocusEffect(useCallback(() => {
dispatch(getOrderHistoryThunk()); dispatch(getOrderHistoryThunk());
}, []); }, []));
const ONGOING_STATUSES = [ const ONGOING_STATUSES = [
'PENDING', 'PENDING',
@ -103,54 +104,59 @@ export const MyOrdersScreen: React.FC = () => {
</TouchableOpacity> </TouchableOpacity>
))} ))}
</View> </View>
<FlatList {isLoading ? (
data={filteredOrders} <View style={styles.loaderContainer}>
keyExtractor={item => item.id} <ActivityIndicator size="large" color={colors.primary} />
renderItem={({ item }) => ( </View>
<OrderHistoryCard ) : (
providerName={item?.merchant?.name || ''} <FlatList
providerImage={item?.merchant?.imageUrl || ''} data={filteredOrders}
orderDate={formatDate(item?.createdAt)} keyExtractor={item => item.id}
status={item.status} renderItem={({ item }) => (
total={Number(item?.totalAmount) || 0} <OrderHistoryCard
items={item.orderItems} providerName={item?.merchant?.name || ''}
onTrack={() => { providerImage={item?.merchant?.imageUrl || ''}
if (item?.status === 'OUT_FOR_DELIVERY') { orderDate={formatDate(item?.createdAt)}
// navigation.navigate('ProviderDetailsScreen', { status={item.status}
// providerName: item?.merchant?.name || '', total={Number(item?.totalAmount) || 0}
// }); items={item.orderItems}
// } else { onTrack={() => {
navigation.navigate('LiveTrackingScreen', { if (item?.status === 'OUT_FOR_DELIVERY') {
orderId: item?.id || '', // navigation.navigate('ProviderDetailsScreen', {
// providerName: item?.merchant?.name || '',
// });
// } else {
navigation.navigate('LiveTrackingScreen', {
orderId: item?.id || '',
});
}
}}
onDetails={() => {
// if (
// item?.status === 'PENDING' ||
// item?.status === 'CONFIRMED' ||
// item?.status === 'PREPARING' ||
// item?.status === 'READY_FOR_PICKUP' ||
// item?.status === 'OUT_FOR_DELIVERY'
// ) {
navigation.navigate('OrderDetailsScreen', {
orderId: item.id,
}); });
} // } else {
}} // navigation.navigate('OrderDeliveredScreen', {
onDetails={() => { // orderId: item.id,
// if ( // });
// item?.status === 'PENDING' || // }
// item?.status === 'CONFIRMED' || }}
// item?.status === 'PREPARING' || />
// item?.status === 'READY_FOR_PICKUP' || )}
// item?.status === 'OUT_FOR_DELIVERY' contentContainerStyle={styles.list}
// ) { ListEmptyComponent={
navigation.navigate('OrderDetailsScreen', { <View style={styles.empty}>
orderId: item.id, <Text style={styles.emptyText}>No orders found</Text>
}); </View>
// } else { }
// navigation.navigate('OrderDeliveredScreen', { />)}
// orderId: item.id,
// });
// }
}}
/>
)}
contentContainerStyle={styles.list}
ListEmptyComponent={
<View style={styles.empty}>
<Text style={styles.emptyText}>No orders found</Text>
</View>
}
/>
</View> </View>
); );
}; };

View File

@ -14,6 +14,7 @@ import { Header } from '@components';
import { useAppTheme } from '@theme'; import { useAppTheme } from '@theme';
import { useAppDispatch, useAppSelector, getAllOffersThunk } from '@store'; import { useAppDispatch, useAppSelector, getAllOffersThunk } from '@store';
import { Promotion, PromotionType } from '@interfaces'; import { Promotion, PromotionType } from '@interfaces';
import Clipboard from '@react-native-clipboard/clipboard';
const getPromoVisuals = (type: PromotionType) => { const getPromoVisuals = (type: PromotionType) => {
switch (type) { switch (type) {
@ -69,16 +70,18 @@ export const OffersScreen: React.FC = () => {
}, [dispatch]), }, [dispatch]),
); );
// const handleCopy = (item: Promotion) => { const handleCopy = (item: Promotion) => {
// setCopiedId(item.id);
// const textToCopy = item.badgeText || item.title; Clipboard.setString(item.badgeText);
// if (Platform.OS === 'android') { setCopiedId(item.id);
// ToastAndroid.show(`Copied "${textToCopy}"`, ToastAndroid.SHORT); // const textToCopy = item.badgeText || item.title;
// } // if (Platform.OS === 'android') {
// setTimeout(() => { // ToastAndroid.show(`Copied "${textToCopy}"`, ToastAndroid.SHORT);
// setCopiedId(prev => (prev === item.id ? null : prev)); // }
// }, 2000); setTimeout(() => {
// }; setCopiedId(prev => (prev === item.id ? null : prev));
}, 2000);
};
const renderOfferCard = ({ item }: { item: Promotion }) => { const renderOfferCard = ({ item }: { item: Promotion }) => {
const visuals = getPromoVisuals(item.promotionType); const visuals = getPromoVisuals(item.promotionType);
@ -119,8 +122,8 @@ export const OffersScreen: React.FC = () => {
<TouchableOpacity <TouchableOpacity
style={[styles.copyButton, isCopied && styles.copyButtonSuccess]} style={[styles.copyButton, isCopied && styles.copyButtonSuccess]}
// onPress={() => handleCopy(item)} onPress={() => handleCopy(item)}
onPress={() => { }} // onPress={() => { }}
activeOpacity={0.8} activeOpacity={0.8}
> >
<Text style={styles.copyButtonText}> <Text style={styles.copyButtonText}>

View File

@ -22,6 +22,7 @@ import { useAppDispatch, useAppSelector } from '@store';
import { getProductDetailsThunk } from './thunk'; import { getProductDetailsThunk } from './thunk';
import { getDiscountPercentage, formatPrice } from '@components'; import { getDiscountPercentage, formatPrice } from '@components';
import { getFullUrl } from '@utils'; import { getFullUrl } from '@utils';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
type ProviderDetailsNavProp = StackNavigationProp< type ProviderDetailsNavProp = StackNavigationProp<
AppStackParamList, AppStackParamList,
@ -101,8 +102,7 @@ export const ProviderDetailsScreen: React.FC = () => {
const handleIncrement = () => { const handleIncrement = () => {
if (!product) return; if (!product) return;
const newQty = localQty + 1; const newQty = localQty + 1;
setLocalQty(newQty); dispatch(updateCartItemThunk({ productId: product.id, quantity: newQty })).unwrap().then(() => setLocalQty(newQty));
dispatch(updateCartItemThunk({ productId: product.id, quantity: newQty }));
}; };
const handleDecrement = () => { const handleDecrement = () => {
@ -375,7 +375,7 @@ export const ProviderDetailsScreen: React.FC = () => {
activeOpacity={0.8} activeOpacity={0.8}
onPress={() => navigation.goBack()} onPress={() => navigation.goBack()}
> >
<Text style={styles.iconButtonText}></Text> <MaterialIcons name="arrow-back" size={24} color="black" />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.iconButtonGroup}> <View style={styles.iconButtonGroup}>
<TouchableOpacity <TouchableOpacity
@ -480,7 +480,7 @@ export const ProviderDetailsScreen: React.FC = () => {
<Text style={styles.qtyValue}>{localQty}</Text> <Text style={styles.qtyValue}>{localQty}</Text>
<TouchableOpacity <TouchableOpacity
style={styles.qtyBtn} style={styles.qtyBtn}
onPress={() => setLocalQty(q => q + 1)} onPress={handleAddToCart}
activeOpacity={0.7} activeOpacity={0.7}
> >
<Text style={styles.qtyBtnText}>+</Text> <Text style={styles.qtyBtnText}>+</Text>

View File

@ -23,6 +23,7 @@ import { MainTabParamList } from '../../../navigation/mainTabNavigator';
import { useAppDispatch, useAppSelector } from '@store'; import { useAppDispatch, useAppSelector } from '@store';
import { getProductsByCategoryThunk } from './thunk'; import { getProductsByCategoryThunk } from './thunk';
import { Products } from '@interfaces'; import { Products } from '@interfaces';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
// ─── Types ─────────────────────────────────────────────────────────────────── // ─── Types ───────────────────────────────────────────────────────────────────
@ -114,7 +115,7 @@ export const SearchScreen: React.FC = () => {
searchQuery.trim().length === 0 searchQuery.trim().length === 0
? true ? true
: p.name.toLowerCase().includes(searchQuery.toLowerCase()) || : p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
(p.brand ?? '').toLowerCase().includes(searchQuery.toLowerCase()), (p.brand ?? '').toLowerCase().includes(searchQuery.toLowerCase()),
); );
// Category chips: prepend "All" // Category chips: prepend "All"
@ -207,7 +208,7 @@ export const SearchScreen: React.FC = () => {
activeOpacity={0.7} activeOpacity={0.7}
onPress={() => navigation.goBack()} onPress={() => navigation.goBack()}
> >
<Text style={styles.backBtnText}></Text> <MaterialIcons name="arrow-back" size={24} color="black" />
</TouchableOpacity> </TouchableOpacity>
<Text style={styles.headerTitle} numberOfLines={1}> <Text style={styles.headerTitle} numberOfLines={1}>
{headerCategoryName} {headerCategoryName}

View File

@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@react-native-async-storage/async-storage": "^3.1.1", "@react-native-async-storage/async-storage": "^3.1.1",
"@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/checkbox": "^0.5.20", "@react-native-community/checkbox": "^0.5.20",
"@react-native/new-app-screen": "0.86.0", "@react-native/new-app-screen": "0.86.0",
"@react-navigation/bottom-tabs": "^7.18.3", "@react-navigation/bottom-tabs": "^7.18.3",

View File

@ -1386,6 +1386,11 @@
dependencies: dependencies:
idb "8.0.3" idb "8.0.3"
"@react-native-clipboard/clipboard@^1.16.3":
version "1.16.3"
resolved "https://registry.yarnpkg.com/@react-native-clipboard/clipboard/-/clipboard-1.16.3.tgz#7807a90fd9984bf4d3a96faf2eee20457984a9bd"
integrity sha512-cMIcvoZKIrShzJHEaHbTAp458R9WOv0fB6UyC7Ek4Qk561Ow/DrzmmJmH/rAZg21Z6ixJ4YSdFDC14crqIBmCQ==
"@react-native-community/checkbox@^0.5.20": "@react-native-community/checkbox@^0.5.20":
version "0.5.20" version "0.5.20"
resolved "https://registry.yarnpkg.com/@react-native-community/checkbox/-/checkbox-0.5.20.tgz#7b8004a80a90988d5dff208693524f069270b849" resolved "https://registry.yarnpkg.com/@react-native-community/checkbox/-/checkbox-0.5.20.tgz#7b8004a80a90988d5dff208693524f069270b849"