feat: story content page, bounty content page

This commit is contained in:
MTG2000
2022-04-19 17:54:06 +03:00
parent e5afadd82a
commit c73a21190b
18 changed files with 312 additions and 25 deletions

View File

@@ -1,7 +1,6 @@
const {
intArg,
objectType,
stringArg,
extendType,
nonNull,
interfaceType,

30
package-lock.json generated
View File

@@ -33,6 +33,7 @@
"linkifyjs": "^3.0.5",
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
"marked": "^4.0.14",
"nexus": "^1.3.0",
"prisma": "3.5.0",
"react": "^17.0.2",
@@ -69,6 +70,7 @@
"@storybook/react": "^6.3.12",
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.throttle": "^4.1.6",
"@types/marked": "^4.0.3",
"@types/react-copy-to-clipboard": "^5.0.2",
"autoprefixer": "^9.8.8",
"gh-pages": "^3.2.3",
@@ -9644,6 +9646,12 @@
"@types/react": "*"
}
},
"node_modules/@types/marked": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz",
"integrity": "sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg==",
"dev": true
},
"node_modules/@types/mdast": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@@ -24915,6 +24923,17 @@
"react": ">= 0.14.0"
}
},
"node_modules/marked": {
"version": "4.0.14",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz",
"integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/match-sorter": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
@@ -68092,6 +68111,12 @@
"@types/react": "*"
}
},
"@types/marked": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz",
"integrity": "sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg==",
"dev": true
},
"@types/mdast": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@@ -80011,6 +80036,11 @@
"dev": true,
"requires": {}
},
"marked": {
"version": "4.0.14",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz",
"integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ=="
},
"match-sorter": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",

View File

@@ -28,6 +28,7 @@
"linkifyjs": "^3.0.5",
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
"marked": "^4.0.14",
"nexus": "^1.3.0",
"prisma": "3.5.0",
"react": "^17.0.2",
@@ -119,6 +120,7 @@
"@storybook/react": "^6.3.12",
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.throttle": "^4.1.6",
"@types/marked": "^4.0.3",
"@types/react-copy-to-clipboard": "^5.0.2",
"autoprefixer": "^9.8.8",
"gh-pages": "^3.2.3",

View File

@@ -6,8 +6,19 @@ import Badge from "src/Components/Badge/Badge"
import Button from "src/Components/Button/Button"
import { Link } from "react-router-dom"
type BountyCardType = Pick<Bounty,
| 'title'
| 'cover_image'
| 'date'
| 'author'
| 'excerpt'
| 'votes_count'
| "reward_amount"
| "tags"
| "applicants_count"
>;
interface Props {
bounty: Bounty
bounty: BountyCardType
}
export default function BountyCard({ bounty }: Props) {
return (

View File

@@ -3,12 +3,20 @@ import { Question } from "src/features/Posts/types"
import Header from "../Header/Header"
import { FiUsers } from "react-icons/fi"
import Badge from "src/Components/Badge/Badge"
import Avatar from "src/features/Profiles/Components/Avatar/Avatar"
import dayjs from "dayjs"
import { Link } from "react-router-dom"
type QuestionCardType = Pick<Question,
| 'title'
| 'date'
| 'author'
| 'excerpt'
| 'votes_count'
| "tags"
| "answers_count"
| "comments"
>;
interface Props {
question: Question
question: QuestionCardType
}
export default function QuestionCard({ question }: Props) {
return (

View File

@@ -4,8 +4,18 @@ import Header from "../Header/Header"
import { BiComment } from 'react-icons/bi'
import { Link } from "react-router-dom"
type StoryCardType = Pick<Story,
| 'title'
| 'cover_image'
| 'date'
| 'author'
| 'excerpt'
| 'votes_count'
| 'comments_count'
>;
interface Props {
story: Story
story: StoryCardType
}
export default function StoryCard({ story }: Props) {
return (

View File

@@ -4,7 +4,7 @@ import { MOCK_DATA } from 'src/mocks/data';
import AuthorCard from './AuthorCard';
export default {
title: 'Posts/Post Page/Components/AuthorCard',
title: 'Posts/Post Details Page/Components/AuthorCard',
component: AuthorCard,
argTypes: {
backgroundColor: { control: 'color' },

View File

@@ -0,0 +1,76 @@
import Header from "src/features/Posts/Components/PostCard/Header/Header"
import { Bounty, } from "src/features/Posts/types"
import { marked } from 'marked';
import styles from './styles.module.css'
import Badge from "src/Components/Badge/Badge";
import { BiComment } from "react-icons/bi";
import VotesCount from "src/Components/VotesCount/VotesCount";
import Button from "src/Components/Button/Button";
import { FiGithub, FiShare2 } from "react-icons/fi";
interface Props {
bounty: Bounty
}
export default function BountyPageContent({ bounty }: Props) {
return (
<div className="bg-white p-32 border rounded-16">
{/* Header */}
<div className="flex flex-col gap-24">
<Header size="lg" showTimeAgo={false} author={bounty.author} date={bounty.date} />
<h1 className="text-h2 font-bolder">{bounty.title} <Badge color="none" size="sm" className="bg-yellow-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">
<VotesCount count={bounty.votes_count} />
<div className="text-black font-medium">
<BiComment /> <span className="align-middle text-body5">32 Comments</span>
</div>
</div>
<div className="flex gap-16">
<Button size='sm' color="black" className="!font-normal">
Express Interest
</Button>
<Button size='sm' color="none" className="bg-gray-500 text-white !font-normal">
<FiGithub className="text-body2" /> View On Github
</Button>
<Button size='sm' color="none" className="bg-gray-500 text-white !font-normal">
<FiShare2 className="text-body2" /> Share
</Button>
</div>
</div>
<div className={`mt-42 ${styles.body}`} dangerouslySetInnerHTML={{ __html: marked.parse(bounty.body) }}>
</div>
{/* Body */}
<div className="flex gap-8">
{bounty.tags.map(tag => <Badge key={tag.id} size='sm'>
{tag.title}
</Badge>)}
</div>
{/* Applicants */}
<div className="border bg-white rounded-8 p-24 mt-16">
<h4 className="text-body2 font-bolder">Applicants</h4>
<ul className="flex flex-col gap-16 mt-16">
<li>
<Header author={bounty.author} size='sm' date={bounty.date} />
<div className="bg-gray-100 mt-10 p-16 rounded-8">
<p className="text-body5 font-medium mb-8">Work Plan</p>
<p className="text-body5 text-gray-600">I will create the widget using nextjs, react and typescript. and also convert it into a npm package.</p>
</div>
</li>
<li>
<Header author={bounty.author} size='sm' date={bounty.date} />
<div className="bg-gray-100 mt-10 p-16 rounded-8">
<p className="text-body5 font-medium mb-8">Work Plan</p>
<p className="text-body5 text-gray-600">I will create the widget using nextjs, react and typescript. and also convert it into a npm package.</p>
</div>
</li>
</ul>
</div>
</div>
)
}

View File

@@ -0,0 +1,27 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { MOCK_DATA } from 'src/mocks/data';
import PageContent from './PageContent';
export default {
title: 'Posts/Post Details Page/Components/PageContent',
component: PageContent,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof PageContent>;
const Template: ComponentStory<typeof PageContent> = (args) => <div className="max-w-[890px]"><PageContent {...args as any} ></PageContent></div>
export const Story = Template.bind({});
Story.args = {
post: MOCK_DATA.posts.stories[0]
}
export const Bounty = Template.bind({});
Bounty.args = {
post: MOCK_DATA.posts.bounties[0]
}

View File

@@ -1,5 +1,12 @@
import Header from "src/features/Posts/Components/PostCard/Header/Header"
import { Post } from "src/features/Posts/types"
import { isBounty, isQuestion, isStory, Post } from "src/features/Posts/types"
import { marked } from 'marked';
import styles from './styles.module.css'
import Badge from "src/Components/Badge/Badge";
import { BiComment } from "react-icons/bi";
import { RiFlashlightLine } from "react-icons/ri";
import StoryPageContent from "./StoryPageContent";
import BountyPageContent from "./BountyPageContent";
interface Props {
@@ -7,9 +14,15 @@ interface Props {
}
export default function PageContent({ post }: Props) {
return (
<div>
<Header size="lg" showTimeAgo={false} author={post.author} date={post.date} />
</div>
)
if (isStory(post))
return <StoryPageContent story={post} />
if (isBounty(post))
return <BountyPageContent bounty={post} />
// if (isQuestion(post))
// return <QuestionCard question={post} />
return null
}

View File

@@ -0,0 +1,39 @@
import Header from "src/features/Posts/Components/PostCard/Header/Header"
import { Story } from "src/features/Posts/types"
import { marked } from 'marked';
import styles from './styles.module.css'
import Badge from "src/Components/Badge/Badge";
import { BiComment } from "react-icons/bi";
import { RiFlashlightLine } from "react-icons/ri";
interface Props {
story: Story
}
export default function StoryPageContent({ story }: Props) {
return (
<div 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} />
<h1 className="text-h2 font-bolder">{story.title}</h1>
<div className="flex gap-8">
{story.tags.map(tag => <Badge key={tag.id} size='sm'>
{tag.title}
</Badge>)}
</div>
<div className="flex gap-24">
<div className="text-black font-medium">
<RiFlashlightLine /> <span className="align-middle text-body5">{story.votes_count} votes</span>
</div>
<div className="text-black font-medium">
<BiComment /> <span className="align-middle text-body5">32 Comments</span>
</div>
</div>
</div>
<div className={`mt-42 ${styles.body}`} dangerouslySetInnerHTML={{ __html: marked.parse(story.body) }}>
</div>
</div>
)
}

View File

@@ -0,0 +1,32 @@
.body h1 {
font-size: 48px;
line-height: 54px;
margin-bottom: min(0.5em);
}
.body h2 {
font-size: 36px;
line-height: 50px;
margin-bottom: min(0.5em);
}
.body h3 {
font-size: 29px;
line-height: 40px;
margin-bottom: min(0.5em);
}
.body h4 {
font-size: 22px;
line-height: 31px;
margin-bottom: min(0.5em);
}
.body h5 {
font-size: 19px;
line-height: 26px;
margin-bottom: min(0.5em);
}
.body p,
.body ul {
font-size: 16px;
line-height: 22px;
margin-bottom: 1.5em;
}

View File

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

View File

@@ -22,9 +22,9 @@ export default function PostActions() {
]
return (
<ul className="bg-white rounded-12 p-16 border w-[96px] flex flex-col gap-32">
<ul className="bg-white rounded-12 p-16 border flex flex-col gap-32">
{actions.map((action, idx) => <li
className={`p-4 text-body5 flex flex-col items-center cursor-pointer rounded-24
className={`py-4 px-16 text-body5 flex flex-col items-center cursor-pointer rounded-24
${idx === 0 ? 'bg-warning-50 hover:bg-warning-100 active:bg-warning-200 text-gray-900 font-medium' : 'text-gray-500 hover:bg-gray-50 active:bg-gray-100'}`}>
<action.icon className={idx === 0 ? "text-fire text-body4 scale-125 mb-4" : "text-body4 mb-8"}></action.icon>
<span>{action.value}</span>

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,7 @@ export type PostBase = {
tags: Tag[]
votes_count: number
type: string
body: string
}
export type Story = PostBase & {
@@ -35,6 +36,10 @@ export type Bounty = PostBase & {
reward_amount: number
deadline: string
applicants_count: number
applicants: {
author: Author
workPlan: string
}[]
}
export function isBounty(post: Post): post is Bounty {

View File

@@ -13,12 +13,46 @@ const getAuthor = () => ({
join_date: date
})
const postBody = `
[Marked] lets you convert [Markdown] into HTML. Markdown is a simple text format whose goal is to be very easy to read and write, even when not converted to HTML. This demo page will let you type anything you like and see how it gets converted. Live. No more waiting around.
### **Why Markdown?**
It's easy. It's not overly bloated, unlike HTML. Also, as the creator of [markdown] says,
A simple markdown editor with preview, implemented with React.js and TypeScript. This React Component aims to provide a simple Markdown editor with syntax highlighting support. This is based on \`textarea\` encapsulation, so it does not depend on any modern code editors such as Acs, CodeMirror, Monaco etc.
![Cover Image](${getCoverImage()})
### **How To Use The Demo**
1. Type in stuff on the left.
2. See the live updates on the right.
That's it. Pretty simple. There's also a drop-down option in the upper right to switch between various views:
- **Preview:** A live display of the generated HTML as it would render in a browser.
- **HTML Source:** The generated HTML before your browser makes it pretty.
- **Lexer Data:** What [marked] uses internally, in case you like gory stuff like this.
- **Quick Reference:** A brief run-down of how to format things using markdown.
Ready to start writing? Either start changing stuff on the left or
[clear everything](/demo/?text=) with a simple click.
[Marked]: https://github.com/markedjs/marked/
[Markdown]: http://daringfireball.net/projects/markdown/
`
export let posts = {
stories: [
{
id: 4,
title: 'Digital Editor, Mars Review of Books',
body: postBody,
cover_image: getCoverImage(),
comments_count: 31,
date,
@@ -30,7 +64,7 @@ export let posts = {
{ id: 2, title: "webln" },
{ id: 3, title: "guide" },
],
author: getAuthor()
author: getAuthor(),
},
] as Story[],
bounties: [
@@ -38,6 +72,7 @@ export let posts = {
type: "Bounty",
id: 22,
title: 'Digital Editor, Mars Review of Books',
body: postBody,
cover_image: getCoverImage(),
applicants_count: 31,
date,

View File

@@ -3,10 +3,10 @@ let coverImgsCntr = -1;
export function getCoverImage() {
const coverImgs = [
'https://picsum.photos/id/10/1660/1200',
'https://picsum.photos/id/1000/1660/1200',
'https://picsum.photos/id/1002/1660/1200',
'https://picsum.photos/id/1018/1660/1200',
'https://picsum.photos/id/10/1600/900',
'https://picsum.photos/id/1000/1600/900',
'https://picsum.photos/id/1002/1600/900',
'https://picsum.photos/id/1018/1600/900',
]
return coverImgs[(++coverImgsCntr) % coverImgs.length]