Progress
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
<Route exact path="/login" component={Login} />
|
||||
<Route exact path="/search" component={Search} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -18,12 +18,9 @@ const Header = () => {
|
||||
top
|
||||
</Link>
|
||||
<div className="ml1">|</div>
|
||||
<Link
|
||||
to="/search"
|
||||
className="ml1 no-underline black"
|
||||
>
|
||||
<Link to="/search" className="ml1 no-underline black">
|
||||
search
|
||||
</Link>
|
||||
</Link>
|
||||
{authToken && (
|
||||
<div className="flex">
|
||||
<div className="ml1">|</div>
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<div>
|
||||
{link.id} - {link.description} ({link.url})
|
||||
</div>
|
||||
<div className="flex mt2 items-start">
|
||||
<div className="flex items-center">
|
||||
<span className="gray">{props.index + 1}.</span>
|
||||
{authToken && (
|
||||
<div
|
||||
className="ml1 gray f11"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={vote}
|
||||
>
|
||||
▲
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml1">
|
||||
<div>
|
||||
{link.description} ({link.url})
|
||||
</div>
|
||||
{authToken && (
|
||||
<div className="f6 lh-copy gray">
|
||||
{link.votes.length} votes | by{' '}
|
||||
{link.postedBy ? link.postedBy.name : 'Unknown'}{' '}
|
||||
{timeDifferenceForDate(link.createdAt)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// const Link = (props) => {
|
||||
// const { link } = props;
|
||||
// return (
|
||||
// <div>
|
||||
// <div>
|
||||
// {link.id} - {link.description} ({link.url})
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
export default Link;
|
||||
@@ -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 (
|
||||
<div>
|
||||
{data && (
|
||||
<>
|
||||
{data.feed.links.map((link) => (
|
||||
<Link key={link.id} link={link} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
{data && (
|
||||
<>
|
||||
{data.feed.links.map((link, index) => (
|
||||
<Link key={link.id} link={link} index={index} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// const LinkList = () => {
|
||||
|
||||
62
client/src/components/Search.js
Normal file
62
client/src/components/Search.js
Normal file
@@ -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 (
|
||||
<>
|
||||
<div>
|
||||
Search
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => setSearchFilter(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
onClick={() =>
|
||||
executeSearch({
|
||||
variables: { filter: searchFilter }
|
||||
})
|
||||
}
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
{data &&
|
||||
data.feed.links.map((link, index) => (
|
||||
<Link key={link.id} link={link} index={index} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
||||
@@ -1 +1,2 @@
|
||||
export const AUTH_TOKEN = 'auth-token';
|
||||
export const AUTH_TOKEN = 'auth-token';
|
||||
export const LINKS_PER_PAGE = 3;
|
||||
45
client/src/utils.js
Normal file
45
client/src/utils.js
Normal file
@@ -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);
|
||||
}
|
||||
Binary file not shown.
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user