From 0b0e1b5bb293b782b77514cb49f00132d6307f38 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Tue, 12 Jul 2022 13:17:43 +0300 Subject: [PATCH] feat: build a new preview component, change the story form errors structure and display, store current draft in storage Issues #67 #66 --- src/App.tsx | 2 - src/Components/Navbar/NavDesktop.tsx | 2 - .../PreviewPostCard.stories.tsx} | 8 +- .../PreviewPostCard/PreviewPostCard.tsx} | 13 +- .../Components/StoryForm/StoryForm.tsx | 199 +++++++++++------- .../pages/CreatePostPage/CreatePostPage.tsx | 9 +- .../pages/PreviewPostPage/PreviewPostPage.tsx | 74 ------- src/services/index.ts | 8 +- src/services/storage.service.ts | 20 ++ src/utils/hooks/usePreload.ts | 1 - 10 files changed, 157 insertions(+), 179 deletions(-) rename src/features/Posts/pages/{PreviewPostPage/PreviewPostContent/PreviewPostContent.stories.tsx => CreatePostPage/Components/PreviewPostCard/PreviewPostCard.stories.tsx} (55%) rename src/features/Posts/pages/{PreviewPostPage/PreviewPostContent/PreviewPostContent.tsx => CreatePostPage/Components/PreviewPostCard/PreviewPostCard.tsx} (81%) delete mode 100644 src/features/Posts/pages/PreviewPostPage/PreviewPostPage.tsx create mode 100644 src/services/storage.service.ts diff --git a/src/App.tsx b/src/App.tsx index 7e1f764..cc94daf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,7 +18,6 @@ import { Loadable } from "./utils/routing"; const FeedPage = Loadable(React.lazy(() => import("./features/Posts/pages/FeedPage/FeedPage"))) const PostDetailsPage = Loadable(React.lazy(() => import("./features/Posts/pages/PostDetailsPage/PostDetailsPage"))) const CreatePostPage = Loadable(React.lazy(() => import("./features/Posts/pages/CreatePostPage/CreatePostPage"))) -const PreviewPostPage = Loadable(React.lazy(() => import("./features/Posts/pages/PreviewPostPage/PreviewPostPage"))) const HottestPage = Loadable(React.lazy(() => import("src/features/Projects/pages/HottestPage/HottestPage"))) const CategoryPage = Loadable(React.lazy(() => import("src/features/Projects/pages/CategoryPage/CategoryPage"))) @@ -84,7 +83,6 @@ function App() { }> - } /> } /> }> diff --git a/src/Components/Navbar/NavDesktop.tsx b/src/Components/Navbar/NavDesktop.tsx index 87d6342..0825ba3 100644 --- a/src/Components/Navbar/NavDesktop.tsx +++ b/src/Components/Navbar/NavDesktop.tsx @@ -21,8 +21,6 @@ import { createRoute } from "src/utils/routing"; export default function NavDesktop() { const [searchOpen, setSearchOpen] = useState(false) - const communityRef = useRef(null); - const [communitymenuProps, toggleCommunityMenu] = useMenuState({ transition: true }); diff --git a/src/features/Posts/pages/PreviewPostPage/PreviewPostContent/PreviewPostContent.stories.tsx b/src/features/Posts/pages/CreatePostPage/Components/PreviewPostCard/PreviewPostCard.stories.tsx similarity index 55% rename from src/features/Posts/pages/PreviewPostPage/PreviewPostContent/PreviewPostContent.stories.tsx rename to src/features/Posts/pages/CreatePostPage/Components/PreviewPostCard/PreviewPostCard.stories.tsx index c1c1f29..85ce3a1 100644 --- a/src/features/Posts/pages/PreviewPostPage/PreviewPostContent/PreviewPostContent.stories.tsx +++ b/src/features/Posts/pages/CreatePostPage/Components/PreviewPostCard/PreviewPostCard.stories.tsx @@ -1,18 +1,18 @@ import { ComponentStory, ComponentMeta } from '@storybook/react'; import { MOCK_DATA } from 'src/mocks/data'; -import PreviewPostContent from './PreviewPostContent'; +import PreviewPostCard from './PreviewPostCard'; export default { title: 'Posts/Post Details Page/Components/Story Page Content', - component: PreviewPostContent, + component: PreviewPostCard, argTypes: { backgroundColor: { control: 'color' }, }, -} as ComponentMeta; +} as ComponentMeta; -const Template: ComponentStory = (args) =>
+const Template: ComponentStory = (args) =>
export const Default = Template.bind({}); Default.args = { diff --git a/src/features/Posts/pages/PreviewPostPage/PreviewPostContent/PreviewPostContent.tsx b/src/features/Posts/pages/CreatePostPage/Components/PreviewPostCard/PreviewPostCard.tsx similarity index 81% rename from src/features/Posts/pages/PreviewPostPage/PreviewPostContent/PreviewPostContent.tsx rename to src/features/Posts/pages/CreatePostPage/Components/PreviewPostCard/PreviewPostCard.tsx index c6d9ebf..c6a1337 100644 --- a/src/features/Posts/pages/PreviewPostPage/PreviewPostContent/PreviewPostContent.tsx +++ b/src/features/Posts/pages/CreatePostPage/Components/PreviewPostCard/PreviewPostCard.tsx @@ -1,23 +1,25 @@ -import Header from "src/features/Posts/Components/PostCard/Header/Header" import { marked } from 'marked'; -import styles from '../../PostDetailsPage/Components/PageContent/styles.module.scss' +import styles from 'src/features/Posts/pages/PostDetailsPage/Components/PageContent/styles.module.scss' import Badge from "src/Components/Badge/Badge"; import { Post } from "src/graphql"; +function isPost(type?: string): type is 'story' { + return type === 'story' + // || type === 'question' || type === 'bounty' +} + interface Props { post: Pick & { tags: Array<{ title: string }> cover_image?: string | File | null } } -export default function PreviewPostContent({ post }: Props) { +export default function PreviewPostContent({ post, }: Props) { let coverImg: string; if (!post.cover_image) @@ -36,7 +38,6 @@ export default function PreviewPostContent({ post }: Props) { className='w-full h-[120px] md:h-[240px] object-cover rounded-12 mb-16' alt="" />}
-

{post.title}

{post.tags.length > 0 &&
{post.tags.map((tag, idx) => diff --git a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx index 3eb4519..77a629a 100644 --- a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx +++ b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx @@ -1,6 +1,6 @@ -import { useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { yupResolver } from "@hookform/resolvers/yup"; -import { Controller, FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form"; +import { Controller, FormProvider, NestedValue, Resolver, useForm } from "react-hook-form"; import Button from "src/Components/Button/Button"; import FilesInput from "src/Components/Inputs/FilesInput/FilesInput"; import TagsInput from "src/Components/Inputs/TagsInput/TagsInput"; @@ -13,6 +13,9 @@ import { stageStory } from 'src/redux/features/staging.slice' import { Override } from 'src/utils/interfaces'; import { NotificationsService } from "src/services/notifications.service"; import { createRoute } from 'src/utils/routing'; +import PreviewPostCard from '../PreviewPostCard/PreviewPostCard' +import { StorageService } from 'src/services'; +import { useThrottledCallback } from '@react-hookz/web'; const FileSchema = yup.lazy((value: string | File[]) => { @@ -54,16 +57,25 @@ export type CreateStoryType = Override +const storageService = new StorageService('story-edit'); + export default function StoryForm() { const dispatch = useAppDispatch(); const { story } = useAppSelector(state => ({ - story: state.staging.story + story: state.staging.story || storageService.get() })) + + const [editMode, setEditMode] = useState(true) + const navigate = useNavigate(); + const errorsContainerRef = useRef(null!); + + const formMethods = useForm({ resolver: yupResolver(schema) as Resolver, + shouldFocusError: false, defaultValues: { id: story?.id ?? null, title: story?.title ?? '', @@ -72,11 +84,19 @@ export default function StoryForm() { body: story?.body ?? '', }, }); - const { handleSubmit, control, register, formState: { errors, }, trigger, getValues, } = formMethods; - const [loading, setLoading] = useState(false) + const { handleSubmit, control, register, formState: { errors, isValid, isSubmitted }, trigger, getValues, watch } = formMethods; - const navigate = useNavigate() + const presistPost = useThrottledCallback((value) => storageService.set(value), [], 1000) + useEffect(() => { + const subscription = watch((value) => presistPost(value)); + return () => subscription.unsubscribe(); + }, [presistPost, watch]); + + + + + const [loading, setLoading] = useState(false); const [createStory] = useCreateStoryMutation({ onCompleted: (data) => { navigate(createRoute({ type: 'story', id: data.createStory?.id!, title: data.createStory?.title })) @@ -89,13 +109,15 @@ export default function StoryForm() { } }); + const clickPreview = async () => { const isValid = await trigger(); if (isValid) { const data = getValues() dispatch(stageStory(data)) - navigate('/blog/preview-post/Story') + storageService.set(data) + setEditMode(false); } else { clickSubmit(); // I'm doing this so that the react-hook-form attaches onChange listener to inputs validation } @@ -114,86 +136,107 @@ export default function StoryForm() { }, } }) - }) + }, () => errorsContainerRef.current.scrollIntoView({ behavior: 'smooth', block: "center" })) const isUpdating = story?.id; + return ( -
-
-
- ( - - )} - /> -

{errors.cover_image?.message}

- - - -
- -
- {errors.title &&

- {errors.title.message} -

} - - - {errors.tags &&

- {errors.tags.message} -

} +
+ +
+ +
- + {editMode && <> +
+
+ ( + + )} + /> - {errors.body &&

- {errors.body.message} -

} + + +
+ +
+ + + +
+ + +
+ + } + {!editMode && } +
+ + {/* */} +
+ +
+
+ {(!isValid && isSubmitted) &&
    + {errors.title &&
  • + {errors.title.message} +
  • } + {errors.cover_image &&
  • + {errors.cover_image.message} +
  • } + {errors.tags &&
  • + {errors.tags.message} +
  • } + {errors.body &&
  • + {errors.body.message} +
  • } +
} +
-
- - -
- +
) } diff --git a/src/features/Posts/pages/CreatePostPage/CreatePostPage.tsx b/src/features/Posts/pages/CreatePostPage/CreatePostPage.tsx index bbe3c90..48c7e65 100644 --- a/src/features/Posts/pages/CreatePostPage/CreatePostPage.tsx +++ b/src/features/Posts/pages/CreatePostPage/CreatePostPage.tsx @@ -2,11 +2,9 @@ import { useState } from "react"; import { Helmet } from "react-helmet"; import { FiArrowLeft } from "react-icons/fi"; import { useNavigate, useParams } from "react-router-dom"; -import { usePreload } from "src/utils/hooks"; import BountyForm from "./Components/BountyForm/BountyForm"; import QuestionForm from "./Components/QuestionForm/QuestionForm"; import StoryForm from "./Components/StoryForm/StoryForm"; -import PostTypeList from "./PostTypeList"; interface Props { @@ -18,7 +16,6 @@ export default function CreatePostPage() { const [postType, setPostType] = useState<'story' | 'bounty' | 'question'>((type as any) ?? 'story'); - usePreload('PreviewPostPage'); const navigate = useNavigate(); @@ -29,7 +26,7 @@ export default function CreatePostPage() { {postType === 'question' && Create Question}
@@ -42,9 +39,7 @@ export default function CreatePostPage() {
-
+
{postType === 'story' && <> {/*

Write a Story diff --git a/src/features/Posts/pages/PreviewPostPage/PreviewPostPage.tsx b/src/features/Posts/pages/PreviewPostPage/PreviewPostPage.tsx deleted file mode 100644 index 9a9897b..0000000 --- a/src/features/Posts/pages/PreviewPostPage/PreviewPostPage.tsx +++ /dev/null @@ -1,74 +0,0 @@ - -import { Helmet } from 'react-helmet' -import { useParams } from 'react-router-dom' -import NotFoundPage from 'src/features/Shared/pages/NotFoundPage/NotFoundPage' -import { useAppSelector, } from 'src/utils/hooks' -import TrendingCard from '../../Components/TrendingCard/TrendingCard' -import AuthorCard from '../PostDetailsPage/Components/AuthorCard/AuthorCard' -import PostActions from '../PostDetailsPage/Components/PostActions/PostActions' -import styles from '../PostDetailsPage/styles.module.scss'; -import PreviewPostContent from './PreviewPostContent/PreviewPostContent' - -function isPost(type?: string): type is 'story' { - return type === 'story' - // || type === 'question' || type === 'bounty' -} - - -export default function PreviewPostPage() { - - const { type: _type } = useParams() - - const type = _type?.toLowerCase(); - - const { post, author, navHeight } = useAppSelector(state => ({ - post: isPost(type) ? state.staging[type] : null, - author: state.user.me, - navHeight: state.ui.navHeight - })) - - - - if (!post) - return - - return ( - <> - - {post.title} - - -
- - - -
- - ) -} diff --git a/src/services/index.ts b/src/services/index.ts index 8a6ce53..5bd6a97 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,5 +1,3 @@ -import Wallet_Service from './wallet.service' - -export { - Wallet_Service -} \ No newline at end of file +export { default as Wallet_Service } from './wallet.service' +export * from './storage.service' +export * from './notifications.service' diff --git a/src/services/storage.service.ts b/src/services/storage.service.ts new file mode 100644 index 0000000..071d6f2 --- /dev/null +++ b/src/services/storage.service.ts @@ -0,0 +1,20 @@ + +export class StorageService { + key: string; + + constructor(key: string) { + this.key = key; + } + + set(newValue: T) { + localStorage.setItem(this.key, JSON.stringify(newValue)); + } + + get() { + const str = localStorage.getItem(this.key); + if (!str) + return null; + + return JSON.parse(str) as T; + } +} diff --git a/src/utils/hooks/usePreload.ts b/src/utils/hooks/usePreload.ts index 121fc2e..681a764 100644 --- a/src/utils/hooks/usePreload.ts +++ b/src/utils/hooks/usePreload.ts @@ -2,7 +2,6 @@ import { useEffect } from 'react'; const Components = { PostPage: () => import('../../features/Posts/pages/PostDetailsPage/PostDetailsPage'), - PreviewPostPage: () => import("../../features/Posts/pages/PreviewPostPage/PreviewPostPage") } type ComponentToLoad = keyof typeof Components;