From 2f9d05b8cb0a2a990e5715bf48fff97300dcc2ad Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Fri, 3 Jun 2022 22:35:31 +0300 Subject: [PATCH] feat: Create story page - topics input component - refactor autocomplete component - create staging slice - fix post details username overflow --- functions/graphql/nexus-typegen.ts | 15 +- functions/graphql/schema.graphql | 10 ++ functions/graphql/types/post.js | 79 +++++++++- src/App.tsx | 2 + .../Inputs/Autocomplete/Autocomplete.tsx | 62 +++----- .../Inputs/FilesInput/FilesInput.tsx | 37 ++++- .../Inputs/TextEditor/SaveModule.tsx | 2 +- src/Components/Navbar/NavDesktop.tsx | 12 +- src/Components/Navbar/NavMobile.tsx | 2 +- src/Components/Navbar/Navbar.tsx | 8 +- .../HackathonCard/HackathonCard.tsx | 5 +- .../Components/SortByFilter/SortByFilter.tsx | 2 + .../pages/HackathonsPage/HackathonsPage.tsx | 2 +- .../Components/PostCard/Header/Header.tsx | 5 +- .../Components/StoryForm/StoryForm.tsx | 143 ++++++++++++++---- .../Components/StoryForm/createStory.graphql | 5 + .../Components/TopicsInput/TopicsInput.tsx | 31 ++++ .../pages/CreatePostPage/PostTypeList.tsx | 20 ++- .../Posts/pages/FeedPage/FeedPage.tsx | 10 ++ .../Posts/pages/FeedPage/SortBy/SortBy.tsx | 2 + .../Components/AuthorCard/AuthorCard.tsx | 5 +- .../Components/PageContent/styles.module.css | 11 ++ .../Components/PostActions/PostActions.tsx | 2 +- .../pages/PostDetailsPage/PostDetailsPage.tsx | 2 +- .../pages/PostDetailsPage/styles.module.scss | 4 + src/graphql/index.tsx | 55 +++++++ src/redux/features/staging.slice.ts | 36 +++++ src/redux/features/user.slice.ts | 30 ++++ src/redux/store.ts | 10 +- src/styles/index.scss | 1 + src/utils/apollo.ts | 2 +- src/utils/hooks/useResizeListener.ts | 7 +- src/utils/interfaces/misc.interfaces.ts | 18 +++ 33 files changed, 538 insertions(+), 99 deletions(-) create mode 100644 src/features/Posts/pages/CreatePostPage/Components/StoryForm/createStory.graphql create mode 100644 src/features/Posts/pages/CreatePostPage/Components/TopicsInput/TopicsInput.tsx create mode 100644 src/redux/features/staging.slice.ts create mode 100644 src/redux/features/user.slice.ts diff --git a/functions/graphql/nexus-typegen.ts b/functions/graphql/nexus-typegen.ts index 9bad7d9..1b64152 100644 --- a/functions/graphql/nexus-typegen.ts +++ b/functions/graphql/nexus-typegen.ts @@ -28,6 +28,14 @@ declare global { } export interface NexusGenInputs { + StoryInputType: { // input type + body: string; // String! + cover_image: string; // String! + id?: number | null; // Int + tags: string[]; // [String!]! + title: string; // String! + topicId: number; // Int! + } } export interface NexusGenEnums { @@ -257,6 +265,7 @@ export interface NexusGenFieldTypes { Mutation: { // field return type confirmDonation: NexusGenRootTypes['Donation']; // Donation! confirmVote: NexusGenRootTypes['Vote']; // Vote! + createStory: NexusGenRootTypes['Story'] | null; // Story donate: NexusGenRootTypes['Donation']; // Donation! vote: NexusGenRootTypes['Vote']; // Vote! } @@ -437,6 +446,7 @@ export interface NexusGenFieldTypeNames { Mutation: { // field return type name confirmDonation: 'Donation' confirmVote: 'Vote' + createStory: 'Story' donate: 'Donation' vote: 'Vote' } @@ -553,6 +563,9 @@ export interface NexusGenArgTypes { payment_request: string; // String! preimage: string; // String! } + createStory: { // args + data?: NexusGenInputs['StoryInputType'] | null; // StoryInputType + } donate: { // args amount_in_sat: number; // Int! } @@ -624,7 +637,7 @@ export interface NexusGenTypeInterfaces { export type NexusGenObjectNames = keyof NexusGenObjects; -export type NexusGenInputNames = never; +export type NexusGenInputNames = keyof NexusGenInputs; export type NexusGenEnumNames = keyof NexusGenEnums; diff --git a/functions/graphql/schema.graphql b/functions/graphql/schema.graphql index 792b8d7..c594405 100644 --- a/functions/graphql/schema.graphql +++ b/functions/graphql/schema.graphql @@ -86,6 +86,7 @@ type LnurlDetails { type Mutation { confirmDonation(payment_request: String!, preimage: String!): Donation! confirmVote(payment_request: String!, preimage: String!): Vote! + createStory(data: StoryInputType): Story donate(amount_in_sat: Int!): Donation! vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote! } @@ -182,6 +183,15 @@ type Story implements PostBase { votes_count: Int! } +input StoryInputType { + body: String! + cover_image: String! + id: Int + tags: [String!]! + title: String! + topicId: Int! +} + type Tag { id: Int! title: String! diff --git a/functions/graphql/types/post.js b/functions/graphql/types/post.js index 51c8143..613e480 100644 --- a/functions/graphql/types/post.js +++ b/functions/graphql/types/post.js @@ -8,9 +8,11 @@ const { stringArg, enumType, arg, + inputObjectType, } = require('nexus'); const { paginationArgs } = require('./helpers'); -const { prisma } = require('../../prisma') +const { prisma } = require('../../prisma'); +const { getUserByPubKey } = require('../../auth/utils/helperFuncs'); const POST_TYPE = enumType({ @@ -136,6 +138,75 @@ const Story = objectType({ }, }) +const StoryInputType = inputObjectType({ + name: 'StoryInputType', + definition(t) { + t.int('id'); + t.nonNull.string('title'); + t.nonNull.string('body'); + t.nonNull.string('cover_image'); + t.nonNull.list.nonNull.string('tags'); + t.nonNull.int('topicId'); + } +}) +const StoryMutation = extendType({ + type: 'Mutation', + definition(t) { + t.field('createStory', { + type: 'Story', + args: { data: StoryInputType }, + async resolve(_root, args, ctx) { + console.log(args.data); + const { id, title, body, cover_image, tags, topicId } = args.data; + const user = await getUserByPubKey(ctx.userPubKey); + + // Do some validation + if (!user) + throw new Error("You have to login"); + // TODO: validate post data + + + // Preprocess & insert + const excerpt = body.replace(/<[^>]+>/g, '').slice(0, 120); + + + return prisma.story.create({ + data: { + title, + body, + cover_image, + excerpt, + tags: { + connectOrCreate: + tags.map(tag => { + tag = tag.toLowerCase().trim(); + return { + where: { + title: tag, + }, + create: { + title: tag + } + } + }) + }, + topic: { + connect: { + id: topicId + } + }, + user: { + connect: { + id: user.id, + } + } + } + }) + } + }) + }, +}) + const BountyApplication = objectType({ name: 'BountyApplication', definition(t) { @@ -325,6 +396,7 @@ module.exports = { BountyApplication, Bounty, Story, + StoryInputType, Question, PostComment, Post, @@ -333,5 +405,8 @@ module.exports = { popularTopics, getFeed, getPostById, - getTrendingPosts + getTrendingPosts, + + // Mutations + StoryMutation } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 57488db..af559fb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ const FeedPage = React.lazy(() => import("./features/Posts/pages/FeedPage/FeedPa const HackathonsPage = React.lazy(() => import("./features/Hackathons/pages/HackathonsPage/HackathonsPage")) const HottestPage = React.lazy(() => import("src/features/Projects/pages/HottestPage/HottestPage")) const PostDetailsPage = React.lazy(() => import("./features/Posts/pages/PostDetailsPage/PostDetailsPage")) +const CreatePostPage = React.lazy(() => import("./features/Posts/pages/CreatePostPage/CreatePostPage")) const CategoryPage = React.lazy(() => import("src/features/Projects/pages/CategoryPage/CategoryPage")) const ExplorePage = React.lazy(() => import("src/features/Projects/pages/ExplorePage")) const DonatePage = React.lazy(() => import("./features/Donations/pages/DonatePage/DonatePage")) @@ -52,6 +53,7 @@ function App() { } /> } /> + } /> } /> } /> diff --git a/src/Components/Inputs/Autocomplete/Autocomplete.tsx b/src/Components/Inputs/Autocomplete/Autocomplete.tsx index d5d8fe4..bd7b873 100644 --- a/src/Components/Inputs/Autocomplete/Autocomplete.tsx +++ b/src/Components/Inputs/Autocomplete/Autocomplete.tsx @@ -1,8 +1,10 @@ +import { useMemo } from "react"; import Select, { StylesConfig } from "react-select"; +import { ControlledStateHandler } from "src/utils/interfaces"; -type Props = { +type Props = { options: T[]; labelField?: keyof T valueField?: keyof T @@ -14,45 +16,13 @@ type Props = { name?: string, className?: string, onBlur?: () => void; - -} & - ( - { - - isMulti: true - onChange?: (values: T[] | null) => void - value?: T[] | null - } - | - { - - isMulti?: false - onChange?: (values: T | null) => void - value?: T | null - } - ) + size?: 'sm' | 'md' | 'lg' +} & ControlledStateHandler -const colourStyles: StylesConfig = { - control: (styles, state) => ({ - ...styles, - padding: '9px 16px', - borderRadius: 12, - }), - indicatorSeparator: (styles, state) => ({ - ...styles, - display: "none" - }), - input: (styles, state) => ({ - ...styles, - " input": { - boxShadow: 'none !important' - }, - }), -}; -export default function AutoComplete({ +export default function AutoComplete({ options, labelField, valueField, @@ -64,10 +34,28 @@ export default function AutoComplete({ value, onChange, onBlur, + size = 'md', ...props +}: Props) { -}: Props) { + const colourStyles: StylesConfig = useMemo(() => ({ + control: (styles, state) => ({ + ...styles, + padding: size === 'md' ? '1px 4px' : '8px 12px', + borderRadius: size === 'md' ? 8 : 12, + }), + indicatorSeparator: (styles, state) => ({ + ...styles, + display: "none" + }), + input: (styles, state) => ({ + ...styles, + " input": { + boxShadow: 'none !important' + }, + }), + }), [size]) return (
diff --git a/src/Components/Inputs/FilesInput/FilesInput.tsx b/src/Components/Inputs/FilesInput/FilesInput.tsx index a71c1f3..396f041 100644 --- a/src/Components/Inputs/FilesInput/FilesInput.tsx +++ b/src/Components/Inputs/FilesInput/FilesInput.tsx @@ -1,6 +1,11 @@ -import React, { ChangeEvent, useRef } from "react" +import { createAction } from "@reduxjs/toolkit"; +import React, { ChangeEvent, useCallback, useRef } from "react" import { BsUpload } from "react-icons/bs"; +import { FaImage } from "react-icons/fa"; import Button from "src/Components/Button/Button" +import { openModal } from "src/redux/features/modals.slice"; +import { useAppDispatch } from "src/utils/hooks"; +import { useReduxEffect } from "src/utils/hooks/useReduxEffect"; import { UnionToObjectKeys } from "src/utils/types/utils"; import FilesThumbnails from "./FilesThumbnails"; @@ -28,6 +33,9 @@ const fileUrlToObject = async (url: string, fileName: string = 'filename') => { return file } +const INSERT_IMAGE_ACTION = createAction<{ src: string, alt?: string }>('COVER_IMAGE_INSERTED')({ src: '', alt: "" }) + + export default function FilesInput({ multiple, value, @@ -41,10 +49,33 @@ export default function FilesInput({ const ref = useRef(null!) + const dispatch = useAppDispatch(); + const handleClick = () => { - ref.current.click(); + // ref.current.click(); + dispatch(openModal({ + Modal: "InsertImageModal", + props: { + callbackAction: { + type: INSERT_IMAGE_ACTION.type, + payload: { + src: "", + alt: "" + } + } + } + })) } + const onInsertImgUrl = useCallback(({ payload: { src, alt } }: typeof INSERT_IMAGE_ACTION) => { + if (typeof value === 'string') + onChange?.([value, src]); + else + onChange?.([...(value ?? []), src]); + }, [onChange, value]) + + useReduxEffect(onInsertImgUrl, INSERT_IMAGE_ACTION.type) + const handleChange = (e: ChangeEvent) => { const files = e.target.files && Array.from(e.target.files).slice(0, max); if (typeof value === 'string') @@ -80,7 +111,7 @@ export default function FilesInput({ const uploadBtn = props.uploadBtn ? React.cloneElement(props.uploadBtn, { onClick: handleClick }) : - + return ( <> diff --git a/src/Components/Inputs/TextEditor/SaveModule.tsx b/src/Components/Inputs/TextEditor/SaveModule.tsx index 2ce6dd3..c7bbe93 100644 --- a/src/Components/Inputs/TextEditor/SaveModule.tsx +++ b/src/Components/Inputs/TextEditor/SaveModule.tsx @@ -23,7 +23,7 @@ export default function SaveModule(props: Props) { useRemirrorContext(changeCallback) - useEvent('blur', () => onBlur()) + // useEvent('focus', () => onBlur()) return <> } diff --git a/src/Components/Navbar/NavDesktop.tsx b/src/Components/Navbar/NavDesktop.tsx index 9ae3d38..67a8f9a 100644 --- a/src/Components/Navbar/NavDesktop.tsx +++ b/src/Components/Navbar/NavDesktop.tsx @@ -15,7 +15,6 @@ import { } from '@szhsin/react-menu'; import '@szhsin/react-menu/dist/index.css'; import { FiAward, FiChevronDown, FiFeather, FiLogIn, FiMic } from "react-icons/fi"; -import { useMeQuery } from "src/graphql"; import Avatar from "src/features/Profiles/Components/Avatar/Avatar"; @@ -24,10 +23,11 @@ export default function NavDesktop() { const communityRef = useRef(null); const [communitymenuProps, toggleCommunityMenu] = useMenuState({ transition: true }); - const meQuery = useMeQuery(); - const { isWalletConnected } = useAppSelector((state) => ({ + + const { isWalletConnected, curUser } = useAppSelector((state) => ({ isWalletConnected: state.wallet.isConnected, + curUser: state.user.me, })); @@ -165,9 +165,9 @@ export default function NavDesktop() { } - {!meQuery.loading && - (meQuery.data?.me ? - }> + {curUser !== undefined && + (curUser ? + }> diff --git a/src/Components/Navbar/NavMobile.tsx b/src/Components/Navbar/NavMobile.tsx index 9640089..83be487 100644 --- a/src/Components/Navbar/NavMobile.tsx +++ b/src/Components/Navbar/NavMobile.tsx @@ -88,7 +88,7 @@ export default function NavMobile() { >
)} { + dispatch(setUser(data.me)) + } + }); useEffect(() => { const nav = document.querySelector("nav"); @@ -63,7 +70,6 @@ export default function Navbar() { document.body.style.paddingTop = `${nav.clientHeight}px`; } } - return () => { document.body.style.paddingTop = oldPadding } diff --git a/src/features/Hackathons/Components/HackathonCard/HackathonCard.tsx b/src/features/Hackathons/Components/HackathonCard/HackathonCard.tsx index 3478f78..ba5a9d4 100644 --- a/src/features/Hackathons/Components/HackathonCard/HackathonCard.tsx +++ b/src/features/Hackathons/Components/HackathonCard/HackathonCard.tsx @@ -14,9 +14,9 @@ interface Props { export default function HackathonCard({ hackathon }: Props) { return ( -
+
-
+

{hackathon.title} @@ -34,6 +34,7 @@ export default function HackathonCard({ hackathon }: Props) {
{hackathon.topics.map(topic =>
{topic.icon} {topic.title}
)}
+
diff --git a/src/features/Hackathons/Components/SortByFilter/SortByFilter.tsx b/src/features/Hackathons/Components/SortByFilter/SortByFilter.tsx index cd238c1..a46ea8d 100644 --- a/src/features/Hackathons/Components/SortByFilter/SortByFilter.tsx +++ b/src/features/Hackathons/Components/SortByFilter/SortByFilter.tsx @@ -56,10 +56,12 @@ export default function SortByFilter({ filterChanged }: Props) { : filterClicked(o ? o.value : null)} /> } diff --git a/src/features/Hackathons/pages/HackathonsPage/HackathonsPage.tsx b/src/features/Hackathons/pages/HackathonsPage/HackathonsPage.tsx index 2cad2e3..c8875d0 100644 --- a/src/features/Hackathons/pages/HackathonsPage/HackathonsPage.tsx +++ b/src/features/Hackathons/pages/HackathonsPage/HackathonsPage.tsx @@ -11,7 +11,7 @@ import { Helmet } from 'react-helmet' export default function HackathonsPage() { - const [sortByFilter, setSortByFilter] = useState(null) + const [sortByFilter, setSortByFilter] = useState('Upcoming') const [topicsFilter, setTopicsFilter] = useState(null) const hackathonsQuery = useGetHackathonsQuery({ diff --git a/src/features/Posts/Components/PostCard/Header/Header.tsx b/src/features/Posts/Components/PostCard/Header/Header.tsx index 47bbc21..7c9149a 100644 --- a/src/features/Posts/Components/PostCard/Header/Header.tsx +++ b/src/features/Posts/Components/PostCard/Header/Header.tsx @@ -1,6 +1,7 @@ import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'; import dayjs from 'dayjs' import { UnionToObjectKeys } from 'src/utils/types/utils'; +import { trimText } from 'src/utils/helperFunctions'; interface Props { author: { @@ -43,8 +44,8 @@ export default function Header({ return (
-
-

{props.author.name}

+
+

{trimText(props.author.name, 30)}

{dateToShow()}

{/* {showTimeAgo &&

diff --git a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx index 85b61c2..8b4d6b7 100644 --- a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx +++ b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import { yupResolver } from "@hookform/resolvers/yup"; import { Controller, FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form"; import Button from "src/Components/Button/Button"; @@ -5,52 +6,117 @@ import FilesInput from "src/Components/Inputs/FilesInput/FilesInput"; import TagsInput from "src/Components/Inputs/TagsInput/TagsInput"; import * as yup from "yup"; import ContentEditor from "../ContentEditor/ContentEditor"; +import { Topic, useCreateStoryMutation } from 'src/graphql' +import { useNavigate } from 'react-router-dom' +import TopicsInput from '../TopicsInput/TopicsInput' +import { useAppDispatch, useAppSelector } from 'src/utils/hooks'; +import { stageStory } from 'src/redux/features/staging.slice' +import { Override } from 'src/utils/interfaces'; +const FileSchema = yup.lazy((value: string | File[]) => { + + switch (typeof value) { + case 'object': + return yup.mixed() + .test("fileSize", "File Size is too large", file => file.size <= 5242880) + .test("fileType", "Unsupported File Format, only png/jpg/jpeg images are allowed", + (file: File) => + ["image/jpeg", "image/png", "image/jpg"].includes(file.type)) + case 'string': + return yup.string().url(); + default: + return yup.mixed() + } +}) const schema = yup.object({ title: yup.string().required().min(10), + topic: yup.object().nullable().required(), tags: yup.array().required().min(1), body: yup.string().required().min(50, 'you have to write at least 10 words'), - cover_image: yup.lazy((value: string | File[]) => { - switch (typeof value) { - case 'object': - return yup.array() - .test("fileSize", "File Size is too large", (files) => (files as File[]).every(file => file.size <= 5242880)) - .test("fileType", "Unsupported File Format, only png/jpg/jpeg images are allowed", - (files) => (files as File[]).every((file: File) => - ["image/jpeg", "image/png", "image/jpg"].includes(file.type))) - case 'string': - return yup.string().url(); - default: - return yup.mixed() - } - }) + cover_image: yup.array().of(FileSchema as any) + }).required(); + interface IFormInputs { + id: number | null title: string - tags: NestedValue - cover_image: NestedValue | string + topic: NestedValue | null + tags: NestedValue<{ title: string }[]> + cover_image: NestedValue | NestedValue body: string } +export type CreateStoryType = Override + export default function StoryForm() { + const dispatch = useAppDispatch(); + const { story } = useAppSelector(state => ({ + story: state.staging.story + })) + const formMethods = useForm({ resolver: yupResolver(schema) as Resolver, defaultValues: { - title: '', - tags: [], - body: '', - cover_image: [] + id: story?.id ?? null, + title: story?.title ?? '', + topic: story?.topic ?? null, + cover_image: story?.cover_image ?? [], + tags: story?.tags ?? [], + body: story?.body ?? '', } }); - const { handleSubmit, control, register, formState: { errors }, } = formMethods; + const { handleSubmit, control, register, formState: { errors, }, trigger, getValues } = formMethods; + const [loading, setLoading] = useState(false) - const onSubmit: SubmitHandler = data => console.log(data); + const navigate = useNavigate() + + const [createStory] = useCreateStoryMutation({ + onCompleted: (data) => { + navigate(`/blog/post/Story/${data.createStory?.id}`) + setLoading(false) + }, + + onError: (error) => { + console.log(error) + alert('Unexpected error happened, please try again') + setLoading(false) + } + }); + + const clickPreview = async () => { + const isValid = await trigger(); + const data = getValues() + console.log(data); + + if (isValid) + dispatch(stageStory(data)) + } + + const onSubmit: SubmitHandler = data => { + setLoading(true); + createStory({ + variables: { + data: { + id: null, + title: data.title, + body: data.body, + tags: data.tags.map(t => t.title), + cover_image: data.cover_image[0] as string, + topicId: 1, + }, + } + }) + } return ( @@ -91,7 +157,25 @@ export default function StoryForm() { {errors.title.message}

} - +

+ Topic +

+
+ ( + + )} + /> +
+ {errors.topic &&

+ {errors.topic.message} +

}

Tags

@@ -113,11 +197,18 @@ export default function StoryForm() {

}
- -
diff --git a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/createStory.graphql b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/createStory.graphql new file mode 100644 index 0000000..b17a62e --- /dev/null +++ b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/createStory.graphql @@ -0,0 +1,5 @@ +mutation createStory($data: StoryInputType) { + createStory(data: $data) { + id + } +} diff --git a/src/features/Posts/pages/CreatePostPage/Components/TopicsInput/TopicsInput.tsx b/src/features/Posts/pages/CreatePostPage/Components/TopicsInput/TopicsInput.tsx new file mode 100644 index 0000000..ba95f7d --- /dev/null +++ b/src/features/Posts/pages/CreatePostPage/Components/TopicsInput/TopicsInput.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import AutoComplete from 'src/Components/Inputs/Autocomplete/Autocomplete' +import { Topic, useAllTopicsQuery } from 'src/graphql' +import { ControlledStateHandler } from 'src/utils/interfaces'; + + + +type Props = ControlledStateHandler + + + +export default function TopicsInput(props: Props) { + + const topicsQuery = useAllTopicsQuery(); + + return ( + + ) +} diff --git a/src/features/Posts/pages/CreatePostPage/PostTypeList.tsx b/src/features/Posts/pages/CreatePostPage/PostTypeList.tsx index 4836f36..cdb3e21 100644 --- a/src/features/Posts/pages/CreatePostPage/PostTypeList.tsx +++ b/src/features/Posts/pages/CreatePostPage/PostTypeList.tsx @@ -1,15 +1,19 @@ +import { spawn } from 'child_process'; import React, { useState } from 'react' const types = [ { text: "📜 Story", - value: 'story' + value: 'story', + disabled: false }, { text: "💰 Bounty", - value: 'bounty' + value: 'bounty', + disabled: true, }, { text: "❓ Question", - value: 'question' + value: 'question', + disabled: true, }, ] as const; @@ -36,10 +40,14 @@ export default function PostTypeList({ selectionChanged }: Props) {
    {types.map((f, idx) =>
  • handleClick(f.value)} + className={` + p-12 rounded-8 cursor-pointer font-bold + ${f.value === selected && 'bg-gray-100'} + ${f.disabled && 'opacity-40'} + `} + onClick={() => !f.disabled && handleClick(f.value)} > - {f.text} + {f.text} {f.disabled && (WIP)}
  • )}
diff --git a/src/features/Posts/pages/FeedPage/FeedPage.tsx b/src/features/Posts/pages/FeedPage/FeedPage.tsx index 8aa6875..d7d76d6 100644 --- a/src/features/Posts/pages/FeedPage/FeedPage.tsx +++ b/src/features/Posts/pages/FeedPage/FeedPage.tsx @@ -10,6 +10,7 @@ import PopularTopicsFilter from './PopularTopicsFilter/PopularTopicsFilter' import SortBy from './SortBy/SortBy' import styles from './styles.module.scss' import { Helmet } from "react-helmet"; +import Button from 'src/Components/Button/Button' export default function FeedPage() { @@ -48,6 +49,14 @@ export default function FeedPage() { top: `${navHeight + 16}px`, maxHeight: `calc(100vh - ${navHeight}px - 16px)`, }}> + +
@@ -55,6 +64,7 @@ export default function FeedPage() { +

diff --git a/src/features/Posts/pages/FeedPage/SortBy/SortBy.tsx b/src/features/Posts/pages/FeedPage/SortBy/SortBy.tsx index 55e7a97..088ad0f 100644 --- a/src/features/Posts/pages/FeedPage/SortBy/SortBy.tsx +++ b/src/features/Posts/pages/FeedPage/SortBy/SortBy.tsx @@ -59,10 +59,12 @@ export default function SortBy({ filterChanged }: Props) { : filterClicked(o ? o.value : null)} /> } diff --git a/src/features/Posts/pages/PostDetailsPage/Components/AuthorCard/AuthorCard.tsx b/src/features/Posts/pages/PostDetailsPage/Components/AuthorCard/AuthorCard.tsx index 74efcdc..11e37ea 100644 --- a/src/features/Posts/pages/PostDetailsPage/Components/AuthorCard/AuthorCard.tsx +++ b/src/features/Posts/pages/PostDetailsPage/Components/AuthorCard/AuthorCard.tsx @@ -2,6 +2,7 @@ import dayjs from "dayjs"; import Button from "src/Components/Button/Button"; import { Author } from "src/features/Posts/types"; import Avatar from "src/features/Profiles/Components/Avatar/Avatar"; +import { trimText } from "src/utils/helperFunctions"; interface Props { author: Author @@ -12,8 +13,8 @@ export default function AuthorCard({ author }: Props) {
-
-

{author.name}

+
+

{trimText(author.name, 333)}

Joined on {dayjs(author.join_date).format('MMMM DD, YYYY')}

diff --git a/src/features/Posts/pages/PostDetailsPage/Components/PageContent/styles.module.css b/src/features/Posts/pages/PostDetailsPage/Components/PageContent/styles.module.css index 8b3abea..3f94af8 100644 --- a/src/features/Posts/pages/PostDetailsPage/Components/PageContent/styles.module.css +++ b/src/features/Posts/pages/PostDetailsPage/Components/PageContent/styles.module.css @@ -1,3 +1,7 @@ +.body :where(h1, h2, h3, h4, h5, h6) { + font-weight: 700; +} + .body h1 { font-size: 48px; line-height: 54px; @@ -30,3 +34,10 @@ line-height: 22px; margin-bottom: 1.5em; } + +.body pre { + background-color: #2b2b2b; + padding: 16px; + border-radius: 12px; + color: whitesmoke; +} diff --git a/src/features/Posts/pages/PostDetailsPage/Components/PostActions/PostActions.tsx b/src/features/Posts/pages/PostDetailsPage/Components/PostActions/PostActions.tsx index 4a316d4..244716f 100644 --- a/src/features/Posts/pages/PostDetailsPage/Components/PostActions/PostActions.tsx +++ b/src/features/Posts/pages/PostDetailsPage/Components/PostActions/PostActions.tsx @@ -36,7 +36,7 @@ export default function PostActions({ post }: Props) { diff --git a/src/features/Posts/pages/PostDetailsPage/PostDetailsPage.tsx b/src/features/Posts/pages/PostDetailsPage/PostDetailsPage.tsx index 4e5948d..423a59d 100644 --- a/src/features/Posts/pages/PostDetailsPage/PostDetailsPage.tsx +++ b/src/features/Posts/pages/PostDetailsPage/PostDetailsPage.tsx @@ -55,7 +55,7 @@ export default function PostDetailsPage() { -