feat: connect feed filters apis with ui, change filters list style

This commit is contained in:
MTG2000
2022-05-20 15:32:38 +03:00
parent 86e832e04b
commit 80c9e31467
46 changed files with 445 additions and 250 deletions

View File

@@ -18,7 +18,7 @@ export default function HackathonsPage() {
take: 10,
skip: 0,
sortBy: sortByFilter,
category: topicsFilter
topic: Number(topicsFilter)
},
})
const { fetchMore, isFetchingMore } = useInfiniteQuery(feedQuery, 'getFeed')

View File

@@ -12,7 +12,7 @@ interface Props {
export default function CommentCard({ comment, onReply }: Props) {
return (
<div className="border rounded-12 p-24">
<Header author={comment.author} date={comment.created_at} />
<Header author={comment.author} date={comment.createdAt} />
<p className="text-body4 mt-16">
{comment.body}
</p>

View File

@@ -4,7 +4,7 @@ import { Author } from "src/features/Posts/types";
export interface Comment {
id: number
author: Author
created_at: string
createdAt: string
body: string
votes_count: number
parentId: number | null

View File

@@ -12,7 +12,7 @@ export type BountyCardType = Pick<Bounty,
| 'type'
| 'title'
| 'cover_image'
| 'date'
| 'createdAt'
| 'author'
| 'excerpt'
| 'votes_count'
@@ -33,7 +33,7 @@ export default function BountyCard({ bounty }: Props) {
<div className="bg-white rounded-12 overflow-hidden border">
<img src={bounty.cover_image} className='h-[200px] w-full object-cover bg-gray-100' alt="" />
<div className="p-24">
<Header author={bounty.author} date={bounty.date} />
<Header author={bounty.author} date={bounty.createdAt} />
<div className="flex flex-col gap-8 md:gap-0 md:flex-row justify-between">
<div>
<Link to={`/blog/post/Bounty/${bounty.id}`}>

View File

@@ -6,7 +6,7 @@ interface Props {
author: {
id: number,
name: string,
image: string
avatar: string
}
date: string;
size?: 'sm' | 'md' | 'lg';
@@ -27,21 +27,25 @@ const nameSize: UnionToObjectKeys<Props, 'size'> = {
export default function Header({
size = 'md',
showTimeAgo = true,
showTimeAgo = false,
...props }: Props) {
const passedTime = dayjs().diff(props.date, 'hour');
const dateToShow = passedTime < 24 ?
`${dayjs().diff(props.date, 'hour')}h ago`
:
dayjs(props.date).format('MMMM DD');
const dateToShow = () => {
const passedTime = dayjs().diff(props.date, 'hour');
if (passedTime === 0) return 'now';
if (passedTime < 24) return `${dayjs().diff(props.date, 'hour')}h ago`
return dayjs(props.date).format('MMMM DD');
}
return (
<div className='flex gap-8'>
<Avatar width={avatarSize[size]} src={props.author.image} />
<Avatar width={avatarSize[size]} src={props.author.avatar} />
<div>
<p className={`${nameSize[size]} text-black font-medium`}>{props.author.name}</p>
<p className={`text-body6 text-gray-600`}>{dateToShow}</p>
<p className={`text-body6 text-gray-600`}>{dateToShow()}</p>
</div>
{/* {showTimeAgo && <p className={`${nameSize[size]} text-gray-500 ml-auto `}>
{dayjs().diff(props.date, 'hour') < 24 ? `${dayjs().diff(props.date, 'hour')}h ago` : undefined}

View File

@@ -11,7 +11,7 @@ export type QuestionCardType = Pick<Question,
| 'id'
| 'type'
| 'title'
| 'date'
| 'createdAt'
| 'author'
| 'excerpt'
| 'votes_count'
@@ -22,7 +22,7 @@ export type QuestionCardType = Pick<Question,
| 'id'
| 'author'
| 'body'
| 'created_at'
| 'createdAt'
>>
};
interface Props {
@@ -33,7 +33,7 @@ export default function QuestionCard({ question }: Props) {
<div className="bg-white rounded-12 overflow-hidden border">
{/* <img src={question.cover_image} className='h-[200px] w-full object-cover' alt="" /> */}
<div className="p-24">
<Header author={question.author} date={question.date} />
<Header author={question.author} date={question.createdAt} />
<div className="flex justify-between">
<Link to={`/blog/post/Question/${question.id}`}>
<h2 className="text-h5 font-bolder mt-16">{question.title}</h2>
@@ -61,7 +61,7 @@ export default function QuestionCard({ question }: Props) {
<div className="flex p-16 mt-16 flex-col gap-10 bg-gray-50">
<div className="flex flex-col gap-10">
{question.comments.slice(0, 2).map(comment => <div key={comment.id} className="border-b last-of-type:border-b-0 pb-8 " >
<Header author={comment.author} size='sm' date={comment.created_at} />
<Header author={comment.author} size='sm' date={comment.createdAt} />
<p className="text-body5 text-gray-600 mt-8">{trimText(comment.body, 80)}</p>
</div>)}
</div>

View File

@@ -10,7 +10,7 @@ export type StoryCardType = Pick<Story,
| 'type'
| 'title'
| 'cover_image'
| 'date'
| 'createdAt'
| 'author'
| 'excerpt'
| 'votes_count'
@@ -25,7 +25,7 @@ export default function StoryCard({ story }: Props) {
<div className="bg-white rounded-12 overflow-hidden border">
<img src={story.cover_image} className='h-[200px] w-full object-cover' alt="" />
<div className="p-24">
<Header author={story.author} date={story.date} />
<Header author={story.author} date={story.createdAt} />
<Link to={`/blog/post/Story/${story.id}`}>
<h2 className="text-h5 font-bolder mt-16">{story.title}</h2>
</Link>

View File

@@ -26,7 +26,7 @@ export default function TrendingCard() {
trendingPosts.data?.getTrendingPosts.map(post => {
return <Link key={post.id} to={`/blog/post/${post.__typename}/${post.id}`} className="border-b pb-4 last-of-type:border-b-0">
<li className="flex items-start gap-8">
<Avatar width={24} src={post.author.image} />
<Avatar width={24} src={post.author.avatar} />
<p className="text-body5 font-medium">{post.title}</p>
</li>
</Link>

View File

@@ -5,7 +5,7 @@ query TrendingPosts {
title
author {
id
image
avatar
}
}
... on Bounty {
@@ -13,7 +13,7 @@ query TrendingPosts {
title
author {
id
image
avatar
}
}
@@ -22,7 +22,7 @@ query TrendingPosts {
title
author {
id
image
avatar
}
}
}

View File

@@ -4,7 +4,7 @@ import { useFeedQuery } from 'src/graphql'
import { useAppSelector, useInfiniteQuery } from 'src/utils/hooks'
import PostsList from '../../Components/PostsList/PostsList'
import TrendingCard from '../../Components/TrendingCard/TrendingCard'
import PopularCategories from './PopularCategories/PopularCategories'
import PopularTopicsFilter from './PopularTopicsFilter/PopularTopicsFilter'
import SortBy from './SortBy/SortBy'
import styles from './styles.module.scss'
@@ -12,15 +12,15 @@ import styles from './styles.module.scss'
export default function FeedPage() {
const [sortByFilter, setSortByFilter] = useState('all')
const [categoryFilter, setCategoryFilter] = useState('all')
const [topicFilter, setTopicFilter] = useState<number | null>(null)
const feedQuery = useFeedQuery({
variables: {
take: 10,
take: 3,
skip: 0,
sortBy: sortByFilter,
category: categoryFilter
topic: topicFilter
},
})
const { fetchMore, isFetchingMore } = useInfiniteQuery(feedQuery, 'getFeed')
@@ -42,9 +42,9 @@ export default function FeedPage() {
<SortBy
filterChanged={setSortByFilter}
/>
<hr className="my-24 bg-gray-100" />
<PopularCategories
filterChanged={setCategoryFilter}
<div className="my-24"></div>
<PopularTopicsFilter
filterChanged={setTopicFilter}
/>
</div>
</aside>

View File

@@ -1,20 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import PopularCategories from './PopularCategories';
export default {
title: 'Posts/Feed Page/Components/PopularCategories',
component: PopularCategories,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof PopularCategories>;
const Template: ComponentStory<typeof PopularCategories> = (args) => <div className="max-w-[326px]"><PopularCategories {...args as any} ></PopularCategories></div>
export const Default = Template.bind({});
Default.args = {
}

View File

@@ -1,54 +0,0 @@
import React, { useState } from 'react'
const filters = [
{
text: "🔥 All",
value: 'all'
}, {
text: "Lightning Network",
value: 'lightning-network'
}, {
text: "Bitcoin",
value: 'bitcoin'
}, {
text: "Cybersecurity",
value: 'cybersecurity'
}, {
text: "Bounties",
value: 'bounties'
}, {
text: "Grants",
value: 'Grants'
},
]
interface Props {
filterChanged?: (newFilter: string) => void
}
export default function PopularCategories({ filterChanged }: Props) {
const [selected, setSelected] = useState(filters[0].value);
const filterClicked = (newValue: string) => {
if (selected === newValue)
return
setSelected(newValue);
filterChanged?.(newValue);
}
return (
<div className=''>
<p className="text-body2 font-bolder text-black mb-16">Popular Categories</p>
<ul>
{filters.map((f, idx) => <li
key={f.value}
className={`p-12 rounded-8 cursor-pointer font-bold ${f.value === selected && 'bg-gray-100'}`}
onClick={() => filterClicked(f.value)}
>
{f.text}
</li>)}
</ul>
</div>
)
}

View File

@@ -0,0 +1,20 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import PopularTopicsFilter from './PopularTopicsFilter';
export default {
title: 'Posts/Feed Page/Components/Popular Topics Filter',
component: PopularTopicsFilter,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof PopularTopicsFilter>;
const Template: ComponentStory<typeof PopularTopicsFilter> = (args) => <div className="max-w-[326px]"><PopularTopicsFilter {...args as any} ></PopularTopicsFilter></div>
export const Default = Template.bind({});
Default.args = {
}

View File

@@ -0,0 +1,74 @@
import React, { useState } from 'react'
import Skeleton from 'react-loading-skeleton';
import { usePopularTopicsQuery } from 'src/graphql';
const filters = [
{
text: "🔥 All",
value: 0
}, {
text: "Lightning Network",
value: 1
}, {
text: "Bitcoin",
value: 2
}, {
text: "Cybersecurity",
value: 3
}, {
text: "Bounties",
value: 4
}, {
text: "Grants",
value: 5
},
]
interface Props {
filterChanged?: (newFilter: number) => void
}
export default function PopularTopicsFilter({ filterChanged }: Props) {
const [selected, setSelected] = useState(filters[0].value);
const topicsQuery = usePopularTopicsQuery();
const filterClicked = (newValue: number) => {
if (selected === newValue)
return
setSelected(newValue);
filterChanged?.(newValue);
}
return (
<div className='bg-white border rounded-12 p-16'>
<p className="text-body2 font-bolder text-black mb-16">Topics</p>
<ul className=' flex flex-col gap-16'>
{topicsQuery.loading ?
Array(4).fill(0).map((_, idx) => <li
key={idx}
className={`flex items-start rounded-8 font-bold`}
>
<span className='bg-gray-50 rounded-8 w-40 h-40 text-center py-8'> </span>
<span className="self-center px-16"><Skeleton width={'10ch'} />
</span>
</li>
)
:
topicsQuery.data?.popularTopics.map((f, idx) => <li
key={f.id}
className={`flex items-start rounded-8 cursor-pointer font-bold ${f.id === selected && 'bg-gray-50'}`}
onClick={() => filterClicked(f.id)}
>
<span className='bg-gray-50 rounded-8 w-40 h-40 text-center py-8'>{f.icon}</span>
<span className="self-center px-16">
{f.title}
</span>
</li>)}
</ul>
</div>
)
}

View File

@@ -0,0 +1,7 @@
query PopularTopics {
popularTopics {
id
title
icon
}
}

View File

@@ -29,7 +29,7 @@ export default function SortBy({ filterChanged }: Props) {
}
return (
<div className=''>
<div className='bg-white border rounded-12 p-16'>
<p className="text-body2 font-bolder text-black mb-16">Sort By</p>
<ul>
{filters.map((f, idx) => <li

View File

@@ -1,13 +1,13 @@
query Feed($take: Int, $skip: Int, $sortBy: String, $category: String) {
getFeed(take: $take, skip: $skip, sortBy: $sortBy, category: $category) {
query Feed($take: Int, $skip: Int, $sortBy: String, $topic: Int) {
getFeed(take: $take, skip: $skip, sortBy: $sortBy, topic: $topic) {
... on Story {
id
title
date
createdAt
author {
id
name
image
avatar
}
excerpt
tags {
@@ -22,11 +22,11 @@ query Feed($take: Int, $skip: Int, $sortBy: String, $category: String) {
... on Bounty {
id
title
date
createdAt
author {
id
name
image
avatar
}
excerpt
tags {
@@ -43,11 +43,11 @@ query Feed($take: Int, $skip: Int, $sortBy: String, $category: String) {
... on Question {
id
title
date
createdAt
author {
id
name
image
avatar
}
excerpt
tags {
@@ -59,12 +59,12 @@ query Feed($take: Int, $skip: Int, $sortBy: String, $category: String) {
answers_count
comments {
id
created_at
createdAt
body
author {
id
name
image
avatar
}
}
}

View File

@@ -11,7 +11,7 @@ export default function AuthorCard({ author }: Props) {
return (
<div className="bg-white p-16 border rounded-8">
<div className='flex gap-8'>
<Avatar width={48} src={author.image} />
<Avatar width={48} src={author.avatar} />
<div>
<p className={`'text-body4' text-black font-medium`}>{author.name}</p>
<p className={`text-body6 text-gray-600`}>Joined on {dayjs(author.join_date).format('MMMM DD, YYYY')}</p>

View File

@@ -9,6 +9,8 @@ import Button from "src/Components/Button/Button";
import { FiGithub, FiShare2 } from "react-icons/fi";
import BountyApplicants from "./BountyApplicants";
import VoteButton from "src/Components/VoteButton/VoteButton";
import { RiFlashlightLine } from "react-icons/ri";
import { numberFormatter } from "src/utils/helperFunctions";
interface Props {
@@ -21,14 +23,16 @@ export default function BountyPageContent({ bounty }: Props) {
{/* Header */}
<div className="flex flex-col gap-24">
<Header size="lg" showTimeAgo={false} author={bounty.author} date={bounty.date} />
<Header size="lg" showTimeAgo={false} author={bounty.author} date={bounty.createdAt} />
<h1 className="text-h2 font-bolder">{bounty.title} <Badge color="none" size="sm" className="bg-warning-500 text-black">Bounty</Badge></h1>
<div className="">
<span className="text-body4 text-gray-600 font-bolder">Reward: </span>
<span className="text-body4 text-purple-500 font-medium">{bounty.reward_amount} sats</span>
</div>
<div className="flex gap-24 items-center">
<VoteButton initVotes={bounty.votes_count} />
<div className="text-black font-medium">
<RiFlashlightLine /> <span className="align-middle text-body5">{numberFormatter(bounty.votes_count)} votes</span>
</div>
<div className="text-black font-medium">
<BiComment /> <span className="align-middle text-body5">32 Comments</span>
</div>

View File

@@ -1,10 +1,12 @@
import { BsBookmark } from "react-icons/bs"
import { MdIosShare, MdLocalFireDepartment } from "react-icons/md"
import { MdIosShare } from "react-icons/md"
import VoteButton from "src/Components/VoteButton/VoteButton"
interface Props {
votes_count: number
}
export default function PostActions() {
export default function PostActions(props: Props) {
const actions = [
{
@@ -19,7 +21,7 @@ export default function PostActions() {
return (
<ul className="bg-white rounded-12 p-16 border flex justify-around md:flex-col gap-32">
<VoteButton initVotes={123} direction='vertical' fillType="upDown" />
<VoteButton initVotes={props.votes_count} direction='vertical' fillType="upDown" />
{actions.map((action, idx) => <li
className={`py-8 px-20 text-body5 flex flex-col justify-center items-center cursor-pointer rounded-8
${'text-gray-500 hover:bg-gray-50 active:bg-gray-100'}`}>

View File

@@ -6,6 +6,7 @@ import Badge from "src/Components/Badge/Badge";
import { BiComment } from "react-icons/bi";
import { RiFlashlightLine } from "react-icons/ri";
import { CommentsSection } from "src/features/Posts/Components/Comments";
import { numberFormatter } from "src/utils/helperFunctions";
interface Props {
@@ -17,7 +18,7 @@ export default function QuestionPageContent({ question }: Props) {
<>
<div id="content" className="bg-white p-32 border rounded-16">
<div className="flex flex-col gap-24">
<Header size="lg" showTimeAgo={false} author={question.author} date={question.date} />
<Header size="lg" showTimeAgo={false} author={question.author} date={question.createdAt} />
<h1 className="text-h2 font-bolder">{question.title}</h1>
<div className="flex gap-8">
{question.tags.map(tag => <Badge key={tag.id} size='sm'>
@@ -26,7 +27,7 @@ export default function QuestionPageContent({ question }: Props) {
</div>
<div className="flex gap-24">
<div className="text-black font-medium">
<RiFlashlightLine /> <span className="align-middle text-body5">{question.votes_count} votes</span>
<RiFlashlightLine /> <span className="align-middle text-body5">{numberFormatter(question.votes_count)} votes</span>
</div>
<div className="text-black font-medium">
<BiComment /> <span className="align-middle text-body5">32 Comments</span>
@@ -38,9 +39,9 @@ export default function QuestionPageContent({ question }: Props) {
</div>
</div>
<div id="comments" className="mt-10">
{/* <div id="comments" className="mt-10">
<CommentsSection comments={question.comments} />
</div>
</div> */}
</>
)
}

View File

@@ -6,6 +6,7 @@ import Badge from "src/Components/Badge/Badge";
import { BiComment } from "react-icons/bi";
import { RiFlashlightLine } from "react-icons/ri";
import { CommentsSection } from "src/features/Posts/Components/Comments";
import { numberFormatter } from "src/utils/helperFunctions";
interface Props {
@@ -17,16 +18,16 @@ export default function StoryPageContent({ story }: Props) {
<>
<div id="content" className="bg-white p-32 border rounded-16">
<div className="flex flex-col gap-24">
<Header size="lg" showTimeAgo={false} author={story.author} date={story.date} />
<Header size="lg" showTimeAgo={false} author={story.author} date={story.createdAt} />
<h1 className="text-h2 font-bolder">{story.title}</h1>
<div className="flex gap-8">
{story.tags.length > 0 && <div className="flex gap-8">
{story.tags.map(tag => <Badge key={tag.id} size='sm'>
{tag.title}
</Badge>)}
</div>
</div>}
<div className="flex gap-24">
<div className="text-black font-medium">
<RiFlashlightLine /> <span className="align-middle text-body5">{story.votes_count} votes</span>
<RiFlashlightLine /> <span className="align-middle text-body5">{numberFormatter(story.votes_count)} votes</span>
</div>
<div className="text-black font-medium">
<BiComment /> <span className="align-middle text-body5">{story.comments_count} Comments</span>
@@ -37,9 +38,9 @@ export default function StoryPageContent({ story }: Props) {
<div className={`mt-42 ${styles.body}`} dangerouslySetInnerHTML={{ __html: marked.parse(story.body) }}>
</div>
</div>
<div id="comments" className="mt-10 comments_col">
{/* <div id="comments" className="mt-10 comments_col">
<CommentsSection comments={story.comments} />
</div>
</div> */}
</>
)
}

View File

@@ -44,7 +44,7 @@ export default function PostDetailsPage() {
top: `${navHeight + 16}px`,
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
}}>
<PostActions />
<PostActions votes_count={post.votes_count} />
</div>
</aside>

View File

@@ -3,11 +3,11 @@ query PostDetails($id: Int!, $type: POST_TYPE!) {
... on Story {
id
title
date
createdAt
author {
id
name
image
avatar
}
body
tags {
@@ -20,25 +20,25 @@ query PostDetails($id: Int!, $type: POST_TYPE!) {
comments_count
comments {
id
created_at
createdAt
body
votes_count
parentId
author {
id
name
image
avatar
}
}
}
... on Bounty {
id
title
date
createdAt
author {
id
name
image
avatar
}
body
tags {
@@ -58,18 +58,18 @@ query PostDetails($id: Int!, $type: POST_TYPE!) {
author {
id
name
image
avatar
}
}
}
... on Question {
id
title
date
createdAt
author {
id
name
image
avatar
}
body
tags {
@@ -81,14 +81,14 @@ query PostDetails($id: Int!, $type: POST_TYPE!) {
answers_count
comments {
id
created_at
createdAt
body
votes_count
parentId
author {
id
name
image
avatar
}
}
}

View File

@@ -4,7 +4,7 @@ import { Tag } from "src/utils/interfaces"
export type User = {
id: number
name: string
image: string
avatar: string
}
export type Author = User & {
@@ -14,7 +14,7 @@ export type Author = User & {
export type PostBase1 = {
id: number
title: string
date: string
createdAt: string
author: Author
excerpt: string
tags: Tag[]

View File

@@ -4,14 +4,16 @@ import ProjectsSection from "./ProjectsSection/ProjectsSection";
export default function ExplorePage() {
return (
<>
<div className="px-32">
<Header />
</div>
<div className="page-container">
<Header />
{/* <div className="my-40 px-32">
<Categories />
</div> */}
<ProjectsSection />
</>
<div className="w-full overflow-hidden">
<ProjectsSection />
</div>
</div>
)
}

View File

@@ -6,10 +6,10 @@ export default function ProjectsRowSkeleton() {
return (
<div className='mb-48'>
<h3 className="font-bolder text-body3 mb-24 px-32">
<h3 className="font-bolder text-body3 mb-24">
<Skeleton width='10ch' />
</h3>
<div className="p-32 flex gap-20">
<div className=" flex gap-20">
{Array(5).fill(0).map((_, idx) => (
<ProjectCardMiniSkeleton key={idx} />
))}

View File

@@ -83,13 +83,13 @@ export default function ProjectsRow({ title, categoryId, projects }: Props) {
return (
<div className='mb-48'>
<h3 className="font-bolder text-body3 mb-24 px-32">
<h3 className="font-bolder text-body3 mb-24">
<span className="align-middle">{title}</span>
{categoryId > 0 && <Link to={`/category/${categoryId}`}>
<MdDoubleArrow className='text-gray-200 ml-8 hover:cursor-pointer transform scale-y-110 scale-x-125 origin-left' />
</Link>}
</h3>
<div className="px-32">
<div className="">
<Carousel
showDots={false}
autoPlay={false}