From 9163e5166c3c0c25c8350ab469fd7c1da17e7830 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Sun, 16 Nov 2025 20:17:27 +0100 Subject: [PATCH] Replace preset topics with dynamic topic listing from server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- package-lock.json | 8 +- package.json | 2 +- src/App.jsx | 204 ++++++++++++++++++++++++++++++++++++---------- src/index.css | 19 +++++ 4 files changed, 183 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 884621c..aab1b62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 8c1bc2f..42bc119 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.jsx b/src/App.jsx index 4bda51c..df841e3 100644 --- a/src/App.jsx +++ b/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,52 +628,122 @@ export default function App() { {activeTab === 'discover' && (

Discover Peers

-

Search for peers by topic

+

Browse topics to find peers

-
- -
- setSearchTopic(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && handleDiscoverPeers()} - style={{...styles.input, flex: 1}} - /> - -
-
- - {discoveredOffers.length > 0 && ( + {!selectedTopic ? (
-

Found {discoveredOffers.length} Peer(s)

- {discoveredOffers.map(offer => { - const isConnected = myConnections.some(c => c.id === offer.id); - const isMine = credentials && offer.peerId === credentials.peerId; +
+

Active Topics ({topics.length})

+ +
- return ( -
-
-
{offer.topics.join(', ')}
-
- Peer: {offer.peerId.substring(0, 16)}... + {topicsLoading ? ( +
+
+
Loading topics...
+
+ ) : topicsError ? ( +
+
+
⚠️
+
Failed to load topics
+
{topicsError}
+ +
+
+ ) : topics.length === 0 ? ( +
+
📭
+
No active topics
+
Create an offer to start a new topic
+
+ ) : ( +
+ {topics.map(topic => ( +
handleDiscoverPeers(topic.topic)} + > +
💬
+
{topic.topic}
+
+ {topic.activePeers} {topic.activePeers === 1 ? 'peer' : 'peers'}
+ ))} +
+ )} +
+ ) : ( +
+
+ +

Topic: {selectedTopic}

+
- {isMine ? ( -
Your offer
- ) : isConnected ? ( -
✓ Connected
- ) : ( - - )} + {discoveredOffers.length > 0 ? ( +
+

+ Found {discoveredOffers.length} peer(s) +

+ {discoveredOffers.map(offer => { + const isConnected = myConnections.some(c => c.id === offer.id); + const isMine = credentials && offer.peerId === credentials.peerId; + + return ( +
+
+
{offer.topics.join(', ')}
+
+ Peer: {offer.peerId.substring(0, 16)}... +
+
+ + {isMine ? ( +
Your offer
+ ) : isConnected ? ( +
✓ Connected
+ ) : ( + + )} +
+ ); + })} +
+ ) : ( +
+
🔍
+
No peers available for this topic
+
+ Try creating an offer or check back later
- ); - })} +
+ )}
)}
@@ -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' } }; diff --git a/src/index.css b/src/index.css index 82c80d6..a9cf4b5 100644 --- a/src/index.css +++ b/src/index.css @@ -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); +}