Added material-ui to RWA example.

This commit is contained in:
Matija Sosic 2021-02-05 10:57:26 +01:00
parent 80d6278798
commit 4bd68ee56b
9 changed files with 558 additions and 181 deletions

View File

@ -2,6 +2,16 @@ import React, { useState } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import _ from 'lodash' import _ from 'lodash'
import Container from '@material-ui/core/Container'
import Grid from '@material-ui/core/Grid'
import Tabs from '@material-ui/core/Tabs'
import Tab from '@material-ui/core/Tab'
import Box from '@material-ui/core/Box'
import Typography from '@material-ui/core/Typography'
import Chip from '@material-ui/core/Chip'
import Paper from '@material-ui/core/Paper';
import { makeStyles } from '@material-ui/core/styles'
import useAuth from '@wasp/auth/useAuth.js' import useAuth from '@wasp/auth/useAuth.js'
import { useQuery } from '@wasp/queries' import { useQuery } from '@wasp/queries'
@ -15,35 +25,98 @@ const MainPage = () => {
const { data: me } = useAuth() const { data: me } = useAuth()
return ( return (
<div> <Container maxWidth="lg">
<Navbar /> <Navbar />
<Tags /> <Grid container spacing={2}>
<Grid item xs={8}>
<FeedTabs me={me}/>
</Grid>
<Grid item xs={4}>
<Tags />
</Grid>
</Grid>
{ me && ( </Container>
<div> )
<h1> Your Feed </h1> }
const useStylesFeedTabs = makeStyles((theme) => ({
root: {
marginBottom: theme.spacing(2),
},
}))
const FeedTabs = ({ me }) => {
const classes = useStylesFeedTabs()
const [value, setValue] = useState(0);
const handleChange = (event, newValue) => setValue(newValue)
return (
<>
<Tabs value={value} onChange={handleChange} className={classes.root}>
<Tab label="Your Feed" id="feed-tabpanel-0"/>
<Tab label="Global Feed" id="feed-tabpanel-1"/>
</Tabs>
<TabPanel value={value} index={0}>
{ me && (
<ArticleListPaginated <ArticleListPaginated
query={getFollowedArticles} query={getFollowedArticles}
makeQueryArgs={({ skip, take }) => ({ skip, take })} makeQueryArgs={({ skip, take }) => ({ skip, take })}
pageSize={2} pageSize={5}
/> />
</div> )}
)} </TabPanel>
<div> <TabPanel value={value} index={1}>
<h1> Global Feed </h1>
<ArticleListPaginated <ArticleListPaginated
query={getAllArticles} query={getAllArticles}
makeQueryArgs={({ skip, take }) => ({ skip, take })} makeQueryArgs={({ skip, take }) => ({ skip, take })}
pageSize={2} pageSize={5}
/> />
</div> </TabPanel>
</>
)
}
function TabPanel(props) {
const { children, value, index, ...other } = props
return (
<div
role="tabpanel"
hidden={value !== index}
id={`feed-tabpanel-${index}`}
{...other}
>
{value === index && (
<Box>
{children}
</Box>
)}
</div> </div>
) )
} }
const useStylesTags = makeStyles((theme) => ({
root: {
padding: theme.spacing(0.5),
margin: 0,
},
chip: {
margin: theme.spacing(0.5),
},
title: {
marginLeft: theme.spacing(0.5)
}
}))
const Tags = () => { const Tags = () => {
const classes = useStylesTags()
const { data: tags } = useQuery(getTags) const { data: tags } = useQuery(getTags)
if (!tags) return null if (!tags) return null
@ -51,13 +124,15 @@ const Tags = () => {
const popularTags = _.take(_.sortBy(tags, [t => -1 * t.numArticles]), 10) const popularTags = _.take(_.sortBy(tags, [t => -1 * t.numArticles]), 10)
return ( return (
<div> <Paper className={classes.root}>
Popular tags: { popularTags.map(tag => ( <Typography variant="subtitle1" className={classes.title}>Popular tags</Typography>
<div> { popularTags.map(tag => (
{ tag.name } ({ tag.numArticles }) <Chip
</div> className={classes.chip}
))} label={`${tag.name} (${tag.numArticles})`}
</div> />
))}
</Paper>
) )
} }

View File

@ -1,26 +1,57 @@
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import Button from '@material-ui/core/Button'
import Typography from '@material-ui/core/Typography'
import { makeStyles } from '@material-ui/core/styles'
import useAuth from '@wasp/auth/useAuth.js' import useAuth from '@wasp/auth/useAuth.js'
const Navbar = () => { const useStyles = makeStyles((theme) => ({
const { data: user } = useAuth() root: {
flexGrow: 1,
marginBottom: 50
},
title: {
flexGrow: 1,
},
}));
const Navbar = () => {
const classes = useStyles()
const { data: user } = useAuth()
if (user) { if (user) {
return ( return (
<div> <div className={classes.root}>
<Link to='/'> Home </Link> <AppBar position="static">
<Link to='/editor'> New Article </Link> <Toolbar>
<Link to='/settings'> Settings </Link> <Typography variant="h6" className={classes.title}>Conduit</Typography>
<Link to={`/@${user.username}`}> { user.username } </Link>
<Button component={ Link } to="/" color="inherit">Home</Button>
<Button component={ Link } to="/editor" color="inherit">New Article</Button>
<Button component={ Link } to="/settings" color="inherit">Settings</Button>
<Button component={ Link } to={`/@${user.username}`} color="inherit">{ user.username }</Button>
</Toolbar>
</AppBar>
</div> </div>
) )
} else { } else {
return ( return (
<div> <div className={classes.root}>
<Link to='/login'> Sign in </Link> <AppBar position="static">
<Link to='/register'> Sign up </Link> <Toolbar>
<Typography variant="h6" className={classes.title}>Conduit</Typography>
<Button component={ Link } to="/login" color="inherit">Sign in</Button>
<Button component={ Link } to="/register" color="inherit">Sign up</Button>
</Toolbar>
</AppBar>
</div> </div>
) )
} }

View File

@ -2,6 +2,13 @@ import React, { useState } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Link, useHistory } from 'react-router-dom' import { Link, useHistory } from 'react-router-dom'
import Container from '@material-ui/core/Container'
import TextField from '@material-ui/core/TextField'
import Grid from '@material-ui/core/Grid'
import Chip from '@material-ui/core/Chip'
import Button from '@material-ui/core/Button'
import { makeStyles } from '@material-ui/core/styles'
import logout from '@wasp/auth/logout.js' import logout from '@wasp/auth/logout.js'
import { useQuery } from '@wasp/queries' import { useQuery } from '@wasp/queries'
@ -11,6 +18,27 @@ import getArticle from '@wasp/queries/getArticle'
import Navbar from '../../Navbar' import Navbar from '../../Navbar'
const useStyles = makeStyles((theme) => ({
/*
root: {
display: 'flex',
flexWrap: 'wrap',
},
*/
textField: {
//marginLeft: theme.spacing(1),
//width: '25ch',
marginBottom: theme.spacing(3)
},
tags: {
'& *:not(:last-child)': {
marginRight: theme.spacing(0.5)
},
marginBottom: theme.spacing(3)
}
}))
const ArticleEditorPage = (props) => { const ArticleEditorPage = (props) => {
// TODO: Here, as in some other places, it feels tricky to figure out what is happening regarding the state. // TODO: Here, as in some other places, it feels tricky to figure out what is happening regarding the state.
// When is article null, when not, should I look into combination of article and articleSlug, then // When is article null, when not, should I look into combination of article and articleSlug, then
@ -21,10 +49,14 @@ const ArticleEditorPage = (props) => {
return articleError return articleError
? articleError.message || articleError ? articleError.message || articleError
: ( : (
<div> <Container maxWidth="lg">
<Navbar /> <Navbar />
<ArticleEditor user={props.user} article={article} /> <Grid container direction="row" justify="center">
</div> <Grid item xs={8}>
<ArticleEditor user={props.user} article={article} />
</Grid>
</Grid>
</Container>
) )
} }
@ -32,8 +64,9 @@ ArticleEditorPage.propTypes = {
user: PropTypes.object user: PropTypes.object
} }
const ArticleEditor = (props) => { const ArticleEditor = (props) => {
const classes = useStyles()
const user = props.user const user = props.user
const article = props.article const article = props.article
@ -85,30 +118,37 @@ const ArticleEditor = (props) => {
) } ) }
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<h2>Article title</h2> <TextField
<input className={classes.textField}
type='text' label="Article Title"
value={title} fullWidth
value={title}
onChange={e => setTitle(e.target.value)} onChange={e => setTitle(e.target.value)}
/> />
<h2>What's this article about?</h2> <TextField
<input className={classes.textField}
type='text' label="What's this article about"
value={description} fullWidth
value={description}
onChange={e => setDescription(e.target.value)} onChange={e => setDescription(e.target.value)}
/> />
<h2>Markdown content</h2> <TextField
<textarea className={classes.textField}
value={markdownContent} label="Markdown content"
multiline
rows={3}
fullWidth
value={markdownContent}
onChange={e => setMarkdownContent(e.target.value)} onChange={e => setMarkdownContent(e.target.value)}
/> />
<h2>Enter tags</h2> <TextField
<input className={classes.textField}
type="text" label="Enter tags"
value={newTagName} fullWidth
value={newTagName}
onChange={e => setNewTagName(e.target.value)} onChange={e => setNewTagName(e.target.value)}
onKeyPress={e => { onKeyPress={e => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
@ -118,18 +158,14 @@ const ArticleEditor = (props) => {
} }
}} }}
/> />
<div> <div className={classes.tags}>
{ tags.map(tag => ( { tags.map(tag => (
<div key={tag.name}> <Chip label={tag.name} onDelete={() => setTags(tags.filter(t => t !== tag))}/>
{tag.name}
<button onClick={() => setTags(tags.filter(t => t !== tag))}> X </button>
</div>
))} ))}
</div> </div>
<div> <Button type='submit' color='primary' variant='contained'>Publish Article</Button>
<input type='submit' value='Publish Article' />
</div>
</form> </form>
</div> </div>
) )

View File

@ -2,12 +2,40 @@ import React from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import moment from 'moment' import moment from 'moment'
import Button from '@material-ui/core/Button'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardActions from '@material-ui/core/CardActions'
import CardHeader from '@material-ui/core/CardHeader'
import Avatar from '@material-ui/core/Avatar'
import Typography from '@material-ui/core/Typography'
import IconButton from '@material-ui/core/IconButton'
import FavoriteIcon from '@material-ui/icons/Favorite'
import { makeStyles } from '@material-ui/core/styles'
import Chip from '@material-ui/core/Chip'
import setArticleFavorited from '@wasp/actions/setArticleFavorited' import setArticleFavorited from '@wasp/actions/setArticleFavorited'
import smileyImageUrl from '../../smiley.jpg' import smileyImageUrl from '../../smiley.jpg'
const useStyles = makeStyles((theme) => ({
root: {
},
article: {
marginBottom: theme.spacing(1)
},
tags: {
marginLeft: 'auto'
},
chip: {
margin: theme.spacing(0.5),
},
}));
const ArticleList = (props) => { const ArticleList = (props) => {
const articles = props.articles const articles = props.articles
return articles ? ( return articles ? (
<div> <div>
{ articles.map(article => <Article article={article} key={article.id} />) } { articles.map(article => <Article article={article} key={article.id} />) }
@ -16,6 +44,7 @@ const ArticleList = (props) => {
} }
const Article = (props) => { const Article = (props) => {
const classes = useStyles()
const article = props.article const article = props.article
const toggleArticleFavorited = async () => { const toggleArticleFavorited = async () => {
@ -23,26 +52,42 @@ const Article = (props) => {
} }
return ( return (
<div style={{ border: '1px solid black' }}> <Card className={classes.article} elevation={2}>
<Link to={`/article/${article.slug}`}> <CardHeader
<h2> { article.title } </h2> avatar={<Avatar>A</Avatar>}
</Link> title={article.user.username}
<p> { article.description } </p> subheader={moment(article.createdAt).format('MMMM DD, YYYY')}
<p> />
<em> Tags: </em>
{ article.tags.map(t => t.name).join('; ') } <CardContent>
</p> <Typography variant="h5">
<p> <Link to={`/article/${article.slug}`}>
<img src={ article.user.profilePictureUrl || smileyImageUrl } width='30px' /> { article.title }
<div> { article.user.username } </div> </Link>
<div> { moment(article.createdAt).format('MMMM DD, YYYY') } </div> </Typography>
</p> <Typography variant="body2" color="textSecondary" component="p">
<div> { article.description }
<button onClick={toggleArticleFavorited}> </Typography>
</CardContent>
<CardActions disableSpacing>
<Button onClick={toggleArticleFavorited} size="small" color="primary">
{ article.favorited ? 'Unlike' : 'Like' } ({ article.favoritesCount }) { article.favorited ? 'Unlike' : 'Like' } ({ article.favoritesCount })
</button> </Button>
</div>
</div> <Button size="small" color="primary">
Read more
</Button>
<span className={classes.tags}>
{ article.tags.map(t => (
<Chip className={classes.chip} label={t.name}/>
))}
</span>
</CardActions>
</Card>
) )
} }

View File

@ -4,6 +4,19 @@ import ReactMarkdown from 'react-markdown'
import moment from 'moment' import moment from 'moment'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Container from '@material-ui/core/Container'
import Grid from '@material-ui/core/Grid'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardActions from '@material-ui/core/CardActions'
import CardHeader from '@material-ui/core/CardHeader'
import TextField from '@material-ui/core/TextField'
import Typography from '@material-ui/core/Typography'
import Avatar from '@material-ui/core/Avatar'
import Chip from '@material-ui/core/Chip'
import Button from '@material-ui/core/Button'
import { makeStyles } from '@material-ui/core/styles'
import useAuth from '@wasp/auth/useAuth.js' import useAuth from '@wasp/auth/useAuth.js'
import { useQuery } from '@wasp/queries' import { useQuery } from '@wasp/queries'
@ -16,7 +29,35 @@ import deleteComment from '@wasp/actions/deleteComment'
import Navbar from '../../Navbar' import Navbar from '../../Navbar'
const useStyles = makeStyles((theme) => ({
tags: {
'& *:not(:last-child)': {
marginRight: theme.spacing(0.5)
},
marginBottom: theme.spacing(3)
},
comments: {
'& *:not(:last-child)': {
marginBottom: theme.spacing(0.5)
},
},
ownArticleButtons: {
'& *:not(:last-child)': {
marginRight: theme.spacing(0.5)
},
marginBottom: theme.spacing(3)
},
textField: {
marginBottom: theme.spacing(3)
},
postCommentButton: {
marginBottom: theme.spacing(3)
}
}))
const ArticleViewPage = (props) => { const ArticleViewPage = (props) => {
const classes = useStyles()
const history = useHistory() const history = useHistory()
const { data: me } = useAuth({ keepPreviousData: true }) const { data: me } = useAuth({ keepPreviousData: true })
@ -50,59 +91,77 @@ const ArticleViewPage = (props) => {
} }
return article ? ( return article ? (
<div> <Container maxWidth="lg">
<Navbar /> <Navbar />
<div> <Grid container direction="row" justify="center">
<div> Author: { article.user.username } </div> <Grid item xs={8}>
<div> Created at: { moment(article.createdAt).format('MMMM DD, YYYY') } </div> <Typography variant="h2">{ article.title }</Typography>
</div>
<div> <div>
<p> { article.title } </p> <div> Author: { article.user.username } </div>
<p> { article.description } </p> <div> Created at: { moment(article.createdAt).format('MMMM DD, YYYY') } </div>
<p> </div>
<ReactMarkdown children={article.markdownContent} /> </Grid>
</p>
<p>
Tags: { article.tags.map(tag => <div> {tag.name} </div>) }
</p>
</div>
{ isMyArticle && ( <Grid item xs={8}>
<div> <p>
<button onClick={handleEditArticle}> Edit Article </button> <Typography variant="h5">
<button onClick={handleDeleteArticle}> Delete Article </button> <ReactMarkdown children={article.markdownContent} />
</div> </Typography>
)} </p>
</Grid>
<Comments article={article}/> <Grid item xs={8}>
</div> <div className={classes.tags}>
<span>Tags:</span>
{ article.tags.map(tag => <Chip label={tag.name} />) }
</div>
{ isMyArticle && (
<div className={classes.ownArticleButtons}>
<Button color="primary" variant="outlined" onClick={handleEditArticle}>
Edit Article
</Button>
<Button color="secondary" variant="outlined" onClick={handleDeleteArticle}>
Delete Article
</Button>
</div>
)}
<Comments article={article}/>
</Grid>
</Grid>
</Container>
) : null ) : null
} }
const Comments = (props) => { const Comments = (props) => {
const classes = useStyles()
const article = props.article const article = props.article
const { data: me } = useAuth() const { data: me } = useAuth()
const { data: comments } = useQuery(getArticleComments, { articleId: article.id }) const { data: comments } = useQuery(getArticleComments, { articleId: article.id })
return comments ? ( return (
<div> <div>
{ me { me
? <CreateComment article={article} /> ? <CreateComment article={article} />
: null // TODO: Instead of nothing, tell them they need to sign up / sign in to comment. : null // TODO: Instead of nothing, tell them they need to sign up / sign in to comment.
} }
{ comments ? (
<div> <div className={classes.comments}>
{ comments.length { comments.length
? comments.map(c => <Comment comment={c} key={c.id} />) ? comments.map(c => <Comment comment={c} key={c.id} />)
: 'No comments yet!' : 'No comments yet!'
} }
</div> </div>
) : null }
</div> </div>
) : null )
} }
Comments.propTypes = { Comments.propTypes = {
article: PropTypes.object.isRequired article: PropTypes.object.isRequired
@ -123,17 +182,28 @@ const Comment = (props) => {
} }
return ( return (
<div style={{ border: '1px solid black', width: '300px' }}> <>
<div> { comment.content } </div> <Card>
<div> { moment(comment.createdAt).format('MMMM DD, YYYY') } </div> <CardHeader
{ /* TODO: Show user's profile picture. */ } avatar={<Avatar>R</Avatar>}
{ /* TODO: Make username a link to the user profile. */ } title={comment.user.username}
<div> { comment.user.username } </div> subheader={ moment(comment.createdAt).format('MMMM DD, YYYY') }
{ (me && me.id === comment.userId) />
? <button onClick={onDelete}> Delete </button>
: null <CardContent>
} <Typography variant="body1">
</div> { comment.content }
</Typography>
</CardContent>
<CardActions>
{ (me && me.id === comment.userId)
? <Button size="small" color="primary" onClick={onDelete}>Delete</Button>
: null
}
</CardActions>
</Card>
</>
) )
} }
Comment.propTypes = { Comment.propTypes = {
@ -141,6 +211,8 @@ Comment.propTypes = {
} }
const CreateComment = (props) => { const CreateComment = (props) => {
const classes = useStyles()
const article = props.article const article = props.article
const [content, setContent] = useState('') const [content, setContent] = useState('')
@ -157,13 +229,17 @@ const CreateComment = (props) => {
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<textarea <TextField
className={classes.textField}
label='Leave a comment'
multiline
fullWidth
rows={3}
value={content} value={content}
onChange={e => setContent(e.target.value)} onChange={e => setContent(e.target.value)}
style={{ width: '300px' }}
/> />
<div> <div className={classes.postCommentButton}>
<input type='submit' value='Post Comment' /> <Button type="submit" color="primary" variant="contained">Post Comment</Button>
</div> </div>
</form> </form>
) )

View File

@ -88,10 +88,10 @@ export const getArticle = async ({ slug }, context) => {
return article return article
} }
export const getArticleComments = async ({ slug }, context) => { export const getArticleComments = async ({ articleId }, context) => {
// TODO: Do some error handling? // TODO: Do some error handling?
const comments = await context.entities.Comment.findMany({ const comments = await context.entities.Comment.findMany({
where: { article: { slug } }, where: { articleId },
include: { include: {
user: { user: {
// TODO: Tricky, if you forget this you could return unwanted fields // TODO: Tricky, if you forget this you could return unwanted fields

View File

@ -1,6 +1,15 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { Link, useHistory } from 'react-router-dom' import { Link, useHistory } from 'react-router-dom'
import Container from '@material-ui/core/Container'
import TextField from '@material-ui/core/TextField'
import Grid from '@material-ui/core/Grid'
import Tabs from '@material-ui/core/Tabs'
import Tab from '@material-ui/core/Tab'
import Box from '@material-ui/core/Box'
import Button from '@material-ui/core/Button'
import { makeStyles } from '@material-ui/core/styles'
import useAuth from '@wasp/auth/useAuth.js' import useAuth from '@wasp/auth/useAuth.js'
import { useQuery } from '@wasp/queries' import { useQuery } from '@wasp/queries'
@ -12,7 +21,16 @@ import Navbar from '../../Navbar'
import ArticleListPaginated from '../../article/components/ArticleListPaginated' import ArticleListPaginated from '../../article/components/ArticleListPaginated'
import smileyImageUrl from '../../smiley.jpg' import smileyImageUrl from '../../smiley.jpg'
const useStyles = makeStyles((theme) => ({
articles: {
marginTop: theme.spacing(5)
}
}))
const UserProfilePage = (props) => { const UserProfilePage = (props) => {
const classes = useStyles()
const history = useHistory() const history = useHistory()
const { data: me } = useAuth() const { data: me } = useAuth()
@ -30,25 +48,32 @@ const UserProfilePage = (props) => {
} }
return user ? ( return user ? (
<div> <Container maxWidth="lg">
<Navbar /> <Navbar />
<img src={user.profilePictureUrl || smileyImageUrl} /> <Grid container direction="row" justify="center">
<p> { user.username } </p> <Grid item xs={8}>
<p> { user.bio } </p> <img src={user.profilePictureUrl || smileyImageUrl} />
{ me && me.username === username && ( <p> { user.username } </p>
<div> <p> { user.bio } </p>
<Link to='/settings'>Edit Profile Settings</Link> { me && me.username === username && (
</div> <div>
)} <Button component={ Link } to="/settings" variant="contained" color="primary">
{ me && me.username !== username && ( Edit Profile Settings
<div> </Button>
<FollowUserButton user={user} /> </div>
</div> )}
)} { me && me.username !== username && (
<div>
<FollowUserButton user={user} />
</div>
)}
<Articles user={user} /> <Articles user={user} />
</div> </Grid>
</Grid>
</Container>
) : null ) : null
} }
@ -72,23 +97,72 @@ const FollowUserButton = (props) => {
) : null ) : null
} }
const useStylesFeedTabs = makeStyles((theme) => ({
root: {
marginBottom: theme.spacing(2),
},
}))
const ProfileFeedTabs = (props) => {
const classes = useStylesFeedTabs()
const [value, setValue] = useState(0);
const handleChange = (event, newValue) => setValue(newValue)
return (
<>
<Tabs value={value} onChange={handleChange} className={classes.root}>
<Tab label="My Articles" id="feed-tabpanel-0"/>
<Tab label="Favorited articles" id="feed-tabpanel-1"/>
</Tabs>
<TabPanel value={value} index={0}>
<ArticleListPaginated
query={getArticlesByUser}
makeQueryArgs={({ skip, take }) => ({ username: props.user.username, skip, take })}
pageSize={5}
/>
</TabPanel>
<TabPanel value={value} index={1}>
<ArticleListPaginated
query={getFavoritedArticles}
makeQueryArgs={({ skip, take }) => ({ username: props.user.username, skip, take })}
pageSize={5}
/>
</TabPanel>
</>
)
}
function TabPanel(props) {
const { children, value, index, ...other } = props
return (
<div
role="tabpanel"
hidden={value !== index}
id={`feed-tabpanel-${index}`}
{...other}
>
{value === index && (
<Box>
{children}
</Box>
)}
</div>
)
}
const Articles = (props) => { const Articles = (props) => {
const classes = useStyles()
const user = props.user const user = props.user
return ( return (
<div> <div className={classes.articles}>
<h1> My Articles </h1> <ProfileFeedTabs {...props} />
<ArticleListPaginated
query={getArticlesByUser}
makeQueryArgs={({ skip, take }) => ({ username: props.user.username, skip, take })}
pageSize={2}
/>
<h1> Favorited Articles </h1>
<ArticleListPaginated
query={getFavoritedArticles}
makeQueryArgs={({ skip, take }) => ({ username: props.user.username, skip, take })}
pageSize={2}
/>
</div> </div>
) )
} }

View File

@ -1,6 +1,12 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { Link, useHistory } from 'react-router-dom' import { Link, useHistory } from 'react-router-dom'
import Container from '@material-ui/core/Container'
import Grid from '@material-ui/core/Grid'
import TextField from '@material-ui/core/TextField'
import Button from '@material-ui/core/Button'
import { makeStyles } from '@material-ui/core/styles'
import logout from '@wasp/auth/logout.js' import logout from '@wasp/auth/logout.js'
import updateUser from '@wasp/actions/updateUser' import updateUser from '@wasp/actions/updateUser'
@ -9,16 +15,31 @@ import Navbar from '../../Navbar'
const UserSettingsPage = ({ user }) => { const UserSettingsPage = ({ user }) => {
return ( return (
<div> <Container maxWidth="lg">
<Navbar /> <Navbar />
<Grid container direction="row" justify="center">
<UserSettings user={user}/> <Grid item xs={6}>
</div> <UserSettings user={user}/>
</Grid>
</Grid>
</Container>
) )
} }
const useStyles = makeStyles((theme) => ({
textField: {
//width: '25ch',
marginBottom: theme.spacing(3)
},
logoutButton: {
marginTop: theme.spacing(3)
}
}))
const UserSettings = (props) => { const UserSettings = (props) => {
const classes = useStyles()
const user = props.user const user = props.user
const history = useHistory() const history = useHistory()
@ -64,46 +85,59 @@ const UserSettings = (props) => {
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<h2>URL of profile picture</h2> <TextField
<input className={classes.textField}
type='text' label="URL of profile picture"
value={profilePictureUrl} fullWidth
value={profilePictureUrl}
onChange={e => setProfilePictureUrl(e.target.value)} onChange={e => setProfilePictureUrl(e.target.value)}
/> />
<h2>Username</h2> <TextField
<input className={classes.textField}
type='text' label="Username"
value={username} fullWidth
value={username}
onChange={e => setUsername(e.target.value)} onChange={e => setUsername(e.target.value)}
/> />
<h2>Short bio</h2> <TextField
<textarea className={classes.textField}
value={bio} label="Short bio"
multiline
rows={3}
fullWidth
value={bio}
onChange={e => setBio(e.target.value)} onChange={e => setBio(e.target.value)}
/> />
<h2>Email</h2> <TextField
<input className={classes.textField}
type='text' label="Email"
value={email} fullWidth
value={email}
onChange={e => setEmail(e.target.value)} onChange={e => setEmail(e.target.value)}
/> />
<h2>New password</h2> <TextField
<input className={classes.textField}
type='password' label="New password"
value={newPassword} type="password"
fullWidth
value={newPassword}
onChange={e => setNewPassword(e.target.value)} onChange={e => setNewPassword(e.target.value)}
/> />
<div> <Button type="submit" color="primary" variant="contained">Update Settings</Button>
<input type='submit' value='Update Settings' />
</div>
</form> </form>
<button onClick={handleLogout}> Log out </button> <Button
className={classes.logoutButton}
type="submit" color="secondary"
variant="contained" onClick={handleLogout}
>
Log out
</Button>
</div> </div>
) )
} }

View File

@ -1,5 +1,9 @@
app Conduit { app Conduit {
title: "Conduit" title: "Conduit",
head: [
"<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" />"
]
} }
auth { auth {
@ -187,5 +191,7 @@ dependencies {=json
"prop-types": "15.7.2", "prop-types": "15.7.2",
"react-markdown": "5.0.3", "react-markdown": "5.0.3",
"moment": "2.29.1", "moment": "2.29.1",
"@material-ui/core": "4.11.3",
"@material-ui/icons": "4.11.2",
"slug": "4.0.2" "slug": "4.0.2"
json=} json=}