All checks were successful
Build and Release / build-and-release (push) Successful in 15m35s
228 lines
10 KiB
JavaScript
228 lines
10 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import { useConvex } from 'convex/react';
|
|
import { api } from '../../../../convex/_generated/api';
|
|
|
|
const MobileChannelSettingsScreen = ({ channel, categories, onClose, onDelete }) => {
|
|
const [visible, setVisible] = useState(false);
|
|
const [channelName, setChannelName] = useState(channel.name);
|
|
const [channelTopic, setChannelTopic] = useState(channel.topic || '');
|
|
const [selectedCategoryId, setSelectedCategoryId] = useState(channel.categoryId || null);
|
|
const [showCategoryPicker, setShowCategoryPicker] = useState(false);
|
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
const convex = useConvex();
|
|
|
|
useEffect(() => {
|
|
requestAnimationFrame(() => setVisible(true));
|
|
}, []);
|
|
|
|
const handleClose = () => {
|
|
setVisible(false);
|
|
setTimeout(onClose, 250);
|
|
};
|
|
|
|
const handleNameChange = (e) => {
|
|
setChannelName(e.target.value.toLowerCase().replace(/\s+/g, '-'));
|
|
};
|
|
|
|
const hasChanges =
|
|
channelName.trim() !== channel.name ||
|
|
channelTopic.trim() !== (channel.topic || '') ||
|
|
selectedCategoryId !== (channel.categoryId || null);
|
|
|
|
const handleSave = async () => {
|
|
if (!hasChanges || saving) return;
|
|
setSaving(true);
|
|
try {
|
|
const trimmedName = channelName.trim();
|
|
if (trimmedName && trimmedName !== channel.name) {
|
|
await convex.mutation(api.channels.rename, { id: channel._id, name: trimmedName });
|
|
}
|
|
const trimmedTopic = channelTopic.trim();
|
|
if (trimmedTopic !== (channel.topic || '')) {
|
|
await convex.mutation(api.channels.updateTopic, { id: channel._id, topic: trimmedTopic });
|
|
}
|
|
if (selectedCategoryId !== (channel.categoryId || null)) {
|
|
await convex.mutation(api.channels.moveChannel, {
|
|
id: channel._id,
|
|
categoryId: selectedCategoryId || undefined,
|
|
position: 0,
|
|
});
|
|
}
|
|
handleClose();
|
|
} catch (err) {
|
|
console.error('Failed to save channel settings:', err);
|
|
alert('Failed to save: ' + err.message);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
try {
|
|
await convex.mutation(api.channels.remove, { id: channel._id });
|
|
if (onDelete) onDelete(channel._id);
|
|
handleClose();
|
|
} catch (err) {
|
|
console.error('Failed to delete channel:', err);
|
|
alert('Failed to delete: ' + err.message);
|
|
}
|
|
};
|
|
|
|
const currentCategoryName = selectedCategoryId
|
|
? (categories || []).find(c => c._id === selectedCategoryId)?.name || 'Unknown'
|
|
: 'None';
|
|
|
|
return ReactDOM.createPortal(
|
|
<div className={`mobile-create-screen${visible ? ' visible' : ''}`}>
|
|
{/* Header */}
|
|
<div className="mobile-create-header">
|
|
<button className="mobile-create-close-btn" onClick={handleClose}>
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
|
</svg>
|
|
</button>
|
|
<span className="mobile-create-title">Channel Settings</span>
|
|
<button
|
|
className={`mobile-create-submit-btn${!hasChanges || saving ? ' disabled' : ''}`}
|
|
onClick={handleSave}
|
|
disabled={!hasChanges || saving}
|
|
>
|
|
{saving ? 'Saving...' : 'Save'}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Body */}
|
|
<div className="mobile-create-body">
|
|
{/* Channel Name */}
|
|
<div className="mobile-create-section">
|
|
<label className="mobile-create-section-label">Channel Name</label>
|
|
<div className="mobile-create-input-wrapper">
|
|
<span className="mobile-create-input-prefix">
|
|
{channel.type === 'voice' ? (
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 3a1 1 0 0 0-1-1h-.06a1 1 0 0 0-.74.32L5.92 7H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.92l4.28 4.68a1 1 0 0 0 .74.32H11a1 1 0 0 0 1-1z" />
|
|
</svg>
|
|
) : '#'}
|
|
</span>
|
|
<input
|
|
className="mobile-create-input"
|
|
type="text"
|
|
placeholder="channel-name"
|
|
value={channelName}
|
|
onChange={handleNameChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Channel Topic */}
|
|
<div className="mobile-create-section">
|
|
<label className="mobile-create-section-label">
|
|
Channel Topic
|
|
<span style={{ float: 'right', fontWeight: 400, textTransform: 'none' }}>
|
|
{channelTopic.length}/1024
|
|
</span>
|
|
</label>
|
|
<div className="mobile-create-input-wrapper" style={{ alignItems: 'flex-start' }}>
|
|
<textarea
|
|
className="mobile-create-input"
|
|
placeholder="Set a topic for this channel"
|
|
value={channelTopic}
|
|
onChange={(e) => {
|
|
if (e.target.value.length <= 1024) setChannelTopic(e.target.value);
|
|
}}
|
|
rows={3}
|
|
style={{
|
|
resize: 'none',
|
|
fontFamily: 'inherit',
|
|
lineHeight: '1.4',
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Category */}
|
|
<div className="mobile-create-section">
|
|
<label className="mobile-create-section-label">Category</label>
|
|
<div
|
|
className="mobile-create-input-wrapper"
|
|
style={{ cursor: 'pointer' }}
|
|
onClick={() => setShowCategoryPicker(!showCategoryPicker)}
|
|
>
|
|
<span className="mobile-create-input" style={{ cursor: 'pointer', userSelect: 'none' }}>
|
|
{currentCategoryName}
|
|
</span>
|
|
<svg
|
|
width="20" height="20" viewBox="0 0 24 24"
|
|
fill="var(--interactive-normal)"
|
|
style={{
|
|
marginRight: 8,
|
|
flexShrink: 0,
|
|
transform: showCategoryPicker ? 'rotate(180deg)' : 'none',
|
|
transition: 'transform 0.15s',
|
|
}}
|
|
>
|
|
<path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z" />
|
|
</svg>
|
|
</div>
|
|
{showCategoryPicker && (
|
|
<div className="mobile-channel-settings-category-list">
|
|
<div
|
|
className={`mobile-channel-settings-category-option${!selectedCategoryId ? ' selected' : ''}`}
|
|
onClick={() => { setSelectedCategoryId(null); setShowCategoryPicker(false); }}
|
|
>
|
|
None
|
|
</div>
|
|
{(categories || []).map(cat => (
|
|
<div
|
|
key={cat._id}
|
|
className={`mobile-channel-settings-category-option${selectedCategoryId === cat._id ? ' selected' : ''}`}
|
|
onClick={() => { setSelectedCategoryId(cat._id); setShowCategoryPicker(false); }}
|
|
>
|
|
{cat.name}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Delete Channel */}
|
|
<div className="mobile-create-section" style={{ marginTop: 16 }}>
|
|
{!confirmDelete ? (
|
|
<button
|
|
className="mobile-channel-settings-delete-btn"
|
|
onClick={() => setConfirmDelete(true)}
|
|
>
|
|
Delete Channel
|
|
</button>
|
|
) : (
|
|
<div className="mobile-channel-settings-delete-confirm">
|
|
<p style={{ color: '#ed4245', fontSize: 14, margin: '0 0 12px' }}>
|
|
Are you sure you want to delete <strong>#{channel.name}</strong>? This cannot be undone.
|
|
</p>
|
|
<div style={{ display: 'flex', gap: 8 }}>
|
|
<button
|
|
className="mobile-channel-settings-cancel-btn"
|
|
onClick={() => setConfirmDelete(false)}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
className="mobile-channel-settings-delete-btn"
|
|
onClick={handleDelete}
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
document.body
|
|
);
|
|
};
|
|
|
|
export default MobileChannelSettingsScreen;
|