veza/apps/web/src/features/chat/components/CreateRoomDialog.tsx
senke 2292ecd56b
Some checks failed
Backend API CI / test-unit (push) Failing after 2s
Frontend CI / test (push) Failing after 3s
Backend API CI / test-integration (push) Failing after 5s
Storybook Audit / Build & audit Storybook (push) Failing after 2s
feat(v0.10.7): Collaboration Temps Réel F481-F483
- F481: Co-listening sessions (WebSocket sync, ListenTogether page)
- F482: Stem sharing (upload/list/download wav,aiff,flac)
- F483: Collaborative rooms (type collaborative, max 10, invite-only)
- Roadmap: v0.10.7 → DONE
2026-03-10 13:34:16 +01:00

176 lines
5.3 KiB
TypeScript

import { useState, useRef } from 'react';
import { Dialog } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { Select } from '@/components/ui/select';
import { apiClient } from '@/services/api/client';
import { useToast } from '@/hooks/useToast';
import { useChatStore } from '../store/chatStore';
import { parseApiError } from '@/utils/apiErrorHandler';
import { ErrorDisplay } from '@/components/ui/ErrorDisplay';
// FE-PAGE-005: Complete Chat page implementation - Room Management
interface CreateRoomDialogProps {
open: boolean;
onClose: () => void;
}
export function CreateRoomDialog({ open, onClose }: CreateRoomDialogProps) {
const [name, setName] = useState('');
const [type, setType] = useState<'public' | 'private' | 'collaborative'>('public');
const [isCreating, setIsCreating] = useState(false);
const [validationError, setValidationError] = useState<string | null>(null);
const [mutationError, setMutationError] = useState<Error | null>(null);
const [retryCount, setRetryCount] = useState(0);
const lastMutationRef = useRef<(() => Promise<void>) | null>(null);
const toast = useToast();
const { addConversation, setCurrentConversation } = useChatStore();
const handleCreate = async () => {
setValidationError(null);
setMutationError(null);
if (!name.trim()) {
setValidationError('Room name is required');
return;
}
// Action 3.4.1.3: Store mutation for retry
const roomName = name.trim();
const roomType = type;
const performMutation = async () => {
const response = await apiClient.post('/conversations', {
name: roomName,
type: roomType,
});
const newRoom = {
id: response.data.id || response.data.conversation?.id,
name: response.data.name || response.data.conversation?.name,
type:
response.data.type || response.data.conversation?.type || roomType,
participants: response.data.participants || [],
unread_count: 0,
};
addConversation(newRoom);
setCurrentConversation(newRoom.id);
toast.success('Room created successfully');
setName('');
setType('public');
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
onClose();
};
lastMutationRef.current = performMutation;
setIsCreating(true);
try {
await performMutation();
} catch (error: unknown) {
const apiError = parseApiError(error);
setMutationError(new Error(apiError.message));
} finally {
setIsCreating(false);
}
};
// Action 3.4.1.3: Retry handler for failed mutations
const handleRetry = async () => {
if (!lastMutationRef.current || retryCount >= 3) return;
setRetryCount((prev) => prev + 1);
setIsCreating(true);
try {
await lastMutationRef.current();
} catch (error) {
// Error will be handled by the mutation function
} finally {
setIsCreating(false);
}
};
return (
<Dialog
open={open}
onClose={onClose}
title="Create New Room"
variant="default"
size="md"
>
<div className="space-y-4">
{mutationError && (
<ErrorDisplay
error={mutationError}
variant="banner"
severity="error"
context={{
action: 'creating room',
resource: 'conversation',
}}
onRetry={retryCount < 3 ? handleRetry : undefined}
onDismiss={() => {
setMutationError(null);
setRetryCount(0);
lastMutationRef.current = null;
}}
/>
)}
{validationError && (
<ErrorDisplay
error={validationError}
variant="inline"
severity="error"
size="sm"
dismissible={false}
/>
)}
<div className="space-y-2">
<Label htmlFor="room-name">Room Name</Label>
<Input
id="room-name"
value={name}
onChange={(e) => {
setName(e.target.value);
setValidationError(null);
}}
placeholder="Enter room name"
maxLength={100}
/>
</div>
<div className="space-y-2">
<Label htmlFor="room-type">Room Type</Label>
<Select
options={[
{ value: 'public', label: 'Public' },
{ value: 'private', label: 'Private' },
{ value: 'collaborative', label: 'Collaborative (invite only, max 10)' },
]}
value={type}
onChange={(value) =>
setType(
(Array.isArray(value) ? value[0] : value) as
| 'public'
| 'private'
| 'collaborative',
)
}
name="room-type"
/>
</div>
<div className="flex justify-end gap-2 pt-4">
<Button variant="outline" onClick={onClose} disabled={isCreating}>
Cancel
</Button>
<Button onClick={handleCreate} disabled={isCreating || !name.trim()}>
{isCreating ? 'Creating...' : 'Create Room'}
</Button>
</div>
</div>
</Dialog>
);
}