frontend: add Integration manager actions (remove/sync) and Guilds UI
This commit is contained in:
parent
71994e0c09
commit
7702d3711b
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Integrations from './Integrations'
|
import Integrations from './Integrations'
|
||||||
|
import Guilds from './Guilds'
|
||||||
|
|
||||||
export default function App(){
|
export default function App(){
|
||||||
return (
|
return (
|
||||||
|
|
@ -7,6 +8,7 @@ export default function App(){
|
||||||
<h1>LifeRPG Modern</h1>
|
<h1>LifeRPG Modern</h1>
|
||||||
<p>Welcome — frontend scaffold. Connect to backend at <code>/api/v1</code>.</p>
|
<p>Welcome — frontend scaffold. Connect to backend at <code>/api/v1</code>.</p>
|
||||||
<Integrations />
|
<Integrations />
|
||||||
|
<Guilds />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
55
modern/frontend/src/Guilds.jsx
Normal file
55
modern/frontend/src/Guilds.jsx
Normal file
|
|
@ -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 (
|
||||||
|
<div style={{marginTop:20}}>
|
||||||
|
<h2>Guilds</h2>
|
||||||
|
<div>
|
||||||
|
<input value={name} onChange={e=>setName(e.target.value)} placeholder="Guild name" />
|
||||||
|
<button onClick={createGuild} style={{marginLeft:8}}>Create</button>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
{guilds && guilds.length ? guilds.map(g=> (
|
||||||
|
<li key={g.id}>
|
||||||
|
<strong>{g.name}</strong> (owner: {g.owner_id}) <button onClick={()=>loadMembers(g.id)}>Members</button>
|
||||||
|
<button style={{marginLeft:8}} onClick={()=>addMember(g.id)}>Add Member</button>
|
||||||
|
</li>
|
||||||
|
)): <li>No guilds</li>}
|
||||||
|
</ul>
|
||||||
|
<h3>Members</h3>
|
||||||
|
<ul>
|
||||||
|
{members && members.length ? members.map(m=> (<li key={m.id}>User {m.user_id} — {m.role}</li>)) : <li>No members</li>}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,8 @@ export default function Integrations(){
|
||||||
const [integrations, setIntegrations] = useState([])
|
const [integrations, setIntegrations] = useState([])
|
||||||
const [events, setEvents] = useState(null)
|
const [events, setEvents] = useState(null)
|
||||||
const [userId] = useState(1)
|
const [userId] = useState(1)
|
||||||
|
const [msg, setMsg] = useState(null)
|
||||||
|
const [loadingId, setLoadingId] = useState(null)
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
API(`/api/v1/users/${userId}/integrations`).then(d=>setIntegrations(d)).catch(()=>setIntegrations([]))
|
API(`/api/v1/users/${userId}/integrations`).then(d=>setIntegrations(d)).catch(()=>setIntegrations([]))
|
||||||
|
|
@ -17,7 +19,37 @@ export default function Integrations(){
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchEvents(integrationId){
|
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 (
|
return (
|
||||||
|
|
@ -27,11 +59,17 @@ export default function Integrations(){
|
||||||
<h3>Your Integrations</h3>
|
<h3>Your Integrations</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{integrations && integrations.length ? integrations.map(i=> (
|
{integrations && integrations.length ? integrations.map(i=> (
|
||||||
<li key={i.id}>
|
<li key={i.id} style={{marginBottom:8}}>
|
||||||
{i.provider} — id: {i.id} — user: {i.user_id} <button style={{marginLeft:8}} onClick={()=>fetchEvents(i.id)}>Fetch Events</button>
|
<strong>{i.provider}</strong> — id: {i.id} — user: {i.user_id}
|
||||||
|
<div style={{display:'inline-block', marginLeft:12}}>
|
||||||
|
<button onClick={()=>fetchEvents(i.id)} disabled={loadingId===i.id} style={{marginRight:6}}>Fetch Events</button>
|
||||||
|
<button onClick={()=>syncIntegration(i.id)} disabled={loadingId===i.id} style={{marginRight:6}}>Sync → Habits</button>
|
||||||
|
<button onClick={()=>removeIntegration(i.id)} disabled={loadingId===i.id}>Remove</button>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
)): <li>No integrations</li>}
|
)): <li>No integrations</li>}
|
||||||
</ul>
|
</ul>
|
||||||
|
{msg && <div style={{marginTop:8, color:'#0366d6'}}>{msg}</div>}
|
||||||
<h3>Events</h3>
|
<h3>Events</h3>
|
||||||
<pre style={{whiteSpace:'pre-wrap',background:'#f6f6f6',padding:10}}>{events? JSON.stringify(events, null, 2): 'No events fetched'}</pre>
|
<pre style={{whiteSpace:'pre-wrap',background:'#f6f6f6',padding:10}}>{events? JSON.stringify(events, null, 2): 'No events fetched'}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user