From 7702d3711b209fbf2c1428300f70f6ea52c0fb9d Mon Sep 17 00:00:00 2001
From: TLimoges33 <125313326+TLimoges33@users.noreply.github.com>
Date: Thu, 28 Aug 2025 17:19:46 +0000
Subject: [PATCH] frontend: add Integration manager actions (remove/sync) and
Guilds UI
---
modern/frontend/src/App.jsx | 2 +
modern/frontend/src/Guilds.jsx | 55 ++++++++++++++++++++++++++++
modern/frontend/src/Integrations.jsx | 44 ++++++++++++++++++++--
3 files changed, 98 insertions(+), 3 deletions(-)
create mode 100644 modern/frontend/src/Guilds.jsx
diff --git a/modern/frontend/src/App.jsx b/modern/frontend/src/App.jsx
index 4c7e150..7a3bfc1 100644
--- a/modern/frontend/src/App.jsx
+++ b/modern/frontend/src/App.jsx
@@ -1,5 +1,6 @@
import React from 'react'
import Integrations from './Integrations'
+import Guilds from './Guilds'
export default function App(){
return (
@@ -7,6 +8,7 @@ export default function App(){
LifeRPG Modern
Welcome — frontend scaffold. Connect to backend at /api/v1.
+
)
}
diff --git a/modern/frontend/src/Guilds.jsx b/modern/frontend/src/Guilds.jsx
new file mode 100644
index 0000000..c23f45e
--- /dev/null
+++ b/modern/frontend/src/Guilds.jsx
@@ -0,0 +1,55 @@
+import React, {useState, useEffect} from 'react'
+
+const API = (path, opts) => fetch(path, {...(opts||{}), credentials: 'include'}).then(r=>r.json())
+
+export default function Guilds(){
+ const [guilds, setGuilds] = useState([])
+ const [name, setName] = useState('')
+ const [members, setMembers] = useState([])
+ const [selectedGuild, setSelectedGuild] = useState(null)
+ const [userId] = useState(1)
+
+ useEffect(()=>{ API('/api/v1/guilds').then(setGuilds).catch(()=>setGuilds([])) }, [])
+
+ function createGuild(){
+ if(!name.trim()) return
+ API('/api/v1/guilds', {method:'POST', body: JSON.stringify({name, owner_id: userId}), headers: {'Content-Type':'application/json'}})
+ .then(g=> setGuilds([...guilds, g]))
+ .catch(()=>{})
+ }
+
+ function loadMembers(gid){
+ API(`/api/v1/guilds/${gid}/members`).then(setMembers).catch(()=>setMembers([]))
+ setSelectedGuild(gid)
+ }
+
+ function addMember(gid){
+ const uid = prompt('User ID to add:')
+ if(!uid) return
+ API(`/api/v1/guilds/${gid}/members`, {method:'POST', body: JSON.stringify({user_id: parseInt(uid)}), headers: {'Content-Type':'application/json'}})
+ .then(()=> loadMembers(gid))
+ .catch(()=> alert('failed'))
+ }
+
+ return (
+
+
Guilds
+
+ setName(e.target.value)} placeholder="Guild name" />
+
+
+
+ {guilds && guilds.length ? guilds.map(g=> (
+ -
+ {g.name} (owner: {g.owner_id})
+
+
+ )): - No guilds
}
+
+
Members
+
+ {members && members.length ? members.map(m=> (- User {m.user_id} — {m.role}
)) : - No members
}
+
+
+ )
+}
diff --git a/modern/frontend/src/Integrations.jsx b/modern/frontend/src/Integrations.jsx
index 908fa29..b0d7457 100644
--- a/modern/frontend/src/Integrations.jsx
+++ b/modern/frontend/src/Integrations.jsx
@@ -6,6 +6,8 @@ export default function Integrations(){
const [integrations, setIntegrations] = useState([])
const [events, setEvents] = useState(null)
const [userId] = useState(1)
+ const [msg, setMsg] = useState(null)
+ const [loadingId, setLoadingId] = useState(null)
useEffect(()=>{
API(`/api/v1/users/${userId}/integrations`).then(d=>setIntegrations(d)).catch(()=>setIntegrations([]))
@@ -17,7 +19,37 @@ export default function Integrations(){
}
function fetchEvents(integrationId){
- API(`/api/v1/integrations/${integrationId}/google/events`).then(d=>setEvents(d)).catch(e=>setEvents({error: String(e)}))
+ setLoadingId(integrationId)
+ fetch(`/api/v1/integrations/${integrationId}/google/events`, {credentials:'include'})
+ .then(r=>r.json())
+ .then(d=>{
+ setEvents(d)
+ setMsg('Fetched events')
+ })
+ .catch(e=>setEvents({error: String(e)}))
+ .finally(()=>setLoadingId(null))
+ }
+
+ function removeIntegration(integrationId){
+ if(!confirm('Remove integration?')) return
+ setLoadingId(integrationId)
+ fetch(`/api/v1/integrations/${integrationId}`, {method: 'DELETE', credentials: 'include'})
+ .then(r=>r.json())
+ .then(d=>{
+ setMsg('Integration removed')
+ setIntegrations(integrations.filter(i=>i.id !== integrationId))
+ })
+ .catch(e=>setMsg('Failed to remove'))
+ .finally(()=>setLoadingId(null))
+ }
+
+ function syncIntegration(integrationId){
+ setLoadingId(integrationId)
+ fetch(`/api/v1/integrations/${integrationId}/sync_to_habits`, {method:'POST', credentials:'include'})
+ .then(r=>r.json())
+ .then(d=>setMsg(`Synced ${d.count || 0} items`))
+ .catch(e=>setMsg('Sync failed'))
+ .finally(()=>setLoadingId(null))
}
return (
@@ -27,11 +59,17 @@ export default function Integrations(){
Your Integrations
+ {msg && {msg}
}
Events
{events? JSON.stringify(events, null, 2): 'No events fetched'}