mirror of
https://github.com/xtr-dev/rondevu-demo.git
synced 2025-12-10 02:43:23 +00:00
Replace preset topics with dynamic topic listing from server
Changed discover page to fetch and display real topics from the API: - Added fetchTopics() to call client.offers.getTopics() - Display actual topic names and active peer counts - Added loading, error, and empty states - Added refresh button to reload topics - Improved UX with better error handling Updated to @xtr-dev/rondevu-client@0.7.4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "rondevu-demo",
|
||||
"version": "0.5.0",
|
||||
"dependencies": {
|
||||
"@xtr-dev/rondevu-client": "^0.7.3",
|
||||
"@xtr-dev/rondevu-client": "^0.7.4",
|
||||
"@zxing/library": "^0.21.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18.2.0",
|
||||
@@ -1171,9 +1171,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@xtr-dev/rondevu-client": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.7.3.tgz",
|
||||
"integrity": "sha512-WcKc+q1JOh/v5doX0PaX9pYIJa0ofJHgxUo+xdOIjuBjUQuQW+F1G71NxtzCia2A62VPJdctL5TgADNKYmlIHQ==",
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.7.4.tgz",
|
||||
"integrity": "sha512-MPmw9iSc7LxLduu4TtVrcPvBl/Cuul5sqgOAKUWW7XYXYAObFYUtu9RcbWShR+a6Bwwx7oHadz5I2U8eWsebXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xtr-dev/rondevu-client": "^0.5.1"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"deploy": "npm run build && npx wrangler pages deploy dist --project-name=rondevu-demo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xtr-dev/rondevu-client": "^0.7.3",
|
||||
"@xtr-dev/rondevu-client": "^0.7.4",
|
||||
"@zxing/library": "^0.21.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18.2.0",
|
||||
|
||||
158
src/App.jsx
158
src/App.jsx
@@ -33,8 +33,11 @@ export default function App() {
|
||||
const [myConnections, setMyConnections] = useState([]);
|
||||
|
||||
// Discovery state
|
||||
const [searchTopic, setSearchTopic] = useState('demo-chat');
|
||||
const [selectedTopic, setSelectedTopic] = useState(null);
|
||||
const [discoveredOffers, setDiscoveredOffers] = useState([]);
|
||||
const [topics, setTopics] = useState([]);
|
||||
const [topicsLoading, setTopicsLoading] = useState(false);
|
||||
const [topicsError, setTopicsError] = useState(null);
|
||||
|
||||
// Messages
|
||||
const [messages, setMessages] = useState([]);
|
||||
@@ -192,8 +195,32 @@ export default function App() {
|
||||
}
|
||||
};
|
||||
|
||||
// Discover peers
|
||||
const handleDiscoverPeers = async () => {
|
||||
// Fetch available topics from server
|
||||
const fetchTopics = async () => {
|
||||
if (!client) return;
|
||||
|
||||
try {
|
||||
setTopicsLoading(true);
|
||||
setTopicsError(null);
|
||||
const result = await client.offers.getTopics({ limit: 100 });
|
||||
setTopics(result.topics);
|
||||
} catch (err) {
|
||||
console.error('Error fetching topics:', err);
|
||||
setTopicsError(err.message);
|
||||
} finally {
|
||||
setTopicsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch topics when discover tab is opened
|
||||
useEffect(() => {
|
||||
if (activeTab === 'discover' && topics.length === 0 && !topicsLoading && client) {
|
||||
fetchTopics();
|
||||
}
|
||||
}, [activeTab, client]);
|
||||
|
||||
// Discover peers by topic
|
||||
const handleDiscoverPeers = async (topicName) => {
|
||||
if (!client) return;
|
||||
|
||||
if (!client.isAuthenticated()) {
|
||||
@@ -202,13 +229,14 @@ export default function App() {
|
||||
}
|
||||
|
||||
try {
|
||||
const offers = await client.offers.findByTopic(searchTopic.trim(), {limit: 50});
|
||||
setSelectedTopic(topicName);
|
||||
const offers = await client.offers.findByTopic(topicName, {limit: 50});
|
||||
setDiscoveredOffers(offers);
|
||||
|
||||
if (offers.length === 0) {
|
||||
toast.error('No peers found!');
|
||||
toast(`No peers found for "${topicName}"`);
|
||||
} else {
|
||||
toast.success(`Found ${offers.length} peer(s)`);
|
||||
toast.success(`Found ${offers.length} peer(s) for "${topicName}"`);
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error(`Error: ${err.message}`);
|
||||
@@ -600,27 +628,87 @@ export default function App() {
|
||||
{activeTab === 'discover' && (
|
||||
<div>
|
||||
<h2>Discover Peers</h2>
|
||||
<p style={styles.desc}>Search for peers by topic</p>
|
||||
<p style={styles.desc}>Browse topics to find peers</p>
|
||||
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<label style={styles.label}>Topic:</label>
|
||||
<div style={{display: 'flex', gap: '10px'}}>
|
||||
<input
|
||||
type="text"
|
||||
value={searchTopic}
|
||||
onChange={(e) => setSearchTopic(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleDiscoverPeers()}
|
||||
style={{...styles.input, flex: 1}}
|
||||
/>
|
||||
<button onClick={handleDiscoverPeers} style={styles.btnPrimary}>
|
||||
🔍 Search
|
||||
{!selectedTopic ? (
|
||||
<div>
|
||||
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px'}}>
|
||||
<h3>Active Topics ({topics.length})</h3>
|
||||
<button onClick={fetchTopics} style={styles.btnSecondary} disabled={topicsLoading}>
|
||||
{topicsLoading ? '⟳ Loading...' : '🔄 Refresh'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{topicsLoading ? (
|
||||
<div style={{textAlign: 'center', padding: '60px', color: '#999'}}>
|
||||
<div style={{fontSize: '3em', marginBottom: '10px'}}>⟳</div>
|
||||
<div>Loading topics...</div>
|
||||
</div>
|
||||
) : topicsError ? (
|
||||
<div style={{textAlign: 'center', padding: '40px'}}>
|
||||
<div style={{...styles.card, background: '#ffebee', color: '#c62828', border: '2px solid #ef9a9a'}}>
|
||||
<div style={{fontSize: '2em', marginBottom: '10px'}}>⚠️</div>
|
||||
<div style={{fontWeight: '600', marginBottom: '5px'}}>Failed to load topics</div>
|
||||
<div style={{fontSize: '0.9em'}}>{topicsError}</div>
|
||||
<button onClick={fetchTopics} style={{...styles.btnPrimary, marginTop: '15px'}}>
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{discoveredOffers.length > 0 && (
|
||||
) : topics.length === 0 ? (
|
||||
<div style={{textAlign: 'center', padding: '60px', color: '#999'}}>
|
||||
<div style={{fontSize: '3em', marginBottom: '10px'}}>📭</div>
|
||||
<div style={{fontWeight: '600', marginBottom: '5px'}}>No active topics</div>
|
||||
<div style={{fontSize: '0.9em'}}>Create an offer to start a new topic</div>
|
||||
</div>
|
||||
) : (
|
||||
<div style={styles.topicsGrid}>
|
||||
{topics.map(topic => (
|
||||
<div
|
||||
key={topic.topic}
|
||||
className="topic-card-hover"
|
||||
onClick={() => handleDiscoverPeers(topic.topic)}
|
||||
>
|
||||
<div style={{fontSize: '2.5em', marginBottom: '10px'}}>💬</div>
|
||||
<div style={{fontWeight: '600', marginBottom: '5px', wordBreak: 'break-word'}}>{topic.topic}</div>
|
||||
<div style={{
|
||||
fontSize: '0.85em',
|
||||
color: '#667eea',
|
||||
fontWeight: '600',
|
||||
background: '#f0f2ff',
|
||||
padding: '4px 12px',
|
||||
borderRadius: '12px',
|
||||
display: 'inline-block',
|
||||
marginTop: '5px'
|
||||
}}>
|
||||
{topic.activePeers} {topic.activePeers === 1 ? 'peer' : 'peers'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<h3>Found {discoveredOffers.length} Peer(s)</h3>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedTopic(null);
|
||||
setDiscoveredOffers([]);
|
||||
fetchTopics(); // Refresh topics when going back
|
||||
}}
|
||||
style={{...styles.btnSecondary, marginBottom: '10px'}}
|
||||
>
|
||||
← Back to Topics
|
||||
</button>
|
||||
<h3>Topic: {selectedTopic}</h3>
|
||||
</div>
|
||||
|
||||
{discoveredOffers.length > 0 ? (
|
||||
<div>
|
||||
<p style={{marginBottom: '15px', color: '#666'}}>
|
||||
Found {discoveredOffers.length} peer(s)
|
||||
</p>
|
||||
{discoveredOffers.map(offer => {
|
||||
const isConnected = myConnections.some(c => c.id === offer.id);
|
||||
const isMine = credentials && offer.peerId === credentials.peerId;
|
||||
@@ -647,6 +735,16 @@ export default function App() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{textAlign: 'center', padding: '40px', color: '#999'}}>
|
||||
<div style={{fontSize: '3em'}}>🔍</div>
|
||||
<div>No peers available for this topic</div>
|
||||
<div style={{fontSize: '0.9em', marginTop: '10px'}}>
|
||||
Try creating an offer or check back later
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -858,6 +956,16 @@ const styles = {
|
||||
fontWeight: '600',
|
||||
width: '100%'
|
||||
},
|
||||
btnSecondary: {
|
||||
padding: '10px 20px',
|
||||
background: '#f5f5f5',
|
||||
color: '#333',
|
||||
border: '2px solid #e0e0e0',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '0.95em',
|
||||
fontWeight: '600'
|
||||
},
|
||||
btnDanger: {
|
||||
padding: '12px 24px',
|
||||
background: '#f44336',
|
||||
@@ -895,5 +1003,11 @@ const styles = {
|
||||
color: 'white',
|
||||
opacity: 0.8,
|
||||
fontSize: '0.9em'
|
||||
},
|
||||
topicsGrid: {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
|
||||
gap: '15px',
|
||||
marginTop: '20px'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -997,3 +997,22 @@ input[type="text"]:disabled {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(85, 104, 211, 0.4);
|
||||
}
|
||||
|
||||
/* Topic cards grid for discovery */
|
||||
.topic-card-hover {
|
||||
padding: 25px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.topic-card-hover:hover {
|
||||
border-color: #667eea;
|
||||
background: #fafbfc;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user