212 lines
6.1 KiB
TypeScript
212 lines
6.1 KiB
TypeScript
import { useTheme } from '@/components/ThemeContext';
|
|
import * as ImagePicker from 'expo-image-picker';
|
|
import { Camera, Play, X } from 'lucide-react-native';
|
|
import React, { forwardRef, useImperativeHandle, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Image, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
|
|
|
type MediaType = { uri: string; type: 'image' | 'video' };
|
|
type StepProps = {
|
|
formData: any;
|
|
updateForm: (key: string, value: any) => void;
|
|
removeMedia: (index: number) => void;
|
|
};
|
|
|
|
type Errors = {
|
|
title?: string;
|
|
description?: string;
|
|
media?: string;
|
|
};
|
|
|
|
const MAX_MEDIA = 10;
|
|
|
|
const StepOneServices = forwardRef(({ formData, updateForm, removeMedia }: StepProps, ref) => {
|
|
const { isDark } = useTheme();
|
|
const { t } = useTranslation();
|
|
const [errors, setErrors] = useState<Errors>({});
|
|
|
|
const validate = () => {
|
|
const e: Errors = {};
|
|
|
|
if (!formData.title || formData.title.trim().length < 5)
|
|
e.title = t("Sarlavha kamida 5 ta belgidan iborat bo'lishi kerak");
|
|
|
|
if (!formData.description || formData.description.trim().length < 10)
|
|
e.description = t("Tavsif kamida 10 ta belgidan iborat bo'lishi kerak");
|
|
|
|
if (!formData.media || formData.media.length === 0)
|
|
e.media = t('Kamida bitta rasm yoki video yuklang');
|
|
|
|
setErrors(e);
|
|
return Object.keys(e).length === 0;
|
|
};
|
|
|
|
useImperativeHandle(ref, () => ({ validate }));
|
|
|
|
const pickMedia = async () => {
|
|
if (formData.media.length >= MAX_MEDIA) return;
|
|
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
|
allowsMultipleSelection: true,
|
|
quality: 0.8,
|
|
});
|
|
|
|
if (!result.canceled) {
|
|
const assets = result.assets
|
|
.slice(0, MAX_MEDIA - formData.media.length)
|
|
.map((a) => ({ uri: a.uri, type: a.type as 'image' | 'video' }));
|
|
|
|
updateForm('media', [...formData.media, ...assets]);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<View style={styles.stepContainer}>
|
|
{/* Sarlavha */}
|
|
<Text style={[styles.label, { color: isDark ? '#f8fafc' : '#0f172a' }]}>{t('Sarlavha')}</Text>
|
|
<View
|
|
style={[
|
|
styles.inputBox,
|
|
{
|
|
backgroundColor: isDark ? '#1e293b' : '#ffffff',
|
|
borderColor: isDark ? '#334155' : '#e2e8f0',
|
|
},
|
|
]}
|
|
>
|
|
<TextInput
|
|
style={[styles.input, { color: isDark ? '#fff' : '#0f172a' }]}
|
|
placeholder={t('Xizmat sarlavhasi')}
|
|
placeholderTextColor={isDark ? '#94a3b8' : '#94a3b8'}
|
|
value={formData.title}
|
|
onChangeText={(t) => updateForm('title', t)}
|
|
/>
|
|
</View>
|
|
{errors.title && <Text style={styles.error}>{errors.title}</Text>}
|
|
|
|
{/* Tavsif */}
|
|
<Text style={[styles.label, { color: isDark ? '#f8fafc' : '#0f172a' }]}>{t('Tavsif')}</Text>
|
|
<View
|
|
style={[
|
|
styles.inputBox,
|
|
styles.textArea,
|
|
{
|
|
backgroundColor: isDark ? '#1e293b' : '#ffffff',
|
|
borderColor: isDark ? '#334155' : '#e2e8f0',
|
|
},
|
|
]}
|
|
>
|
|
<TextInput
|
|
style={[styles.input, { color: isDark ? '#fff' : '#0f172a' }]}
|
|
placeholder={t('Batafsil yozing...')}
|
|
placeholderTextColor={isDark ? '#94a3b8' : '#94a3b8'}
|
|
multiline
|
|
value={formData.description}
|
|
onChangeText={(t) => updateForm('description', t)}
|
|
/>
|
|
</View>
|
|
{errors.description && <Text style={styles.error}>{errors.description}</Text>}
|
|
|
|
{/* Media */}
|
|
<Text style={[styles.label, { color: isDark ? '#f8fafc' : '#0f172a' }]}>
|
|
{t('Media')} ({formData.media.length}/{MAX_MEDIA})
|
|
</Text>
|
|
<View style={styles.media}>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.upload,
|
|
{
|
|
borderColor: '#2563eb',
|
|
backgroundColor: isDark ? '#1e293b' : '#f8fafc',
|
|
},
|
|
]}
|
|
onPress={pickMedia}
|
|
>
|
|
<Camera size={28} color="#2563eb" />
|
|
<Text style={styles.uploadText}>{t('Yuklash')}</Text>
|
|
</TouchableOpacity>
|
|
|
|
{formData.media.map((m: MediaType, i: number) => (
|
|
<View key={i} style={styles.preview}>
|
|
<Image source={{ uri: m.uri }} style={styles.image} />
|
|
{m.type === 'video' && (
|
|
<View style={styles.play}>
|
|
<Play size={14} color="#fff" fill="#fff" />
|
|
</View>
|
|
)}
|
|
<TouchableOpacity style={styles.remove} onPress={() => removeMedia(i)}>
|
|
<X size={12} color="#fff" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
))}
|
|
</View>
|
|
{errors.media && <Text style={styles.error}>{errors.media}</Text>}
|
|
</View>
|
|
);
|
|
});
|
|
|
|
export default StepOneServices;
|
|
|
|
const styles = StyleSheet.create({
|
|
stepContainer: { gap: 10 },
|
|
label: { fontWeight: '700', fontSize: 15 },
|
|
error: { color: '#ef4444', fontSize: 13, marginLeft: 6 },
|
|
inputBox: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
paddingHorizontal: 16,
|
|
height: 56,
|
|
},
|
|
textArea: { height: 120, alignItems: 'flex-start', paddingTop: 16 },
|
|
input: { flex: 1, fontSize: 16 },
|
|
media: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
|
|
upload: {
|
|
width: 100,
|
|
height: 100,
|
|
borderRadius: 16,
|
|
borderWidth: 2,
|
|
borderStyle: 'dashed',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
uploadText: { color: '#2563eb', fontSize: 11, marginTop: 4, fontWeight: '600' },
|
|
preview: { width: 100, height: 100 },
|
|
image: { width: '100%', height: '100%', borderRadius: 16 },
|
|
play: {
|
|
position: 'absolute',
|
|
top: '40%',
|
|
left: '40%',
|
|
backgroundColor: 'rgba(0,0,0,.5)',
|
|
padding: 6,
|
|
borderRadius: 20,
|
|
},
|
|
remove: {
|
|
position: 'absolute',
|
|
top: -6,
|
|
right: -6,
|
|
backgroundColor: '#ef4444',
|
|
padding: 4,
|
|
borderRadius: 10,
|
|
},
|
|
prefixContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginRight: 12,
|
|
},
|
|
prefix: {
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
letterSpacing: 0.3,
|
|
},
|
|
prefixFocused: {
|
|
color: '#fff',
|
|
},
|
|
divider: {
|
|
width: 1.5,
|
|
height: 24,
|
|
marginLeft: 12,
|
|
},
|
|
});
|