mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo-client.git
synced 2026-09-21 16:52:02 -07:00
Added profiles view
This commit is contained in:
@@ -23,6 +23,7 @@ import SweepWif from './sweep/index.js'
|
|||||||
import SignMessage from './sign/index.js'
|
import SignMessage from './sign/index.js'
|
||||||
import ServerSelectView from './configuration/select-server-view'
|
import ServerSelectView from './configuration/select-server-view'
|
||||||
import UserDataReview from './user-data-review'
|
import UserDataReview from './user-data-review'
|
||||||
|
import RecentProfiles from './recent-profiles'
|
||||||
|
|
||||||
function AppBody (props) {
|
function AppBody (props) {
|
||||||
// Dependency injection through props
|
// Dependency injection through props
|
||||||
@@ -36,6 +37,7 @@ function AppBody (props) {
|
|||||||
<Route path='/bch' element={<BchSend appData={appData} />} />
|
<Route path='/bch' element={<BchSend appData={appData} />} />
|
||||||
<Route path='/wallet' element={<Wallet appData={appData} />} />
|
<Route path='/wallet' element={<Wallet appData={appData} />} />
|
||||||
<Route path='/slp-tokens' element={<SlpTokens appData={appData} />} />
|
<Route path='/slp-tokens' element={<SlpTokens appData={appData} />} />
|
||||||
|
<Route path='/profile/recent' element={<RecentProfiles />} />
|
||||||
<Route path='/placeholder2' element={<Placeholder2 />} />
|
<Route path='/placeholder2' element={<Placeholder2 />} />
|
||||||
<Route path='/placeholder3' element={<Placeholder3 />} />
|
<Route path='/placeholder3' element={<Placeholder3 />} />
|
||||||
<Route path='/servers' element={<ServerSelectView appData={appData} />} />
|
<Route path='/servers' element={<ServerSelectView appData={appData} />} />
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
Display the most recent Memo profiles from psf-memo-db.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { Container, Row, Col, Spinner, Table } from 'react-bootstrap'
|
||||||
|
|
||||||
|
// Local libraries
|
||||||
|
import MemoDb from '../../../services/memo-db'
|
||||||
|
import '../../../App.css'
|
||||||
|
|
||||||
|
function truncate (str, maxLen = 16) {
|
||||||
|
if (!str || str.length <= maxLen) return str
|
||||||
|
const half = Math.floor((maxLen - 3) / 2)
|
||||||
|
return `${str.slice(0, half)}...${str.slice(-half)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSeen (seen) {
|
||||||
|
if (!seen) return ''
|
||||||
|
const ms = seen > 1e12 ? seen : seen * 1000
|
||||||
|
return new Date(ms).toLocaleString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function RecentProfiles () {
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
const [profiles, setProfiles] = useState([])
|
||||||
|
const [pagination, setPagination] = useState(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadProfiles = async () => {
|
||||||
|
try {
|
||||||
|
const memoDb = new MemoDb()
|
||||||
|
const data = await memoDb.getRecentProfiles({ limit: 100, offset: 0 })
|
||||||
|
setProfiles(data.profiles || [])
|
||||||
|
setPagination(data.pagination || null)
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message || 'Failed to load recent profiles')
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProfiles()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<h1 className='mt-4'>Recent Profiles</h1>
|
||||||
|
{pagination && (
|
||||||
|
<p className='text-muted'>
|
||||||
|
Showing {profiles.length} of {pagination.total} profiles
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && <p className='text-danger'>{error}</p>}
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div className='text-center my-5'>
|
||||||
|
<Spinner animation='border' role='status' variant='primary'>
|
||||||
|
<span className='visually-hidden'>Loading...</span>
|
||||||
|
</Spinner>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && (
|
||||||
|
<Table striped bordered hover responsive className='mt-3'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Address</th>
|
||||||
|
<th>Bio</th>
|
||||||
|
<th>Block</th>
|
||||||
|
<th>Seen</th>
|
||||||
|
<th>TxID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{profiles.map((profile) => (
|
||||||
|
<tr key={`${profile.addr}-${profile.txid}`}>
|
||||||
|
<td>
|
||||||
|
<span style={{ fontFamily: 'monospace' }} title={profile.addr}>
|
||||||
|
{truncate(profile.addr, 24)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{profile.text}</td>
|
||||||
|
<td>{profile.blockHeight}</td>
|
||||||
|
<td>{formatSeen(profile.seen)}</td>
|
||||||
|
<td>
|
||||||
|
<span style={{ fontFamily: 'monospace' }} title={profile.txid}>
|
||||||
|
{truncate(profile.txid, 20)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecentProfiles
|
||||||
@@ -54,6 +54,14 @@ function NavMenu (props) {
|
|||||||
Tokens
|
Tokens
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
className={currentPath === '/profile/recent' ? 'nav-link-active' : 'nav-link-inactive'}
|
||||||
|
to='/profile/recent'
|
||||||
|
onClick={handleClickEvent}
|
||||||
|
>
|
||||||
|
Profiles
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
className={currentPath === '/wallet' ? 'nav-link-active' : 'nav-link-inactive'}
|
className={currentPath === '/wallet' ? 'nav-link-active' : 'nav-link-inactive'}
|
||||||
to='/wallet'
|
to='/wallet'
|
||||||
|
|||||||
+3
-1
@@ -12,7 +12,9 @@ const config = {
|
|||||||
// Backup Info that goes into the Footer.
|
// Backup Info that goes into the Footer.
|
||||||
ghPagesUrl: 'https://permissionless-software-foundation.github.io/react-bootstrap-web3-spa/',
|
ghPagesUrl: 'https://permissionless-software-foundation.github.io/react-bootstrap-web3-spa/',
|
||||||
ghRepo: 'https://github.com/Permissionless-Software-Foundation/bch-wallet-web3-spa',
|
ghRepo: 'https://github.com/Permissionless-Software-Foundation/bch-wallet-web3-spa',
|
||||||
radicleUrl: 'https://app.radicle.network/seeds/maple.radicle.garden/rad:git:hnrkd5cjwwb5tzx37hq9uqm5ubon7ee468xcy/remotes/hyyycncbn9qzqmobnhjq9rry6t4mbjiadzjoyhaknzxjcz3cxkpfpc'
|
radicleUrl: 'https://app.radicle.network/seeds/maple.radicle.garden/rad:git:hnrkd5cjwwb5tzx37hq9uqm5ubon7ee468xcy/remotes/hyyycncbn9qzqmobnhjq9rry6t4mbjiadzjoyhaknzxjcz3cxkpfpc',
|
||||||
|
|
||||||
|
backend: 'http://localhost:5021'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
HTTP client for the psf-memo-db REST API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
|
class MemoDb {
|
||||||
|
constructor () {
|
||||||
|
this.axios = axios
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecentProfiles ({ limit = 100, offset = 0 } = {}) {
|
||||||
|
try {
|
||||||
|
const result = await this.axios.get(`${config.backend}/profile/recent`, {
|
||||||
|
params: { limit, offset }
|
||||||
|
})
|
||||||
|
|
||||||
|
return result.data
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in getRecentProfiles()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MemoDb
|
||||||
Reference in New Issue
Block a user