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">
|
<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"
|
||||||
|
|||||||
@@ -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
11
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user