Add company logo upload feature to business setup step 1 with image picker, guidelines, and proper spacing

This commit is contained in:
2025-10-13 18:29:25 +05:30
parent e0c68dbbd2
commit 60a53088d3
5 changed files with 270 additions and 1 deletions

View File

@@ -1,6 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application <application
android:name=".MainApplication" android:name=".MainApplication"

View File

@@ -34,6 +34,12 @@
</dict> </dict>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string></string> <string></string>
<key>NSCameraUsageDescription</key>
<string>Pennyzo needs access to your camera to take photos of your company logo</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Pennyzo needs access to your photo library to select your company logo</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Pennyzo needs access to save photos to your library</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>

11
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"react-i18next": "^16.0.0", "react-i18next": "^16.0.0",
"react-native": "0.81.4", "react-native": "0.81.4",
"react-native-gesture-handler": "^2.21.2", "react-native-gesture-handler": "^2.21.2",
"react-native-image-picker": "^8.2.1",
"react-native-safe-area-context": "^5.5.2", "react-native-safe-area-context": "^5.5.2",
"react-native-screens": "^4.10.1", "react-native-screens": "^4.10.1",
"react-native-vector-icons": "^10.2.0" "react-native-vector-icons": "^10.2.0"
@@ -11102,6 +11103,16 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-image-picker": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-8.2.1.tgz",
"integrity": "sha512-FBeGYJGFDjMdGCcyubDJgBAPCQ4L1D3hwLXyUU91jY9ahOZMTbluceVvRmrEKqnDPFJ0gF1NVhJ0nr1nROFLdg==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-is-edge-to-edge": { "node_modules/react-native-is-edge-to-edge": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz",

View File

@@ -20,6 +20,7 @@
"react-i18next": "^16.0.0", "react-i18next": "^16.0.0",
"react-native": "0.81.4", "react-native": "0.81.4",
"react-native-gesture-handler": "^2.21.2", "react-native-gesture-handler": "^2.21.2",
"react-native-image-picker": "^8.2.1",
"react-native-safe-area-context": "^5.5.2", "react-native-safe-area-context": "^5.5.2",
"react-native-screens": "^4.10.1", "react-native-screens": "^4.10.1",
"react-native-vector-icons": "^10.2.0" "react-native-vector-icons": "^10.2.0"

View File

@@ -1,8 +1,9 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { StyleSheet, Text, View, TextInput, TouchableOpacity, ScrollView } from 'react-native'; import { StyleSheet, Text, View, TextInput, TouchableOpacity, ScrollView, Image, Alert } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { launchImageLibrary, launchCamera, ImagePickerResponse } from 'react-native-image-picker';
import { colors, typography, spacing, borderRadius, shadows, iconSizes } from '../theme'; import { colors, typography, spacing, borderRadius, shadows, iconSizes } from '../theme';
import { RootStackParamList } from '../types/navigation'; import { RootStackParamList } from '../types/navigation';
@@ -66,6 +67,7 @@ export const BusinessSetupScreen: React.FC<Props> = ({ navigation }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [step, setStep] = useState<SetupStep>('company'); const [step, setStep] = useState<SetupStep>('company');
const [companyName, setCompanyName] = useState(''); const [companyName, setCompanyName] = useState('');
const [companyLogo, setCompanyLogo] = useState<string | null>(null);
const [selectedIndustry, setSelectedIndustry] = useState<string | null>(null); const [selectedIndustry, setSelectedIndustry] = useState<string | null>(null);
const [selectedAgents, setSelectedAgents] = useState<string[]>([]); const [selectedAgents, setSelectedAgents] = useState<string[]>([]);
@@ -96,6 +98,81 @@ export const BusinessSetupScreen: React.FC<Props> = ({ navigation }) => {
} }
}; };
const handleImagePicker = () => {
Alert.alert(
'Upload Company Logo',
'Choose an option',
[
{
text: 'Take Photo',
onPress: () => {
launchCamera(
{
mediaType: 'photo',
quality: 0.8,
maxWidth: 800,
maxHeight: 800,
includeBase64: false,
},
handleImageResponse
);
},
},
{
text: 'Choose from Gallery',
onPress: () => {
launchImageLibrary(
{
mediaType: 'photo',
quality: 0.8,
maxWidth: 800,
maxHeight: 800,
includeBase64: false,
},
handleImageResponse
);
},
},
{
text: 'Cancel',
style: 'cancel',
},
]
);
};
const handleImageResponse = (response: ImagePickerResponse) => {
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.errorCode) {
console.log('ImagePicker Error: ', response.errorMessage);
Alert.alert('Error', 'Failed to pick image. Please try again.');
} else if (response.assets && response.assets[0]) {
const imageUri = response.assets[0].uri;
if (imageUri) {
setCompanyLogo(imageUri);
}
}
};
const handleRemoveLogo = () => {
Alert.alert(
'Remove Logo',
'Are you sure you want to remove the company logo?',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Remove',
onPress: () => setCompanyLogo(null),
style: 'destructive',
},
]
);
};
const handleContinue = () => { const handleContinue = () => {
if (step === 'company' && companyName) { if (step === 'company' && companyName) {
setStep('industry'); setStep('industry');
@@ -137,6 +214,7 @@ export const BusinessSetupScreen: React.FC<Props> = ({ navigation }) => {
<Text style={styles.stepTitle}>{t('businessSetup.steps.company.title')}</Text> <Text style={styles.stepTitle}>{t('businessSetup.steps.company.title')}</Text>
<Text style={styles.stepSubtitle}>{t('businessSetup.steps.company.subtitle')}</Text> <Text style={styles.stepSubtitle}>{t('businessSetup.steps.company.subtitle')}</Text>
{/* Company Name Input */}
<View style={styles.inputContainer}> <View style={styles.inputContainer}>
<Text style={styles.inputLabel}>{t('businessSetup.steps.company.label')}</Text> <Text style={styles.inputLabel}>{t('businessSetup.steps.company.label')}</Text>
<TextInput <TextInput
@@ -148,6 +226,56 @@ export const BusinessSetupScreen: React.FC<Props> = ({ navigation }) => {
autoFocus autoFocus
/> />
</View> </View>
{/* Company Logo Upload */}
<View style={styles.logoUploadContainer}>
<View style={styles.logoHeaderContainer}>
<Text style={styles.inputLabel}>Company Logo (Optional)</Text>
<View style={styles.logoInfoBadge}>
<MaterialCommunityIcons name="information" size={14} color={colors.highlight} />
<Text style={styles.logoInfoText}>Square format recommended</Text>
</View>
</View>
{companyLogo ? (
<View style={styles.logoPreviewContainer}>
<Image source={{ uri: companyLogo }} style={styles.logoPreview} />
<TouchableOpacity
style={styles.logoRemoveButton}
onPress={handleRemoveLogo}
>
<MaterialCommunityIcons name="close-circle" size={28} color="#EF4444" />
</TouchableOpacity>
<TouchableOpacity
style={styles.logoChangeButton}
onPress={handleImagePicker}
>
<MaterialCommunityIcons name="pencil" size={18} color={colors.background} />
<Text style={styles.logoChangeText}>Change Logo</Text>
</TouchableOpacity>
</View>
) : (
<TouchableOpacity
style={styles.logoUploadButton}
onPress={handleImagePicker}
>
<View style={styles.logoUploadIcon}>
<MaterialCommunityIcons name="image-plus" size={40} color={colors.highlight} />
</View>
<Text style={styles.logoUploadText}>Upload Company Logo</Text>
<Text style={styles.logoUploadHint}>Tap to choose from gallery or take a photo</Text>
<View style={styles.logoGuidelinesContainer}>
<View style={styles.guidelineItem}>
<MaterialCommunityIcons name="aspect-ratio" size={16} color={colors.textSecondary} />
<Text style={styles.guidelineText}>Square (1:1) or similar ratio</Text>
</View>
<View style={styles.guidelineItem}>
<MaterialCommunityIcons name="file-image" size={16} color={colors.textSecondary} />
<Text style={styles.guidelineText}>PNG, JPG formats supported</Text>
</View>
</View>
</TouchableOpacity>
)}
</View>
</View> </View>
)} )}
@@ -469,4 +597,123 @@ const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
color: colors.background, color: colors.background,
}, },
logoUploadContainer: {
width: '100%',
marginTop: spacing.xxl,
marginBottom: spacing.xl,
},
logoUploadButton: {
width: '100%',
minHeight: 220,
borderRadius: borderRadius.lg,
borderWidth: 3,
borderColor: colors.highlight,
borderStyle: 'dashed',
backgroundColor: `${colors.highlight}15`,
alignItems: 'center',
justifyContent: 'center',
gap: spacing.xs,
padding: spacing.lg,
paddingVertical: spacing.xl,
},
logoUploadIcon: {
width: 70,
height: 70,
borderRadius: 35,
backgroundColor: `${colors.highlight}25`,
alignItems: 'center',
justifyContent: 'center',
marginBottom: spacing.xs,
},
logoUploadText: {
...typography.bodyBold,
fontSize: 16,
color: colors.highlightAlt,
},
logoUploadHint: {
...typography.caption,
color: colors.textSecondary,
textAlign: 'center',
marginBottom: spacing.xs,
},
logoPreviewContainer: {
width: '100%',
alignItems: 'center',
position: 'relative',
},
logoPreview: {
width: 200,
height: 200,
borderRadius: borderRadius.lg,
borderWidth: 3,
borderColor: colors.highlight,
},
logoRemoveButton: {
position: 'absolute',
top: -10,
right: '50%',
marginRight: -110,
backgroundColor: colors.background,
borderRadius: 14,
...shadows.md,
},
logoChangeButton: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
marginTop: spacing.md,
paddingVertical: spacing.sm,
paddingHorizontal: spacing.lg,
backgroundColor: colors.highlight,
borderRadius: borderRadius.md,
...shadows.glow,
},
logoChangeText: {
...typography.bodyBold,
fontSize: 14,
color: colors.background,
},
logoHeaderContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.sm,
flexWrap: 'wrap',
gap: spacing.xs,
},
logoInfoBadge: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
paddingHorizontal: spacing.sm,
paddingVertical: spacing.xs,
backgroundColor: `${colors.highlight}15`,
borderRadius: borderRadius.sm,
borderWidth: 1,
borderColor: `${colors.highlight}30`,
},
logoInfoText: {
...typography.caption,
fontSize: 11,
color: colors.highlight,
fontWeight: '600',
},
logoGuidelinesContainer: {
marginTop: spacing.sm,
gap: spacing.xs,
width: '100%',
alignItems: 'center',
},
guidelineItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: spacing.xs,
},
guidelineText: {
...typography.caption,
fontSize: 11,
color: colors.textSecondary,
flexShrink: 1,
},
}); });