diff --git a/src/App-old.jsx b/src/App-old.jsx
deleted file mode 100644
index 5c0bf56..0000000
--- a/src/App-old.jsx
+++ /dev/null
@@ -1,1368 +0,0 @@
-import React, { useState, useEffect, useRef } from 'react';
-import { Rondevu } from '@xtr-dev/rondevu-client';
-import toast, { Toaster } from 'react-hot-toast';
-
-const API_URL = 'https://api.ronde.vu';
-
-// Preset RTC configurations
-const RTC_PRESETS = {
- 'ipv4-turn': {
- name: 'IPv4 TURN (Recommended)',
- config: {
- iceServers: [
- { urls: ["stun:57.129.61.67:3478"] },
- {
- urls: [
- "turn:57.129.61.67:3478?transport=tcp",
- "turn:57.129.61.67:3478?transport=udp",
- ],
- username: "webrtcuser",
- credential: "supersecretpassword"
- }
- ],
- }
- },
- 'hostname-turns': {
- name: 'Hostname TURNS (TLS)',
- config: {
- iceServers: [
- { urls: ["stun:turn.share.fish:3478"] },
- {
- urls: [
- "turns:turn.share.fish:5349?transport=tcp",
- "turns:turn.share.fish:5349?transport=udp",
- "turn:turn.share.fish:3478?transport=tcp",
- "turn:turn.share.fish:3478?transport=udp",
- ],
- username: "webrtcuser",
- credential: "supersecretpassword"
- }
- ],
- }
- },
- 'google-stun': {
- name: 'Google STUN Only',
- config: {
- iceServers: [
- { urls: 'stun:stun.l.google.com:19302' },
- { urls: 'stun:stun1.l.google.com:19302' }
- ]
- }
- },
- 'relay-only': {
- name: 'Force TURN Relay (Testing)',
- config: {
- iceServers: [
- { urls: ["stun:57.129.61.67:3478"] },
- {
- urls: [
- "turn:57.129.61.67:3478?transport=tcp",
- "turn:57.129.61.67:3478?transport=udp",
- ],
- username: "webrtcuser",
- credential: "supersecretpassword"
- }
- ],
- iceTransportPolicy: 'relay'
- }
- },
- 'custom': {
- name: 'Custom Configuration',
- config: null // Will be loaded from user input
- }
-};
-
-export default function App() {
- const [client, setClient] = useState(null);
- const [credentials, setCredentials] = useState(null);
- const [myUsername, setMyUsername] = useState(null);
-
- // Setup
- const [setupStep, setSetupStep] = useState('register'); // register, claim, ready
- const [usernameInput, setUsernameInput] = useState('');
-
- // Contacts
- const [contacts, setContacts] = useState([]);
- const [contactInput, setContactInput] = useState('');
- const [onlineUsers, setOnlineUsers] = useState(new Set());
-
- // Chat
- const [activeChats, setActiveChats] = useState({});
- const [selectedChat, setSelectedChat] = useState(null);
- const [messageInputs, setMessageInputs] = useState({});
-
- // Service
- const [serviceHandle, setServiceHandle] = useState(null);
- const chatEndRef = useRef(null);
-
- // Settings
- const [showSettings, setShowSettings] = useState(false);
- const [rtcPreset, setRtcPreset] = useState('ipv4-turn');
- const [customRtcConfig, setCustomRtcConfig] = useState('');
-
- // Get current RTC configuration
- const getCurrentRtcConfig = () => {
- if (rtcPreset === 'custom') {
- try {
- return JSON.parse(customRtcConfig);
- } catch (err) {
- console.error('Invalid custom RTC config:', err);
- return RTC_PRESETS['ipv4-turn'].config;
- }
- }
- return RTC_PRESETS[rtcPreset]?.config || RTC_PRESETS['ipv4-turn'].config;
- };
-
- // Load saved settings
- useEffect(() => {
- const savedPreset = localStorage.getItem('rondevu-rtc-preset');
- const savedCustomConfig = localStorage.getItem('rondevu-rtc-custom');
-
- if (savedPreset) {
- setRtcPreset(savedPreset);
- }
- if (savedCustomConfig) {
- setCustomRtcConfig(savedCustomConfig);
- }
- }, []);
-
- // Save settings when they change
- useEffect(() => {
- localStorage.setItem('rondevu-rtc-preset', rtcPreset);
- }, [rtcPreset]);
-
- useEffect(() => {
- if (customRtcConfig) {
- localStorage.setItem('rondevu-rtc-custom', customRtcConfig);
- }
- }, [customRtcConfig]);
-
- // Load saved data and auto-register
- useEffect(() => {
- const savedCreds = localStorage.getItem('rondevu-chat-credentials');
- const savedUsername = localStorage.getItem('rondevu-chat-username');
- const savedContacts = localStorage.getItem('rondevu-chat-contacts');
-
- const initialize = async () => {
- let clientInstance;
-
- // Load contacts first
- if (savedContacts) {
- try {
- setContacts(JSON.parse(savedContacts));
- } catch (err) {
- console.error('Failed to load contacts:', err);
- }
- }
-
- // Handle credentials
- if (savedCreds) {
- try {
- const creds = JSON.parse(savedCreds);
- setCredentials(creds);
- clientInstance = new Rondevu({ baseUrl: API_URL, credentials: creds });
- setClient(clientInstance);
-
- // If we have username too, go straight to ready
- if (savedUsername) {
- setMyUsername(savedUsername);
- setSetupStep('ready');
- } else {
- // Have creds but no username - go to claim step
- setSetupStep('claim');
- }
- } catch (err) {
- console.error('Failed to load credentials:', err);
- // Invalid saved creds - auto-register
- clientInstance = new Rondevu({ baseUrl: API_URL });
- setClient(clientInstance);
- await autoRegister(clientInstance);
- }
- } else {
- // No saved credentials - auto-register
- console.log('No saved credentials, auto-registering...');
- clientInstance = new Rondevu({ baseUrl: API_URL });
- setClient(clientInstance);
- await autoRegister(clientInstance);
- }
- };
-
- const autoRegister = async (clientInstance) => {
- try {
- console.log('Starting auto-registration...');
- const creds = await clientInstance.register();
- console.log('Registration successful:', creds);
- setCredentials(creds);
- localStorage.setItem('rondevu-chat-credentials', JSON.stringify(creds));
- const newClient = new Rondevu({ baseUrl: API_URL, credentials: creds });
- setClient(newClient);
- setSetupStep('claim');
- } catch (err) {
- console.error('Auto-registration failed:', err);
- toast.error(`Registration failed: ${err.message}`);
- setSetupStep('claim'); // Still allow username claim, might work anyway
- }
- };
-
- initialize();
- }, []);
-
- // Auto-scroll chat
- useEffect(() => {
- chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
- }, [activeChats, selectedChat]);
-
- // Start chat service when ready
- useEffect(() => {
- if (setupStep === 'ready' && myUsername && client && !serviceHandle) {
- startChatService();
- }
- }, [setupStep, myUsername, client]);
-
- // Check online status periodically
- // Note: Online detection by attempting to query services
- // In v0.9.0 there's no direct listServices API, so we check by attempting connection
- useEffect(() => {
- if (setupStep !== 'ready' || !client) return;
-
- const checkOnlineStatus = async () => {
- const online = new Set();
- for (const contact of contacts) {
- try {
- // Try to query the service via discovery endpoint
- const response = await fetch(`${API_URL}/index/${contact}/query`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ serviceFqn: 'chat.rondevu@1.0.0' })
- });
-
- if (response.ok) {
- online.add(contact);
- }
- } catch (err) {
- // User offline or doesn't exist
- }
- }
- setOnlineUsers(online);
- };
-
- checkOnlineStatus();
- const interval = setInterval(checkOnlineStatus, 10000); // Check every 10s
-
- return () => clearInterval(interval);
- }, [contacts, setupStep, client]);
-
- // Claim username
- const handleClaimUsername = async () => {
- if (!client || !usernameInput) return;
- try {
- const claim = await client.usernames.claimUsername(usernameInput);
- client.usernames.saveKeypairToStorage(usernameInput, claim.publicKey, claim.privateKey);
- setMyUsername(usernameInput);
- localStorage.setItem('rondevu-chat-username', usernameInput);
- setSetupStep('ready');
- toast.success(`Welcome, ${usernameInput}!`);
- } catch (err) {
- toast.error(`Error: ${err.message}`);
- }
- };
-
- // Start pooled chat service with durable connections
- const startChatService = async () => {
- if (!client || !myUsername || serviceHandle) return;
-
- try {
- const keypair = client.usernames.loadKeypairFromStorage(myUsername);
- if (!keypair) {
- toast.error('Username keypair not found');
- return;
- }
-
- const service = await client.exposeService({
- username: myUsername,
- privateKey: keypair.privateKey,
- serviceFqn: 'chat.rondevu@1.0.0',
- isPublic: true,
- ttl: 300000, // 5 minutes - service auto-refreshes
- ttlRefreshMargin: 0.2, // Refresh at 80% of TTL
- poolSize: 10, // Support up to 10 simultaneous connections
- rtcConfig: getCurrentRtcConfig(),
- handler: (channel, connectionId) => {
- console.log(`📡 New chat connection: ${connectionId}`);
-
- // Wait for peer to identify themselves
- channel.on('message', (data) => {
- try {
- const msg = JSON.parse(data);
-
- if (msg.type === 'identify') {
- // Peer identified themselves
- setActiveChats(prev => ({
- ...prev,
- [msg.from]: {
- username: msg.from,
- channel,
- connectionId,
- messages: prev[msg.from]?.messages || [],
- status: 'connected'
- }
- }));
-
- // Remove old handler and add new one for chat messages
- channel.off('message');
- channel.on('message', (chatData) => {
- try {
- const chatMsg = JSON.parse(chatData);
- if (chatMsg.type === 'message') {
- setActiveChats(prev => ({
- ...prev,
- [msg.from]: {
- ...prev[msg.from],
- messages: [...(prev[msg.from]?.messages || []), {
- from: msg.from,
- text: chatMsg.text,
- timestamp: Date.now()
- }]
- }
- }));
- }
- } catch (err) {
- console.error('Failed to parse chat message:', err);
- }
- });
-
- // Send acknowledgment
- channel.send(JSON.stringify({
- type: 'identify_ack',
- from: myUsername
- }));
- }
- } catch (err) {
- console.error('Failed to parse identify message:', err);
- }
- });
-
- channel.on('close', () => {
- console.log(`👋 Chat closed: ${connectionId}`);
- setActiveChats(prev => {
- const updated = { ...prev };
- Object.keys(updated).forEach(user => {
- if (updated[user].connectionId === connectionId) {
- updated[user] = { ...updated[user], status: 'disconnected' };
- }
- });
- return updated;
- });
- });
- }
- });
-
- // Start the service
- await service.start();
-
- // Listen for service events
- service.on('connection', (connId) => {
- console.log(`🔗 New connection: ${connId}`);
- });
-
- service.on('disconnection', (connId) => {
- console.log(`🔌 Disconnected: ${connId}`);
- });
-
- service.on('ttl-refreshed', (expiresAt) => {
- console.log(`🔄 Service TTL refreshed, expires at: ${new Date(expiresAt)}`);
- });
-
- service.on('error', (error, context) => {
- console.error(`❌ Service error (${context}):`, error);
- });
-
- setServiceHandle(service);
- console.log('✅ Chat service started');
- } catch (err) {
- console.error('Error starting chat service:', err);
- toast.error(`Failed to start chat: ${err.message}`);
- }
- };
-
- // Add contact
- const handleAddContact = () => {
- if (!contactInput || contacts.includes(contactInput)) {
- toast.error('Invalid or duplicate contact');
- return;
- }
- if (contactInput === myUsername) {
- toast.error("You can't add yourself!");
- return;
- }
-
- const newContacts = [...contacts, contactInput];
- setContacts(newContacts);
- localStorage.setItem('rondevu-chat-contacts', JSON.stringify(newContacts));
- setContactInput('');
- toast.success(`Added ${contactInput}`);
- };
-
- // Remove contact
- const handleRemoveContact = (contact) => {
- const newContacts = contacts.filter(c => c !== contact);
- setContacts(newContacts);
- localStorage.setItem('rondevu-chat-contacts', JSON.stringify(newContacts));
- if (selectedChat === contact) {
- setSelectedChat(null);
- }
- toast.success(`Removed ${contact}`);
- };
-
- // Start chat with contact using durable connection
- const handleStartChat = async (contact) => {
- if (!client || activeChats[contact]?.status === 'connected') {
- setSelectedChat(contact);
- return;
- }
-
- try {
- toast.loading(`Connecting to ${contact}...`, { id: 'connecting' });
-
- // Create durable connection
- const connection = await client.connect(contact, 'chat.rondevu@1.0.0', {
- rtcConfig: getCurrentRtcConfig(),
- maxReconnectAttempts: 5
- });
-
- // Create data channel (must match service pool's channel name)
- const channel = connection.createChannel('rondevu-service');
-
- // Listen for connection events
- connection.on('connected', () => {
- console.log(`✅ Connected to ${contact}`);
- });
-
- connection.on('reconnecting', (attempt, max, delay) => {
- console.log(`🔄 Reconnecting to ${contact} (${attempt}/${max}) in ${delay}ms`);
- toast.loading(`Reconnecting to ${contact}...`, { id: 'reconnecting' });
- });
-
- connection.on('disconnected', () => {
- console.log(`🔌 Disconnected from ${contact}`);
- setActiveChats(prev => ({
- ...prev,
- [contact]: { ...prev[contact], status: 'reconnecting' }
- }));
- });
-
- connection.on('failed', (error) => {
- console.error(`❌ Connection to ${contact} failed:`, error);
- toast.error(`Connection to ${contact} failed`, { id: 'connecting' });
- setActiveChats(prev => ({
- ...prev,
- [contact]: { ...prev[contact], status: 'disconnected' }
- }));
- });
-
- // Wait for acknowledgment
- channel.on('message', (data) => {
- try {
- const msg = JSON.parse(data);
-
- if (msg.type === 'identify_ack') {
- // Connection established
- toast.success(`Connected to ${contact}`, { id: 'connecting' });
-
- setActiveChats(prev => ({
- ...prev,
- [contact]: {
- username: contact,
- channel,
- connection,
- messages: prev[contact]?.messages || [],
- status: 'connected'
- }
- }));
- setSelectedChat(contact);
-
- // Update handler for chat messages
- channel.off('message');
- channel.on('message', (chatData) => {
- try {
- const chatMsg = JSON.parse(chatData);
- if (chatMsg.type === 'message') {
- setActiveChats(prev => ({
- ...prev,
- [contact]: {
- ...prev[contact],
- messages: [...(prev[contact]?.messages || []), {
- from: contact,
- text: chatMsg.text,
- timestamp: Date.now()
- }]
- }
- }));
- }
- } catch (err) {
- console.error('Failed to parse message:', err);
- }
- });
- }
- } catch (err) {
- console.error('Failed to parse ack:', err);
- }
- });
-
- channel.on('close', () => {
- setActiveChats(prev => ({
- ...prev,
- [contact]: { ...prev[contact], status: 'disconnected' }
- }));
- toast.error(`Disconnected from ${contact}`);
- });
-
- // Connect and send identification
- await connection.connect();
-
- channel.send(JSON.stringify({
- type: 'identify',
- from: myUsername
- }));
-
- } catch (err) {
- console.error('Failed to connect:', err);
- toast.error(`Failed to connect to ${contact}`, { id: 'connecting' });
- }
- };
-
- // Send message
- const handleSendMessage = (contact) => {
- const text = messageInputs[contact];
- if (!text || !activeChats[contact]?.channel) return;
-
- const chat = activeChats[contact];
- if (chat.status !== 'connected') {
- toast.error('Not connected');
- return;
- }
-
- try {
- chat.channel.send(JSON.stringify({
- type: 'message',
- text
- }));
-
- setActiveChats(prev => ({
- ...prev,
- [contact]: {
- ...prev[contact],
- messages: [...prev[contact].messages, {
- from: myUsername,
- text,
- timestamp: Date.now()
- }]
- }
- }));
-
- setMessageInputs(prev => ({ ...prev, [contact]: '' }));
- } catch (err) {
- console.error('Failed to send message:', err);
- toast.error('Failed to send message');
- }
- };
-
- // Clear all data
- const handleLogout = () => {
- if (window.confirm('Are you sure you want to logout? This will clear all data.')) {
- localStorage.clear();
- window.location.reload();
- }
- };
-
- if (!client) {
- return
Loading...
;
- }
-
- return (
-
-
-
- {/* Settings Modal */}
- {showSettings && (
-
setShowSettings(false)}>
-
e.stopPropagation()}>
-
WebRTC Configuration
-
-
-
-
-
-
- {rtcPreset === 'custom' && (
-
-
-
- )}
-
- {rtcPreset !== 'custom' && (
-
-
-
- {JSON.stringify(getCurrentRtcConfig(), null, 2)}
-
-
- )}
-
-
-
-
-
-
- )}
-
- {/* Setup Screen */}
- {setupStep !== 'ready' && (
-
-
-
Rondevu Chat
-
Decentralized P2P Chat
-
- {setupStep === 'register' && (
-
- )}
-
- {setupStep === 'claim' && (
-
-
Choose your unique username
-
setUsernameInput(e.target.value.toLowerCase())}
- onKeyPress={(e) => e.key === 'Enter' && handleClaimUsername()}
- style={styles.setupInput}
- autoFocus
- />
-
-
- 3-32 characters, lowercase letters, numbers, and dashes only
-
-
- )}
-
-
- )}
-
- {/* Main Chat Screen */}
- {setupStep === 'ready' && (
-
- {/* Sidebar */}
-
- {/* User Header */}
-
-
-
@{myUsername}
-
- Online
-
-
-
-
-
-
-
-
- {/* Add Contact */}
-
- setContactInput(e.target.value.toLowerCase())}
- onKeyPress={(e) => e.key === 'Enter' && handleAddContact()}
- style={styles.contactInput}
- />
-
-
-
- {/* Contacts List */}
-
-
- Friends ({contacts.length})
-
- {contacts.length === 0 ? (
-
-
No friends yet
-
- Add friends by their username above
-
-
- ) : (
- contacts.map(contact => {
- const isOnline = onlineUsers.has(contact);
- const hasActiveChat = activeChats[contact]?.status === 'connected';
-
- return (
-
hasActiveChat ? setSelectedChat(contact) : handleStartChat(contact)}
- >
-
- {contact[0].toUpperCase()}
-
-
-
-
{contact}
-
- {hasActiveChat ? 'Chatting' : isOnline ? 'Online' : 'Offline'}
-
-
-
-
- );
- })
- )}
-
-
-
- {/* Chat Area */}
-
- {!selectedChat ? (
-
-
Select a friend to chat
-
- Click on a friend from the sidebar to start chatting
-
-
- ) : (
- <>
- {/* Chat Header */}
-
-
- {selectedChat[0].toUpperCase()}
-
-
-
@{selectedChat}
-
- {activeChats[selectedChat]?.status === 'connected' ? (
- <> Connected>
- ) : (
- 'Connecting...'
- )}
-
-
-
-
-
- {/* Messages */}
-
- {(!activeChats[selectedChat] || activeChats[selectedChat].messages.length === 0) ? (
-
-
No messages yet
-
- Send a message to start the conversation
-
-
- ) : (
- activeChats[selectedChat].messages.map((msg, idx) => (
-
-
- {msg.text}
-
-
- {new Date(msg.timestamp).toLocaleTimeString([], {
- hour: '2-digit',
- minute: '2-digit'
- })}
-
-
- ))
- )}
-
-
-
- {/* Input */}
-
- setMessageInputs(prev => ({
- ...prev,
- [selectedChat]: e.target.value
- }))}
- onKeyPress={(e) => {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- handleSendMessage(selectedChat);
- }
- }}
- disabled={activeChats[selectedChat]?.status !== 'connected'}
- style={styles.messageInput}
- autoFocus
- />
-
-
- >
- )}
-
-
- )}
-
- );
-}
-
-const styles = {
- container: {
- height: '100vh',
- background: '#1a1a1a',
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
- },
- loading: {
- height: '100vh',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- color: '#e0e0e0',
- fontSize: '24px'
- },
- setupScreen: {
- height: '100vh',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- padding: '20px'
- },
- setupBox: {
- background: '#2a2a2a',
- borderRadius: '16px',
- padding: '40px',
- maxWidth: '400px',
- width: '100%',
- boxShadow: '0 20px 60px rgba(0,0,0,0.5)',
- textAlign: 'center',
- border: '1px solid #3a3a3a'
- },
- setupTitle: {
- fontSize: '2.5em',
- margin: '0 0 10px 0',
- color: '#e0e0e0'
- },
- setupSubtitle: {
- fontSize: '1.1em',
- color: '#a0a0a0',
- margin: '0 0 30px 0'
- },
- setupDesc: {
- color: '#a0a0a0',
- marginBottom: '20px'
- },
- setupInput: {
- width: '100%',
- padding: '15px',
- fontSize: '16px',
- border: '1px solid #3a3a3a',
- background: '#1a1a1a',
- color: '#e0e0e0',
- borderRadius: '8px',
- marginBottom: '15px',
- boxSizing: 'border-box',
- outline: 'none',
- },
- setupButton: {
- width: '100%',
- padding: '15px',
- fontSize: '16px',
- fontWeight: '600',
- background: '#4a9eff',
- color: 'white',
- border: 'none',
- borderRadius: '8px',
- cursor: 'pointer',
- },
- setupHint: {
- fontSize: '12px',
- color: '#808080',
- marginTop: '10px'
- },
- mainScreen: {
- height: '100vh',
- display: 'flex'
- },
- sidebar: {
- width: '320px',
- background: '#2a2a2a',
- borderRight: '1px solid #3a3a3a',
- display: 'flex',
- flexDirection: 'column'
- },
- userHeader: {
- padding: '20px',
- borderBottom: '1px solid #3a3a3a',
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center'
- },
- userHeaderName: {
- fontSize: '18px',
- fontWeight: '600',
- color: '#e0e0e0'
- },
- userHeaderStatus: {
- fontSize: '12px',
- color: '#a0a0a0',
- marginTop: '4px',
- display: 'flex',
- alignItems: 'center',
- gap: '5px'
- },
- onlineDot: {
- width: '8px',
- height: '8px',
- borderRadius: '50%',
- background: '#4caf50',
- display: 'inline-block'
- },
- settingsBtn: {
- padding: '8px 12px',
- background: '#3a3a3a',
- color: '#e0e0e0',
- border: 'none',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '18px',
- lineHeight: '1'
- },
- logoutBtn: {
- padding: '8px 12px',
- background: '#3a3a3a',
- color: '#e0e0e0',
- border: 'none',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '14px'
- },
- addContactBox: {
- padding: '15px',
- borderBottom: '1px solid #3a3a3a',
- display: 'flex',
- gap: '8px'
- },
- contactInput: {
- flex: 1,
- padding: '10px',
- border: '1px solid #3a3a3a',
- background: '#1a1a1a',
- color: '#e0e0e0',
- borderRadius: '6px',
- fontSize: '14px',
- outline: 'none'
- },
- addBtn: {
- padding: '10px 15px',
- background: '#4a9eff',
- color: 'white',
- border: 'none',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '14px'
- },
- contactsList: {
- flex: 1,
- overflowY: 'auto'
- },
- contactsHeader: {
- padding: '15px 20px',
- fontSize: '12px',
- fontWeight: '600',
- color: '#808080',
- textTransform: 'uppercase',
- letterSpacing: '0.5px'
- },
- emptyState: {
- padding: '40px 20px',
- textAlign: 'center',
- color: '#808080'
- },
- contactItem: {
- padding: '15px 20px',
- display: 'flex',
- alignItems: 'center',
- gap: '12px',
- cursor: 'pointer',
- transition: 'background 0.2s',
- borderBottom: '1px solid #3a3a3a'
- },
- contactItemActive: {
- background: '#3a3a3a'
- },
- contactAvatar: {
- width: '40px',
- height: '40px',
- borderRadius: '50%',
- background: '#4a9eff',
- color: 'white',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- fontSize: '18px',
- fontWeight: '600',
- position: 'relative'
- },
- contactDot: {
- position: 'absolute',
- bottom: '0',
- right: '0',
- width: '12px',
- height: '12px',
- borderRadius: '50%',
- border: '2px solid #2a2a2a'
- },
- contactName: {
- fontSize: '15px',
- fontWeight: '600',
- color: '#e0e0e0'
- },
- contactStatus: {
- fontSize: '12px',
- color: '#a0a0a0',
- marginTop: '2px'
- },
- removeBtn: {
- padding: '4px 8px',
- background: 'transparent',
- border: 'none',
- cursor: 'pointer',
- fontSize: '16px',
- color: '#808080',
- opacity: 0.6,
- },
- chatArea: {
- flex: 1,
- background: '#1a1a1a',
- display: 'flex',
- flexDirection: 'column'
- },
- emptyChat: {
- flex: 1,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- color: '#808080'
- },
- chatHeader: {
- padding: '20px',
- borderBottom: '1px solid #3a3a3a',
- display: 'flex',
- alignItems: 'center',
- gap: '15px',
- background: '#2a2a2a'
- },
- chatHeaderAvatar: {
- width: '48px',
- height: '48px',
- borderRadius: '50%',
- background: '#4a9eff',
- color: 'white',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- fontSize: '20px',
- fontWeight: '600'
- },
- chatHeaderName: {
- fontSize: '18px',
- fontWeight: '600',
- color: '#e0e0e0'
- },
- chatHeaderStatus: {
- fontSize: '13px',
- color: '#a0a0a0',
- marginTop: '4px',
- display: 'flex',
- alignItems: 'center',
- gap: '5px'
- },
- closeChatBtn: {
- padding: '8px 12px',
- background: '#3a3a3a',
- border: 'none',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '14px',
- color: '#e0e0e0'
- },
- messagesArea: {
- flex: 1,
- overflowY: 'auto',
- padding: '20px',
- background: '#1a1a1a'
- },
- emptyMessages: {
- height: '100%',
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- color: '#808080'
- },
- message: {
- marginBottom: '12px',
- display: 'flex',
- flexDirection: 'column',
- maxWidth: '70%'
- },
- messageMe: {
- alignSelf: 'flex-end',
- alignItems: 'flex-end'
- },
- messageThem: {
- alignSelf: 'flex-start',
- alignItems: 'flex-start'
- },
- messageText: {
- padding: '12px 16px',
- borderRadius: '16px',
- fontSize: '15px',
- lineHeight: '1.4',
- wordWrap: 'break-word',
- boxShadow: '0 1px 2px rgba(0,0,0,0.2)'
- },
- messageTime: {
- fontSize: '11px',
- color: '#808080',
- marginTop: '4px',
- padding: '0 8px'
- },
- inputArea: {
- padding: '20px',
- borderTop: '1px solid #3a3a3a',
- display: 'flex',
- gap: '12px',
- background: '#2a2a2a'
- },
- messageInput: {
- flex: 1,
- padding: '12px 16px',
- border: '1px solid #3a3a3a',
- background: '#1a1a1a',
- color: '#e0e0e0',
- borderRadius: '24px',
- fontSize: '15px',
- outline: 'none',
- },
- sendBtn: {
- padding: '12px 24px',
- borderRadius: '24px',
- background: '#4a9eff',
- color: 'white',
- border: 'none',
- cursor: 'pointer',
- fontSize: '15px',
- fontWeight: '600',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- },
- modalOverlay: {
- position: 'fixed',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- background: 'rgba(0, 0, 0, 0.8)',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- zIndex: 1000
- },
- modalContent: {
- background: '#2a2a2a',
- borderRadius: '12px',
- padding: '24px',
- maxWidth: '600px',
- width: '90%',
- maxHeight: '80vh',
- overflowY: 'auto',
- boxShadow: '0 4px 20px rgba(0, 0, 0, 0.5)'
- },
- modalTitle: {
- fontSize: '24px',
- color: '#e0e0e0',
- marginBottom: '20px',
- fontWeight: '600'
- },
- settingsSection: {
- marginBottom: '20px'
- },
- settingsLabel: {
- display: 'block',
- color: '#e0e0e0',
- marginBottom: '8px',
- fontSize: '14px',
- fontWeight: '500'
- },
- settingsSelect: {
- width: '100%',
- padding: '10px',
- background: '#1a1a1a',
- color: '#e0e0e0',
- border: '1px solid #3a3a3a',
- borderRadius: '6px',
- fontSize: '14px',
- outline: 'none',
- cursor: 'pointer'
- },
- settingsTextarea: {
- width: '100%',
- padding: '12px',
- background: '#1a1a1a',
- color: '#e0e0e0',
- border: '1px solid #3a3a3a',
- borderRadius: '6px',
- fontSize: '13px',
- fontFamily: 'monospace',
- outline: 'none',
- resize: 'vertical'
- },
- settingsPreview: {
- width: '100%',
- padding: '12px',
- background: '#1a1a1a',
- color: '#4a9eff',
- border: '1px solid #3a3a3a',
- borderRadius: '6px',
- fontSize: '13px',
- fontFamily: 'monospace',
- overflowX: 'auto',
- margin: 0
- },
- settingsHint: {
- fontSize: '12px',
- color: '#808080',
- marginTop: '6px'
- },
- modalActions: {
- display: 'flex',
- justifyContent: 'flex-end',
- gap: '12px',
- marginTop: '24px'
- },
- modalBtn: {
- padding: '10px 20px',
- background: '#4a9eff',
- color: 'white',
- border: 'none',
- borderRadius: '6px',
- cursor: 'pointer',
- fontSize: '14px',
- fontWeight: '600'
- }
-};
-
-// Add hover effects via CSS
-if (typeof document !== 'undefined') {
- const style = document.createElement('style');
- style.textContent = `
- button:hover:not(:disabled) {
- opacity: 0.9;
- transform: scale(1.02);
- }
- button:active:not(:disabled) {
- transform: scale(0.98);
- }
- button:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .contact-item:hover {
- background: #333333 !important;
- }
- .contact-item:active {
- background: #2a2a2a !important;
- }
- input:focus {
- border-color: #4a9eff !important;
- }
- `;
- document.head.appendChild(style);
-}
diff --git a/src/components/ActionSelector.jsx b/src/components/ActionSelector.jsx
deleted file mode 100644
index 1a0fd9a..0000000
--- a/src/components/ActionSelector.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import QRScanner from './QRScanner';
-
-function ActionSelector({ action, onSelectAction, onScanComplete, onScanCancel, log }) {
- return (
-
-
Chat Demo
-
-
-
-
-
- {action === 'scan' && (
-
- )}
-
- );
-}
-
-export default ActionSelector;
diff --git a/src/components/ChatView.jsx b/src/components/ChatView.jsx
deleted file mode 100644
index 53a6127..0000000
--- a/src/components/ChatView.jsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { useRef } from 'react';
-import Message from './Message';
-import FileUploadProgress from './FileUploadProgress';
-
-function ChatView({
- connectedPeer,
- currentConnectionId,
- messages,
- messageInput,
- setMessageInput,
- channelReady,
- logs,
- fileUploadProgress,
- onSendMessage,
- onFileSelect,
- onDisconnect,
- onDownloadFile,
- onCancelUpload
-}) {
- const fileInputRef = useRef(null);
-
- return (
-
-
-
-
Connected
-
- Peer: {connectedPeer || 'Unknown'} • ID: {currentConnectionId}
-
-
-
-
-
-
- {messages.length === 0 ? (
-
No messages yet. Start chatting!
- ) : (
- messages.map((msg, idx) => (
-
- ))
- )}
-
-
- {fileUploadProgress && (
-
- )}
-
-
-
-
- setMessageInput(e.target.value)}
- onKeyPress={(e) => e.key === 'Enter' && onSendMessage()}
- placeholder="Type a message..."
- disabled={!channelReady}
- />
-
-
-
- {logs.length > 0 && (
-
- Activity Log ({logs.length})
-
- {logs.map((log, idx) => (
-
- [{log.timestamp}] {log.message}
-
- ))}
-
-
- )}
-
- );
-}
-
-export default ChatView;
diff --git a/src/components/ConnectionForm.jsx b/src/components/ConnectionForm.jsx
deleted file mode 100644
index 86120c6..0000000
--- a/src/components/ConnectionForm.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import QRCodeDisplay from './QRCodeDisplay';
-
-function ConnectionForm({
- action,
- connectionId,
- setConnectionId,
- connectionStatus,
- qrCodeUrl,
- currentConnectionId,
- onConnect,
- onBack
-}) {
- return (
-
-
{action === 'create' ? 'Create Connection' : 'Join Connection'}
-
-
-
-
setConnectionId(e.target.value)}
- placeholder={action === 'create' ? 'Auto-generated if empty' : 'Enter connection ID'}
- autoFocus={action === 'connect'}
- />
- {action === 'create' && !connectionId && (
-
Leave empty to auto-generate a random ID
- )}
-
-
-
-
-
-
-
- {qrCodeUrl && connectionStatus === 'connecting' && action === 'create' && (
-
- )}
-
-
- );
-}
-
-export default ConnectionForm;
diff --git a/src/components/FileUploadProgress.jsx b/src/components/FileUploadProgress.jsx
deleted file mode 100644
index 85a0342..0000000
--- a/src/components/FileUploadProgress.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-function FileUploadProgress({ fileName, progress, onCancel }) {
- return (
-
- );
-}
-
-export default FileUploadProgress;
diff --git a/src/components/Header.jsx b/src/components/Header.jsx
deleted file mode 100644
index 5a788cd..0000000
--- a/src/components/Header.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-function Header() {
- return (
-
- );
-}
-
-export default Header;
diff --git a/src/components/Message.jsx b/src/components/Message.jsx
deleted file mode 100644
index b1b1817..0000000
--- a/src/components/Message.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
-function Message({ message, onDownload }) {
- const isFile = message.messageType === 'file';
-
- return (
-
- {isFile ? (
-
-
📎
-
-
{message.file.name}
-
{(message.file.size / 1024).toFixed(2)} KB
-
-
-
- ) : (
-
{message.text}
- )}
-
{message.timestamp.toLocaleTimeString()}
-
- );
-}
-
-export default Message;
diff --git a/src/components/MethodSelector.jsx b/src/components/MethodSelector.jsx
deleted file mode 100644
index f0c505d..0000000
--- a/src/components/MethodSelector.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-function MethodSelector({ action, onSelectMethod, onBack }) {
- return (
-
-
{action === 'create' ? 'Create' : 'Join'} by...
-
-
- {action === 'join' && (
-
- )}
-
-
-
-
- );
-}
-
-export default MethodSelector;
diff --git a/src/components/QRCodeDisplay.jsx b/src/components/QRCodeDisplay.jsx
deleted file mode 100644
index 6418ac6..0000000
--- a/src/components/QRCodeDisplay.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-function QRCodeDisplay({ qrCodeUrl, connectionId }) {
- if (!qrCodeUrl) return null;
-
- return (
-
-
Scan to connect:
-

-
{connectionId}
-
- );
-}
-
-export default QRCodeDisplay;
diff --git a/src/components/QRScanner.jsx b/src/components/QRScanner.jsx
deleted file mode 100644
index 4d6dbc7..0000000
--- a/src/components/QRScanner.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { useRef, useEffect } from 'react';
-import { BrowserQRCodeReader } from '@zxing/library';
-
-function QRScanner({ onScan, onCancel, log }) {
- const videoRef = useRef(null);
- const scannerRef = useRef(null);
-
- useEffect(() => {
- startScanning();
- return () => {
- stopScanning();
- };
- }, []);
-
- const startScanning = async () => {
- try {
- scannerRef.current = new BrowserQRCodeReader();
- log('Starting QR scanner...', 'info');
-
- const videoInputDevices = await scannerRef.current.listVideoInputDevices();
-
- if (videoInputDevices.length === 0) {
- log('No camera found', 'error');
- return;
- }
-
- // Prefer back camera (environment-facing)
- let selectedDeviceId = videoInputDevices[0].deviceId;
- const backCamera = videoInputDevices.find(device =>
- device.label.toLowerCase().includes('back') ||
- device.label.toLowerCase().includes('rear') ||
- device.label.toLowerCase().includes('environment')
- );
-
- if (backCamera) {
- selectedDeviceId = backCamera.deviceId;
- log('Using back camera', 'info');
- } else {
- log('Back camera not found, using default', 'info');
- }
-
- scannerRef.current.decodeFromVideoDevice(
- selectedDeviceId,
- videoRef.current,
- (result, err) => {
- if (result) {
- const scannedId = result.getText();
- log(`Scanned: ${scannedId}`, 'success');
- stopScanning();
- onScan(scannedId);
- }
- }
- );
- } catch (error) {
- log(`Scanner error: ${error.message}`, 'error');
- }
- };
-
- const stopScanning = () => {
- if (scannerRef.current) {
- scannerRef.current.reset();
- log('Scanner stopped', 'info');
- }
- };
-
- return (
-
-
-
-
- );
-}
-
-export default QRScanner;
diff --git a/src/components/TopicsList.jsx b/src/components/TopicsList.jsx
deleted file mode 100644
index 1618155..0000000
--- a/src/components/TopicsList.jsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { useState, useEffect } from 'react';
-
-function TopicsList({ rdv, onClose }) {
- const [topics, setTopics] = useState([]);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
- const [page, setPage] = useState(1);
- const [pagination, setPagination] = useState(null);
- const [limit] = useState(20);
-
- useEffect(() => {
- loadTopics();
- }, [page]);
-
- const loadTopics = async () => {
- setLoading(true);
- setError(null);
- try {
- const response = await rdv.api.listTopics(page, limit);
- setTopics(response.topics);
- setPagination(response.pagination);
- } catch (err) {
- setError(err.message);
- } finally {
- setLoading(false);
- }
- };
-
- const handleRefresh = () => {
- loadTopics();
- };
-
- const handlePrevPage = () => {
- if (page > 1) {
- setPage(page - 1);
- }
- };
-
- const handleNextPage = () => {
- if (pagination?.hasMore) {
- setPage(page + 1);
- }
- };
-
- return (
-
-
e.stopPropagation()}>
-
-
Active Topics
-
-
-
-
- {error && (
-
- Error: {error}
-
- )}
-
- {loading ? (
-
Loading topics...
- ) : (
- <>
- {topics.length === 0 ? (
-
- No active topics found. Be the first to create one!
-
- ) : (
-
- {topics.map((topic) => (
-
-
{topic.topic}
-
- {topic.count} {topic.count === 1 ? 'peer' : 'peers'}
-
-
- ))}
-
- )}
-
- {pagination && (
-
-
-
- Page {pagination.page} of {Math.ceil(pagination.total / pagination.limit)}
- {' '}({pagination.total} total)
-
-
-
- )}
- >
- )}
-
-
-
-
-
-
-
-
- );
-}
-
-export default TopicsList;