Add company logo upload feature to business setup step 1 with image picker, guidelines, and proper spacing
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<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
|
||||
android:name=".MainApplication"
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<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>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"react-i18next": "^16.0.0",
|
||||
"react-native": "0.81.4",
|
||||
"react-native-gesture-handler": "^2.21.2",
|
||||
"react-native-image-picker": "^8.2.1",
|
||||
"react-native-safe-area-context": "^5.5.2",
|
||||
"react-native-screens": "^4.10.1",
|
||||
"react-native-vector-icons": "^10.2.0"
|
||||
@@ -11102,6 +11103,16 @@
|
||||
"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": {
|
||||
"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",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"react-i18next": "^16.0.0",
|
||||
"react-native": "0.81.4",
|
||||
"react-native-gesture-handler": "^2.21.2",
|
||||
"react-native-image-picker": "^8.2.1",
|
||||
"react-native-safe-area-context": "^5.5.2",
|
||||
"react-native-screens": "^4.10.1",
|
||||
"react-native-vector-icons": "^10.2.0"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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 { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { launchImageLibrary, launchCamera, ImagePickerResponse } from 'react-native-image-picker';
|
||||
import { colors, typography, spacing, borderRadius, shadows, iconSizes } from '../theme';
|
||||
import { RootStackParamList } from '../types/navigation';
|
||||
|
||||
@@ -66,6 +67,7 @@ export const BusinessSetupScreen: React.FC<Props> = ({ navigation }) => {
|
||||
const { t } = useTranslation();
|
||||
const [step, setStep] = useState<SetupStep>('company');
|
||||
const [companyName, setCompanyName] = useState('');
|
||||
const [companyLogo, setCompanyLogo] = useState<string | null>(null);
|
||||
const [selectedIndustry, setSelectedIndustry] = useState<string | null>(null);
|
||||
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 = () => {
|
||||
if (step === 'company' && companyName) {
|
||||
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.stepSubtitle}>{t('businessSetup.steps.company.subtitle')}</Text>
|
||||
|
||||
{/* Company Name Input */}
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.inputLabel}>{t('businessSetup.steps.company.label')}</Text>
|
||||
<TextInput
|
||||
@@ -148,6 +226,56 @@ export const BusinessSetupScreen: React.FC<Props> = ({ navigation }) => {
|
||||
autoFocus
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
|
||||
@@ -469,4 +597,123 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user