283 lines
7.1 KiB
TypeScript

import { useState } from 'react';
import {
Modal,
View,
Text,
TextInput,
Pressable,
StyleSheet,
ActivityIndicator,
Platform,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useTranslation } from 'react-i18next';
import { apiFetch } from '../../lib/api';
type Props = {
visible: boolean;
onClose: () => void;
onCreated: (room: any) => void;
};
export function CreateRoomSheet({ visible, onClose, onCreated }: Props) {
const { t } = useTranslation();
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [isPublic, setIsPublic] = useState(true);
const [joinMode, setJoinMode] = useState<'approval' | 'invite_only'>('approval');
const [creating, setCreating] = useState(false);
function reset() {
setName('');
setDescription('');
setIsPublic(true);
setJoinMode('approval');
}
async function create() {
const trimmed = name.trim();
if (!trimmed || creating) return;
setCreating(true);
try {
const room = await apiFetch<any>('/api/chat/rooms', {
method: 'POST',
body: {
name: trimmed,
description: description.trim() || undefined,
isPublic,
joinMode: isPublic ? 'open' : joinMode,
},
});
onCreated(room);
reset();
onClose();
} catch (err: any) {
console.error('Room erstellen fehlgeschlagen:', err.message);
} finally {
setCreating(false);
}
}
return (
<Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
<Pressable style={styles.backdrop} onPress={onClose}>
<Pressable style={styles.sheet} onPress={() => {}}>
<View style={styles.grabber} />
<Text style={styles.title}>{t('chat.create_group')}</Text>
<TextInput
value={name}
onChangeText={setName}
placeholder={t('chat.room_name')}
placeholderTextColor="#a3a3a3"
style={styles.input}
maxLength={60}
/>
<TextInput
value={description}
onChangeText={setDescription}
placeholder={t('chat.room_description')}
placeholderTextColor="#a3a3a3"
multiline
style={[styles.input, { height: 70, textAlignVertical: 'top' }]}
maxLength={250}
/>
{/* Public toggle */}
<Pressable
style={styles.toggleRow}
onPress={() => setIsPublic((v) => !v)}
>
<Text style={styles.toggleLabel}>{t('chat.public_room')}</Text>
<View style={[styles.toggle, isPublic && styles.toggleOn]}>
<View style={[styles.toggleKnob, isPublic && styles.toggleKnobOn]} />
</View>
</Pressable>
{/* Join mode (private only) */}
{!isPublic && (
<View style={{ marginTop: 8 }}>
<Text style={styles.subLabel}>{t('chat.join_mode')}</Text>
<View style={styles.modeRow}>
{(['approval', 'invite_only'] as const).map((mode) => (
<Pressable
key={mode}
style={[styles.modeBtn, joinMode === mode && styles.modeBtnActive]}
onPress={() => setJoinMode(mode)}
>
<Text
style={[
styles.modeBtnText,
joinMode === mode && styles.modeBtnTextActive,
]}
>
{t(`chat.join_mode_${mode === 'approval' ? 'approval' : 'invite'}`)}
</Text>
</Pressable>
))}
</View>
</View>
)}
{/* Actions */}
<View style={styles.actions}>
<Pressable onPress={onClose} style={styles.cancelBtn}>
<Text style={styles.cancelText}>{t('common.cancel')}</Text>
</Pressable>
<Pressable
onPress={create}
disabled={!name.trim() || creating}
style={[
styles.createBtn,
{ opacity: !name.trim() || creating ? 0.5 : 1 },
]}
>
{creating ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Text style={styles.createText}>{t('chat.create')}</Text>
)}
</Pressable>
</View>
</Pressable>
</Pressable>
</Modal>
);
}
const styles = StyleSheet.create({
backdrop: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'flex-end',
},
sheet: {
backgroundColor: '#fff',
borderTopLeftRadius: 22,
borderTopRightRadius: 22,
padding: 18,
paddingBottom: Platform.OS === 'ios' ? 32 : 18,
},
grabber: {
width: 36,
height: 4,
borderRadius: 2,
backgroundColor: '#d4d4d4',
alignSelf: 'center',
marginBottom: 12,
},
title: {
fontSize: 17,
fontFamily: 'Nunito_700Bold',
color: '#171717',
marginBottom: 14,
},
input: {
backgroundColor: '#f5f5f5',
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 12,
fontSize: 14,
fontFamily: 'Nunito_400Regular',
color: '#171717',
marginBottom: 10,
},
toggleRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 6,
marginTop: 4,
},
toggleLabel: {
fontSize: 14,
fontFamily: 'Nunito_600SemiBold',
color: '#171717',
},
toggle: {
width: 46,
height: 28,
borderRadius: 14,
backgroundColor: '#e5e5e5',
padding: 2,
justifyContent: 'center',
},
toggleOn: {
backgroundColor: '#007AFF',
},
toggleKnob: {
width: 24,
height: 24,
borderRadius: 12,
backgroundColor: '#fff',
shadowColor: '#000',
shadowOpacity: 0.15,
shadowRadius: 2,
shadowOffset: { width: 0, height: 1 },
elevation: 2,
},
toggleKnobOn: {
transform: [{ translateX: 18 }],
},
subLabel: {
fontSize: 12,
fontFamily: 'Nunito_600SemiBold',
color: '#737373',
marginBottom: 6,
},
modeRow: {
flexDirection: 'row',
},
modeBtn: {
flex: 1,
paddingVertical: 8,
borderRadius: 10,
borderWidth: 1,
borderColor: '#e5e5e5',
alignItems: 'center',
marginRight: 6,
},
modeBtnActive: {
backgroundColor: '#eff6ff',
borderColor: '#007AFF',
},
modeBtnText: {
fontSize: 12,
fontFamily: 'Nunito_600SemiBold',
color: '#737373',
},
modeBtnTextActive: {
color: '#007AFF',
},
actions: {
flexDirection: 'row',
marginTop: 20,
},
cancelBtn: {
flex: 1,
backgroundColor: '#f5f5f5',
paddingVertical: 12,
borderRadius: 12,
alignItems: 'center',
marginRight: 6,
},
cancelText: {
fontSize: 14,
fontFamily: 'Nunito_600SemiBold',
color: '#171717',
},
createBtn: {
flex: 1,
backgroundColor: '#007AFF',
paddingVertical: 12,
borderRadius: 12,
alignItems: 'center',
marginLeft: 6,
},
createText: {
fontSize: 14,
fontFamily: 'Nunito_700Bold',
color: '#fff',
},
});