mirror of
https://github.com/fazo96/ipfs-boards
synced 2025-01-26 15:04:19 +01:00
implement commenting and posting
This commit is contained in:
parent
2670ab2298
commit
1711807308
43
components/AddComment.js
Normal file
43
components/AddComment.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { TextField, InputAdornment, IconButton } from '@material-ui/core'
|
||||||
|
import SendIcon from '@material-ui/icons/Send'
|
||||||
|
import { openBoard } from './system'
|
||||||
|
|
||||||
|
class AddComment extends React.PureComponent {
|
||||||
|
state = { text: '' }
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { text } = this.state
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
label="Comment"
|
||||||
|
placeholder="What's on your mind?"
|
||||||
|
value={text}
|
||||||
|
onChange={event => this.setState({ text: event.target.value })}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={this.submit} disabled={!text}>
|
||||||
|
<SendIcon />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
submit = async () => {
|
||||||
|
const { boardId, postId, parentId, afterSend } = this.props
|
||||||
|
const { text } = this.state
|
||||||
|
const board = await openBoard(boardId)
|
||||||
|
const comment = { text }
|
||||||
|
await board.commentPost(postId, comment, parentId)
|
||||||
|
if (afterSend) afterSend()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddComment
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ export default function Board({ boardId, posts }){
|
|||||||
<CardHeader
|
<CardHeader
|
||||||
avatar={<Avatar><EmptyIcon /></Avatar>}
|
avatar={<Avatar><EmptyIcon /></Avatar>}
|
||||||
title="No Posts Yet"
|
title="No Posts Yet"
|
||||||
subheader="Why don't you break the ice?"
|
subheader="Don't panic. Your device will keep looking for new posts in the network."
|
||||||
/>
|
/>
|
||||||
</Card>}
|
</Card>}
|
||||||
<Button
|
<Button
|
||||||
|
82
components/Comments.js
Normal file
82
components/Comments.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { List, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Button } from '@material-ui/core'
|
||||||
|
import AddComment from './AddComment'
|
||||||
|
import CommentIcon from '@material-ui/icons/Comment'
|
||||||
|
import { openBoard } from './system'
|
||||||
|
|
||||||
|
class Comments extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
comments: [],
|
||||||
|
replying: false
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.refreshComments()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { boardId, postId, parentId } = this.props
|
||||||
|
if (prevProps.boardId !== boardId || prevProps.postId !== postId || prevProps.parentId !== parentId) {
|
||||||
|
this.refreshComments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshComments() {
|
||||||
|
const { boardId, postId, parentId } = this.props
|
||||||
|
const board = await openBoard(boardId)
|
||||||
|
const comments = await board.getComments(postId, parentId)
|
||||||
|
this.setState({ comments })
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleReplying = () => this.setState({ replying: !this.state.replying })
|
||||||
|
|
||||||
|
afterCommenting = () => {
|
||||||
|
this.setState({ replying: false })
|
||||||
|
this.refreshComments()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { boardId, postId, parentId, ...others } = this.props
|
||||||
|
const { comments = [], replying } = this.state
|
||||||
|
return (
|
||||||
|
<List {...others}>
|
||||||
|
{comments.length === 0 && !parentId && (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText>No comments yet</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
{replying && (
|
||||||
|
<AddComment
|
||||||
|
boardId={boardId}
|
||||||
|
postId={postId}
|
||||||
|
parentId={parentId}
|
||||||
|
afterSend={this.afterCommenting}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={this.toggleReplying}
|
||||||
|
>
|
||||||
|
<CommentIcon style={{ marginRight: '8px' }} />
|
||||||
|
{parentId ? 'Reply' : 'Add Comment'}
|
||||||
|
</Button>
|
||||||
|
{comments.map(c => (
|
||||||
|
<React.Fragment>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText>{c.text}</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
<Comments
|
||||||
|
boardId={boardId}
|
||||||
|
postId={postId}
|
||||||
|
parentId={c.multihash}
|
||||||
|
style={{ marginLeft: '32px' }}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Comments
|
||||||
|
|
27
components/Container.js
Normal file
27
components/Container.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
container: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
maxWidth: '600px',
|
||||||
|
flexGrow: 2
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const Container = ({ children }) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Container
|
37
components/Post.js
Normal file
37
components/Post.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Fab, Card, CardContent, CardHeader, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import Comments from './Comments'
|
||||||
|
import AddIcon from '@material-ui/icons/Add'
|
||||||
|
import ProfileIcon from '@material-ui/icons/AccountCircle'
|
||||||
|
|
||||||
|
const Post = ({ post = {}, boardId }) => {
|
||||||
|
const found = Boolean(post.multihash)
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Card>
|
||||||
|
<CardHeader
|
||||||
|
title={found ? post.title || 'Untitled Post' : 'Not Found'}
|
||||||
|
subheader="Last Activity X Time Ago"
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
{post.text || post.multihash || '(This post is empty)'}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<List>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemAvatar><ProfileIcon /></ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary="Username"
|
||||||
|
secondary="Discovered X Time Ago"
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
{boardId && post.multihash && (
|
||||||
|
<Comments boardId={boardId} postId={post.multihash} />
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Post
|
@ -28,11 +28,12 @@ class Status extends React.PureComponent {
|
|||||||
let statusText = 'Pre-Rendered'
|
let statusText = 'Pre-Rendered'
|
||||||
if (!info.isServer) {
|
if (!info.isServer) {
|
||||||
statusText = 'Offline'
|
statusText = 'Offline'
|
||||||
|
if (info.ipfsLoading) statusText = 'Starting IPFS'
|
||||||
if (info.ipfsReady) {
|
if (info.ipfsReady) {
|
||||||
statusText = 'Starting DB'
|
|
||||||
Icon = ConnectedIcon
|
Icon = ConnectedIcon
|
||||||
|
statusText = `${info.ipfsPeers.length} Peers`
|
||||||
}
|
}
|
||||||
if (info.orbitDbReady) statusText = `${info.ipfsPeers.length} Peers`
|
if (info.orbitDbLoading) statusText = 'Starting DB'
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Button color="inherit">
|
return <Button color="inherit">
|
||||||
|
@ -21,12 +21,43 @@ export function getGlobalData() {
|
|||||||
return scope.ipfsBoards
|
return scope.ipfsBoards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getIPFSOptions() {
|
||||||
|
const common = {
|
||||||
|
libp2p: {
|
||||||
|
config: {
|
||||||
|
pubsub: { enabled: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isServer()) {
|
||||||
|
return {
|
||||||
|
relay: { enabled: true, hop: { enabled: true, active: true } },
|
||||||
|
...common
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const serverInfo = await getServerInfo()
|
||||||
|
let additionalOptions = {}
|
||||||
|
if (serverInfo) {
|
||||||
|
additionalOptions = {
|
||||||
|
config: {
|
||||||
|
Bootstrap: [ ...serverInfo.multiaddrs ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...common,
|
||||||
|
...additionalOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getIPFS() {
|
export async function getIPFS() {
|
||||||
const data = getGlobalData()
|
const data = getGlobalData()
|
||||||
if (data.ipfs) return data.ipfs
|
if (data.ipfs) return data.ipfs
|
||||||
|
const IPFS = await import(/* webpackChunkName: "ipfs" */ 'ipfs')
|
||||||
|
const options = await getIPFSOptions()
|
||||||
if (!data.ipfsPromise) {
|
if (!data.ipfsPromise) {
|
||||||
const IPFS = await import(/* webpackChunkName: "ipfs" */ 'ipfs')
|
data.ipfsPromise = IPFS.create(options)
|
||||||
data.ipfsPromise = IPFS.create()
|
|
||||||
}
|
}
|
||||||
data.ipfs = await data.ipfsPromise
|
data.ipfs = await data.ipfsPromise
|
||||||
delete data.ipfsPromise
|
delete data.ipfsPromise
|
||||||
@ -34,19 +65,24 @@ export async function getIPFS() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrbitDB() {
|
export async function getOrbitDB() {
|
||||||
const data = getGlobalData()
|
try {
|
||||||
if (data.orbitDb) return data.orbitDb
|
const data = getGlobalData()
|
||||||
const ipfs = await getIPFS()
|
if (data.orbitDb) return data.orbitDb
|
||||||
if (!data.orbitDbPromise) {
|
const ipfs = await getIPFS()
|
||||||
const OrbitDB = await import(/* webpackChunkName: "orbit-db" */ 'orbit-db').then(m => m.default)
|
const OrbitDB = await import(/* webpackChunkName: "orbit-db" */ 'orbit-db').then(m => m.default)
|
||||||
const BoardStore = await import(/* webpackChunkName: "orbit-db-discussion-board" */ 'orbit-db-discussion-board').then(m => m.default)
|
const BoardStore = await import(/* webpackChunkName: "orbit-db-discussion-board" */ 'orbit-db-discussion-board').then(m => m.default)
|
||||||
OrbitDB.addDatabaseType(BoardStore.type, BoardStore)
|
if (!data.orbitDbPromise) {
|
||||||
data.orbitDbPromise = OrbitDB.createInstance(ipfs)
|
OrbitDB.addDatabaseType(BoardStore.type, BoardStore)
|
||||||
|
data.orbitDbPromise = OrbitDB.createInstance(ipfs)
|
||||||
|
}
|
||||||
|
data.orbitDb = await data.orbitDbPromise
|
||||||
|
delete data.orbitDbPromise
|
||||||
|
if (!data.boards) data.boards = {}
|
||||||
|
return data.orbitDb
|
||||||
|
} catch (error) {
|
||||||
|
console.log('FATAL: COULD NOT LOAD ORBITDB', error)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
data.orbitDb = await data.orbitDbPromise
|
|
||||||
delete data.orbitDbPromise
|
|
||||||
if (!data.boards) data.boards = {}
|
|
||||||
return data.orbitDb
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openBoard(id) {
|
export async function openBoard(id) {
|
||||||
@ -106,13 +142,43 @@ export function getInfo() {
|
|||||||
|
|
||||||
export async function refreshInfo() {
|
export async function refreshInfo() {
|
||||||
const data = getGlobalData()
|
const data = getGlobalData()
|
||||||
|
const ipfsReady = Boolean(data.ipfs)
|
||||||
|
const multiaddrs = ipfsReady ? data.ipfs.libp2p.peerInfo.multiaddrs.toArray().map(m => m.toJSON()) : []
|
||||||
data.info = {
|
data.info = {
|
||||||
isServer: isServer(),
|
isServer: isServer(),
|
||||||
ipfsReady: Boolean(data.ipfs),
|
ipfsReady,
|
||||||
|
ipfsLoading: Boolean(data.ipfsPromise),
|
||||||
orbitDbReady: Boolean(data.orbitDb),
|
orbitDbReady: Boolean(data.orbitDb),
|
||||||
|
orbitDbPromise: Boolean(data.orbitDbPromise),
|
||||||
openBoards: Object.keys(data.boards || {}),
|
openBoards: Object.keys(data.boards || {}),
|
||||||
ipfsPeers: await getIPFSPeers(),
|
ipfsPeers: await getIPFSPeers(),
|
||||||
pubsub: await getPubsubInfo()
|
pubsub: await getPubsubInfo(),
|
||||||
|
multiaddrs
|
||||||
}
|
}
|
||||||
return data.info
|
return data.info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getServerInfo() {
|
||||||
|
const response = await fetch('/api/status')
|
||||||
|
if (response.status === 200) {
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function connectoToIPFSMultiaddr(multiaddr) {
|
||||||
|
const ipfs = await getIPFS()
|
||||||
|
try {
|
||||||
|
// console.log(`Connecting to ${multiaddr}...`)
|
||||||
|
await ipfs.swarm.connect(multiaddr)
|
||||||
|
console.log(`Connected to ${multiaddr}!`)
|
||||||
|
} catch (error) {
|
||||||
|
// console.log(`Connection to ${multiaddr} failed:`, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function connectIPFSToBackend() {
|
||||||
|
const serverInfo = await getServerInfo()
|
||||||
|
const addresses = serverInfo.multiaddrs
|
||||||
|
return Promise.race(addresses.map(connectoToIPFSMultiaddr))
|
||||||
|
}
|
999
package-lock.json
generated
999
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@
|
|||||||
"@material-ui/core": "^4.5.1",
|
"@material-ui/core": "^4.5.1",
|
||||||
"@material-ui/icons": "^4.5.1",
|
"@material-ui/icons": "^4.5.1",
|
||||||
"@material-ui/styles": "^4.5.0",
|
"@material-ui/styles": "^4.5.0",
|
||||||
"ipfs": "~0.38.0",
|
"ipfs": "~0.39.0",
|
||||||
"next": "9.1.1",
|
"next": "9.1.1",
|
||||||
"orbit-db": "~0.22.0",
|
"orbit-db": "~0.22.0",
|
||||||
"orbit-db-discussion-board": "https://github.com/fazo96/orbit-db-discussion-board.git",
|
"orbit-db-discussion-board": "https://github.com/fazo96/orbit-db-discussion-board.git",
|
||||||
|
@ -5,6 +5,8 @@ import { ThemeProvider } from '@material-ui/styles';
|
|||||||
import AppBar from '../components/AppBar'
|
import AppBar from '../components/AppBar'
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||||
import theme from '../components/theme';
|
import theme from '../components/theme';
|
||||||
|
import Container from '../components/Container'
|
||||||
|
import { connectIPFSToBackend } from '../components/system';
|
||||||
|
|
||||||
export default class MyApp extends App {
|
export default class MyApp extends App {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -13,6 +15,8 @@ export default class MyApp extends App {
|
|||||||
if (jssStyles) {
|
if (jssStyles) {
|
||||||
jssStyles.parentNode.removeChild(jssStyles);
|
jssStyles.parentNode.removeChild(jssStyles);
|
||||||
}
|
}
|
||||||
|
// Connect IPFS to backend server to improve connectivity
|
||||||
|
connectIPFSToBackend().catch(error => console.log('connectIPFSToBackend failed', error))
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -27,7 +31,9 @@ export default class MyApp extends App {
|
|||||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<AppBar />
|
<AppBar />
|
||||||
<Component {...pageProps} />
|
<Container>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</Container>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
8
pages/api/status.js
Normal file
8
pages/api/status.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { refreshInfo } from '../../components/system'
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const info = await refreshInfo()
|
||||||
|
res.setHeader('Content-Type', 'application/json')
|
||||||
|
res.statusCode = 200
|
||||||
|
res.end(JSON.stringify(info))
|
||||||
|
}
|
@ -1,33 +1,46 @@
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Fab, Card, CardContent, CardContentText, CardHeader, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'
|
import { openBoard } from '../../../../components/system'
|
||||||
import AddIcon from '@material-ui/icons/Add'
|
import Post from '../../../../components/Post'
|
||||||
import ProfileIcon from '@material-ui/icons/AccountCircle'
|
|
||||||
|
|
||||||
const Post = () => {
|
const findPost = (posts, id) => {
|
||||||
return (
|
const results = posts.filter(p => p.multihash === id)
|
||||||
<React.Fragment>
|
if (results.length > 0) return results[0]
|
||||||
<Card>
|
return undefined
|
||||||
<CardHeader
|
|
||||||
title="First Post"
|
|
||||||
subheader="Last Activity X Time Ago"
|
|
||||||
/>
|
|
||||||
<CardContent>
|
|
||||||
<CardContentText>Lorem Ipsum...</CardContentText>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<List>
|
|
||||||
<ListItem>
|
|
||||||
<ListItemAvatar><ProfileIcon /></ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary="Username"
|
|
||||||
secondary="Discovered X Time Ago"
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
<Fab color="primary"><AddIcon /></Fab>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Post
|
class PostPage extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
post: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.refreshPost()
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshPost() {
|
||||||
|
const { boardId, postId } = this.props
|
||||||
|
const board = await openBoard(boardId)
|
||||||
|
const post = findPost(board.posts, postId)
|
||||||
|
this.setState({ post })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { post: postProp, boardId } = this.props
|
||||||
|
const { post } = this.state
|
||||||
|
return <Post
|
||||||
|
post={post || postProp}
|
||||||
|
boardId={boardId}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PostPage.getInitialProps = async ({ query }) => {
|
||||||
|
const board = await openBoard(query.board)
|
||||||
|
return {
|
||||||
|
post: findPost(board.posts, query.post),
|
||||||
|
boardId: query.board,
|
||||||
|
postId: query.post
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PostPage
|
||||||
|
@ -2,10 +2,25 @@ import React, { useState } from 'react'
|
|||||||
import { openBoard } from '../../../../components/system'
|
import { openBoard } from '../../../../components/system'
|
||||||
import Router from 'next/router'
|
import Router from 'next/router'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { Card, CardHeader, CardContent, TextField, Button, Avatar } from '@material-ui/core'
|
import { Typography, Box, Card, CardHeader, Divider, CardActions, CardContent, TextField, Button, Avatar, Tabs, Tab } from '@material-ui/core'
|
||||||
import SendIcon from '@material-ui/icons/Send'
|
import SendIcon from '@material-ui/icons/Send'
|
||||||
import AddIcon from '@material-ui/icons/Add'
|
import AddIcon from '@material-ui/icons/Add'
|
||||||
|
|
||||||
|
function TabPanel(props) {
|
||||||
|
const { children, value, index, ...other } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
component="div"
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
<Box p={3}>{children}</Box>
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
card: {
|
card: {
|
||||||
maxWidth: 600,
|
maxWidth: 600,
|
||||||
@ -22,7 +37,18 @@ const useStyles = makeStyles(theme => ({
|
|||||||
},
|
},
|
||||||
buttonIcon: {
|
buttonIcon: {
|
||||||
marginLeft: theme.spacing(1)
|
marginLeft: theme.spacing(1)
|
||||||
}
|
},
|
||||||
|
tabContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
tabPanel: {
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
borderRight: `1px solid ${theme.palette.divider}`,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
async function createPost(boardId, postData) {
|
async function createPost(boardId, postData) {
|
||||||
@ -33,7 +59,21 @@ async function createPost(boardId, postData) {
|
|||||||
|
|
||||||
export default function CreatePost({ boardId }) {
|
export default function CreatePost({ boardId }) {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
// State
|
||||||
const [title, setTitle] = useState('')
|
const [title, setTitle] = useState('')
|
||||||
|
const [cid, setCID] = useState('')
|
||||||
|
const [content, setContent] = useState('')
|
||||||
|
const [tab, setTab] = useState(0)
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
const submitPost = () => {
|
||||||
|
const payload = { title }
|
||||||
|
if (tab === 0) payload.text = content
|
||||||
|
if (tab === 2) payload.multihash = cid
|
||||||
|
return createPost(boardId, payload)
|
||||||
|
}
|
||||||
|
|
||||||
return <Card className={classes.card}>
|
return <Card className={classes.card}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
avatar={<Avatar><AddIcon /></Avatar>}
|
avatar={<Avatar><AddIcon /></Avatar>}
|
||||||
@ -53,22 +93,65 @@ export default function CreatePost({ boardId }) {
|
|||||||
autoFocus
|
autoFocus
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<TextField
|
</CardContent>
|
||||||
className={classes.field}
|
<Divider />
|
||||||
variant="filled"
|
<div className={classes.tabContainer}>
|
||||||
label="CID"
|
<Tabs
|
||||||
placeholder="Paste the IPFS CID or your post content (WIP)"
|
orientation="vertical"
|
||||||
value={title}
|
value={tab}
|
||||||
onChange={e => setTitle(e.target.value)}
|
onChange={(event, value) => setTab(value)}
|
||||||
fullWidth
|
className={classes.tabs}
|
||||||
/>
|
>
|
||||||
<Button variant="contained" color="primary" onClick={() => createPost(boardId, { title })}>
|
<Tab label="Text" />
|
||||||
Submit <SendIcon className={classes.buttonIcon} />
|
<Tab label="Media" />
|
||||||
</Button>
|
<Tab label="IPFS CID" />
|
||||||
<Button className={classes.secondaryButton} onClick={() => Router.push(`/b/${boardId}`)}>
|
</Tabs>
|
||||||
|
<TabPanel value={tab} index={0} className={classes.tabPanel}>
|
||||||
|
<TextField
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
placeholder="What's on your mind?"
|
||||||
|
rows={3}
|
||||||
|
variant="outlined"
|
||||||
|
label="Post"
|
||||||
|
value={content}
|
||||||
|
onChange={event => setContent(event.target.value)}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tab} index={1} className={classes.tabPanel}>
|
||||||
|
WIP
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tab} index={2} className={classes.tabPanel}>
|
||||||
|
<TextField
|
||||||
|
className={classes.field}
|
||||||
|
variant="filled"
|
||||||
|
label="CID"
|
||||||
|
placeholder="Paste the IPFS CID or your post content (WIP)"
|
||||||
|
value={cid}
|
||||||
|
onChange={e => setCID(e.target.value)}
|
||||||
|
helperText="Enter the CID or Multihash of existing IPFS content"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<CardActions>
|
||||||
|
<Button
|
||||||
|
className={classes.secondaryButton}
|
||||||
|
onClick={() => Router.push(`/b/${boardId}`)}
|
||||||
|
style={{ marginLeft: 'auto' }}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={submitPost}
|
||||||
|
>
|
||||||
|
Submit <SendIcon className={classes.buttonIcon} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,44 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Card, CardContent, TextField, Button } from '@material-ui/core'
|
import { Card, CardContent, TextField, Button, Typography, Divider} from '@material-ui/core'
|
||||||
import OpenIcon from '@material-ui/icons/Add'
|
import OpenIcon from '@material-ui/icons/Add'
|
||||||
|
import Router from 'next/router'
|
||||||
|
import { makeStyles } from '@material-ui/styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
button: {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
marginLeft: 'auto',
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
export default function OpenBoard() {
|
export default function OpenBoard() {
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
|
const styles = useStyles()
|
||||||
return <Card>
|
return <Card>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h5">
|
||||||
|
Open a Board
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle1">
|
||||||
|
IPFS Boards is a work in progress. Thank you for testing the app!
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
<Divider />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<TextField
|
<TextField
|
||||||
label="Board"
|
|
||||||
placeholder="Type a name..."
|
placeholder="Type a name..."
|
||||||
value={name}
|
value={name}
|
||||||
onChange={e => setName(e.target.value)}
|
onChange={e => setName(e.target.value)}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<Button variant="contained" color="primary">
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={styles.button}
|
||||||
|
disabled={!name}
|
||||||
|
onClick={() => Router.push(`/b/${name}`)}
|
||||||
|
>
|
||||||
<OpenIcon /> Open
|
<OpenIcon /> Open
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Fab, Card, CardActions, CardHeader, Button } from '@material-ui/core'
|
import { Fab, Card, CardActions, CardHeader, Button } from '@material-ui/core'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import AddIcon from '@material-ui/icons/Add'
|
import AddIcon from '@material-ui/icons/Add'
|
||||||
import ViewIcon from '@material-ui/icons/Visibility'
|
import ViewIcon from '@material-ui/icons/Visibility'
|
||||||
import DeleteIcon from '@material-ui/icons/Delete'
|
import DeleteIcon from '@material-ui/icons/Delete'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import Router from 'next/router'
|
import Router from 'next/router'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
fab: {
|
||||||
|
float: 'right',
|
||||||
|
marginTop: theme.spacing(2)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
|
const styles = useStyles()
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Card>
|
<Card>
|
||||||
@ -24,7 +33,9 @@ const Home = () => {
|
|||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
<Link href="/b/open">
|
<Link href="/b/open">
|
||||||
<Fab color="primary"><AddIcon /></Fab>
|
<Fab color="primary" className={styles.fab}>
|
||||||
|
<AddIcon />
|
||||||
|
</Fab>
|
||||||
</Link>
|
</Link>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user