diff --git a/client/src/components/App.js b/client/src/components/App.js index 42441ee..bb17aba 100644 --- a/client/src/components/App.js +++ b/client/src/components/App.js @@ -6,6 +6,7 @@ import LinkList from './LinkList'; import CreateLink from './CreateLink' import Header from './Header'; import Login from './Login' +import Search from './Search'; import { Switch, Route } from 'react-router-dom'; const App = () => { @@ -21,6 +22,7 @@ const App = () => { component={CreateLink} /> + diff --git a/client/src/components/CreateLink.js b/client/src/components/CreateLink.js index b37cbb0..b4d7e1e 100644 --- a/client/src/components/CreateLink.js +++ b/client/src/components/CreateLink.js @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useHistory } from 'react-router'; import { useMutation, gql } from '@apollo/client'; +import { FEED_QUERY } from './LinkList'; const CREATE_LINK_MUTATION = gql` mutation PostMutation( @@ -28,7 +29,36 @@ const CreateLink = () => { variables: { description: formState.description, url: formState.url - } + }, + update: (cache, { data: { post } }) => { + const take = LINKS_PER_PAGE; + const skip = 0; + const orderBy = { createdAt: 'desc' }; + + const data = cache.readQuery({ + query: FEED_QUERY, + variables: { + take, + skip, + orderBy + } + }); + + cache.writeQuery({ + query: FEED_QUERY, + data: { + feed: { + links: [post, ...data.feed.links] + } + }, + variables: { + take, + skip, + orderBy + } + }); + }, + onCompleted: () => history.push('/new/1') }); return ( diff --git a/client/src/components/Header.js b/client/src/components/Header.js index 2278284..7aa1c9e 100644 --- a/client/src/components/Header.js +++ b/client/src/components/Header.js @@ -18,12 +18,9 @@ const Header = () => { top
|
- + search - + {authToken && (
|
diff --git a/client/src/components/Link.js b/client/src/components/Link.js index e748b84..68d8482 100644 --- a/client/src/components/Link.js +++ b/client/src/components/Link.js @@ -1,14 +1,104 @@ import React from 'react'; +import { useMutation, gql } from '@apollo/client'; +import { AUTH_TOKEN, LINKS_PER_PAGE } from '../constants'; +import { timeDifferenceForDate } from '../utils' + +const VOTE_MUTATION = gql` + mutation VoteMutation($linkId: ID!) { + vote(linkId: $linkId) { + id + link { + id + votes { + id + user { + id + } + } + } + user { + id + } + } + } +`; const Link = (props) => { const { link } = props; + const authToken = localStorage.getItem(AUTH_TOKEN); + const take = LINKS_PER_PAGE; + const skip = 0; + const orderBy = { createdAt: 'desc' }; + + const [vote] = useMutation(VOTE_MUTATION, { + variables: { + linkId: link.id + }, + update(cache, { data: { vote } }) { + const { feed } = cache.readQuery({ + query: FEED_QUERY + }); + + const updatedLinks = feed.links.map((feedLink) => { + if (feedLink.id === link.id) { + return { + ...feedLink, + votes: [...feedLink.votes, vote] + }; + } + return feedLink; + }); + + cache.writeQuery({ + query: FEED_QUERY, + data: { + feed: { + links: updatedLinks + } + } + }); + } + }); + return ( -
-
- {link.id} - {link.description} ({link.url}) -
+
+
+ {props.index + 1}. + {authToken && ( +
+ ▲ +
+ )} +
+
+
+ {link.description} ({link.url}) +
+ {authToken && ( +
+ {link.votes.length} votes | by{' '} + {link.postedBy ? link.postedBy.name : 'Unknown'}{' '} + {timeDifferenceForDate(link.createdAt)} +
+ )} +
); }; +// const Link = (props) => { +// const { link } = props; +// return ( +//
+//
+// {link.id} - {link.description} ({link.url}) +//
+//
+// ); +// }; + export default Link; \ No newline at end of file diff --git a/client/src/components/LinkList.js b/client/src/components/LinkList.js index 79b394c..adfa286 100644 --- a/client/src/components/LinkList.js +++ b/client/src/components/LinkList.js @@ -1,9 +1,9 @@ import React from 'react'; import Link from './Link'; - +import { LINKS_PER_PAGE } from '../constants'; import { useQuery, gql } from '@apollo/client'; -const FEED_QUERY = gql` +export const FEED_QUERY = gql` { feed { id @@ -12,25 +12,35 @@ const FEED_QUERY = gql` createdAt url description + postedBy { + id + name + } + votes { + id + user { + id + } + } } } } `; const LinkList = () => { - const { data } = useQuery(FEED_QUERY); + const { data } = useQuery(FEED_QUERY); - return ( -
- {data && ( - <> - {data.feed.links.map((link) => ( - - ))} - - )} -
- ); + return ( +
+ {data && ( + <> + {data.feed.links.map((link, index) => ( + + ))} + + )} +
+ ); }; // const LinkList = () => { diff --git a/client/src/components/Search.js b/client/src/components/Search.js new file mode 100644 index 0000000..3999754 --- /dev/null +++ b/client/src/components/Search.js @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { useMutation } from '@apollo/client'; +import { useLazyQuery } from '@apollo/client'; +import gql from 'graphql-tag'; +import Link from './Link'; + +const FEED_SEARCH_QUERY = gql` + query FeedSearchQuery($filter: String!) { + feed(filter: $filter) { + id + links { + id + url + description + createdAt + postedBy { + id + name + } + votes { + id + user { + id + } + } + } + } + } +`; + +const Search = () => { + const [searchFilter, setSearchFilter] = useState(''); + const [executeSearch, { data }] = useLazyQuery( + FEED_SEARCH_QUERY + ); + return ( + <> +
+ Search + setSearchFilter(e.target.value)} + /> + +
+ {data && + data.feed.links.map((link, index) => ( + + ))} + + ); +}; + +export default Search; \ No newline at end of file diff --git a/client/src/constants.js b/client/src/constants.js index 4a08f42..28d0c71 100644 --- a/client/src/constants.js +++ b/client/src/constants.js @@ -1 +1,2 @@ -export const AUTH_TOKEN = 'auth-token'; \ No newline at end of file +export const AUTH_TOKEN = 'auth-token'; +export const LINKS_PER_PAGE = 3; \ No newline at end of file diff --git a/client/src/utils.js b/client/src/utils.js new file mode 100644 index 0000000..8244897 --- /dev/null +++ b/client/src/utils.js @@ -0,0 +1,45 @@ +function timeDifference(current, previous) { + const milliSecondsPerMinute = 60 * 1000; + const milliSecondsPerHour = milliSecondsPerMinute * 60; + const milliSecondsPerDay = milliSecondsPerHour * 24; + const milliSecondsPerMonth = milliSecondsPerDay * 30; + const milliSecondsPerYear = milliSecondsPerDay * 365; + + const elapsed = current - previous; + + if (elapsed < milliSecondsPerMinute / 3) { + return 'just now'; + } + + if (elapsed < milliSecondsPerMinute) { + return 'less than 1 min ago'; + } else if (elapsed < milliSecondsPerHour) { + return ( + Math.round(elapsed / milliSecondsPerMinute) + + ' min ago' + ); + } else if (elapsed < milliSecondsPerDay) { + return ( + Math.round(elapsed / milliSecondsPerHour) + ' h ago' + ); + } else if (elapsed < milliSecondsPerMonth) { + return ( + Math.round(elapsed / milliSecondsPerDay) + ' days ago' + ); + } else if (elapsed < milliSecondsPerYear) { + return ( + Math.round(elapsed / milliSecondsPerMonth) + ' mo ago' + ); + } else { + return ( + Math.round(elapsed / milliSecondsPerYear) + + ' years ago' + ); + } +} + +export function timeDifferenceForDate(date) { + const now = new Date().getTime(); + const updated = new Date(date).getTime(); + return timeDifference(now, updated); +} \ No newline at end of file diff --git a/server/prisma/dev.db b/server/prisma/dev.db index d3758df..f2f4ae0 100644 Binary files a/server/prisma/dev.db and b/server/prisma/dev.db differ diff --git a/server/src/resolvers/Query.js b/server/src/resolvers/Query.js index e20900b..4469fae 100644 --- a/server/src/resolvers/Query.js +++ b/server/src/resolvers/Query.js @@ -1,11 +1,11 @@ async function feed(parent, args, context, info) { const where = args.filter ? { - OR: [ - { description: { contains: args.filter } }, - { url: { contains: args.filter } } - ] - } + OR: [ + { description: { contains: args.filter } }, + { url: { contains: args.filter } } + ] + } : {}; const links = await context.prisma.link.findMany({