From 8b6ec6394bf3b85e352ed625f2f5d39e5afd521c Mon Sep 17 00:00:00 2001 From: carrick Date: Tue, 13 Feb 2024 15:17:22 +0900 Subject: [PATCH 01/10] feat: add turnstile --- public/index.html | 3 +- src/components/write/PublishActionButtons.tsx | 10 ++- .../write/PublishActionButtonsContainer.tsx | 7 ++ src/containers/write/PublishCaptcha.tsx | 47 ++++++++++++ .../write/PublishCaptchaContainer.tsx | 15 ++++ src/containers/write/PublishSettings.tsx | 4 +- src/index.tsx | 11 +++ src/lib/graphql/user.ts | 3 + src/lib/hooks/useTurnstile.tsx | 74 +++++++++++++++++++ 9 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 src/containers/write/PublishCaptcha.tsx create mode 100644 src/containers/write/PublishCaptchaContainer.tsx create mode 100644 src/lib/hooks/useTurnstile.tsx diff --git a/public/index.html b/public/index.html index 9530b7b3..ccd9abc7 100644 --- a/public/index.html +++ b/public/index.html @@ -35,9 +35,10 @@ window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); - gtag('config', 'G-8D0MD2S4PK'); + + React App diff --git a/src/components/write/PublishActionButtons.tsx b/src/components/write/PublishActionButtons.tsx index 0301178f..40d8d4a6 100644 --- a/src/components/write/PublishActionButtons.tsx +++ b/src/components/write/PublishActionButtons.tsx @@ -6,6 +6,7 @@ import media from '../../lib/styles/media'; const PublishActionButtonsBlock = styled.div` display: flex; justify-content: flex-end; + margin-top: 0.5rem; ${media.custom(767)} { margin-top: 2rem; } @@ -15,12 +16,14 @@ export interface PublishActionButtonsProps { onCancel: () => void; onPublish: () => void; edit: boolean; + isLoading: boolean; } const PublishActionButtons: React.FC = ({ onCancel, onPublish, edit, + isLoading, }) => { return ( @@ -32,7 +35,12 @@ const PublishActionButtons: React.FC = ({ > 취소 - diff --git a/src/containers/write/PublishActionButtonsContainer.tsx b/src/containers/write/PublishActionButtonsContainer.tsx index 4b59cfaa..ee159670 100644 --- a/src/containers/write/PublishActionButtonsContainer.tsx +++ b/src/containers/write/PublishActionButtonsContainer.tsx @@ -17,6 +17,7 @@ import { setHeadingId } from '../../lib/heading'; import { useHistory } from 'react-router'; import { toast } from 'react-toastify'; import { useUncachedApolloClient } from '../../lib/graphql/UncachedApolloContext'; +import useTurnstile from '../../lib/hooks/useTurnstile'; type PublishActionButtonsContainerProps = {}; @@ -25,6 +26,10 @@ const PublishActionButtonsContainer: React.FC< > = () => { const history = useHistory(); const client = useApolloClient(); + const user = useSelector((state: RootState) => state.core.user); + + const isTurnstileEnabled = !!user && !user.is_trusted; + const { isLoading, token } = useTurnstile(isTurnstileEnabled); const options = useSelector((state: RootState) => pick( @@ -77,6 +82,7 @@ const PublishActionButtonsContainer: React.FC< short_description: options.description, }, series_id: safe(() => options.selectedSeries!.id), + token, }; const onPublish = async () => { @@ -115,6 +121,7 @@ const PublishActionButtonsContainer: React.FC< onCancel={onCancel} onPublish={options.postId ? onEdit : onPublish} edit={!!options.postId && !options.isTemp} + isLoading={isLoading} /> ); }; diff --git a/src/containers/write/PublishCaptcha.tsx b/src/containers/write/PublishCaptcha.tsx new file mode 100644 index 00000000..fee6d8c1 --- /dev/null +++ b/src/containers/write/PublishCaptcha.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import styled from 'styled-components'; +import Spinner from '../../components/common/SpinnerBlock'; +import useTurnstile from '../../lib/hooks/useTurnstile'; + +const PublishCapchaBlock = styled.section` + margin-top: 1.5rem; + + & > #cf-turnstile { + iframe { + width: 100% !important; + } + } +`; + +const SpinnerBlock = styled(PublishCapchaBlock)` + display: flex; + align-items: center; + justify-content: center; + + & > div { + width: 50px; + height: 50px; + } +`; + +interface PublishCaptchaProps {} + +const PublishCaptcha: React.FC = () => { + const { isReady } = useTurnstile(); + + if (!isReady) { + return ( + + + + ); + } + + return ( + +
+
+ ); +}; + +export default PublishCaptcha; diff --git a/src/containers/write/PublishCaptchaContainer.tsx b/src/containers/write/PublishCaptchaContainer.tsx new file mode 100644 index 00000000..99175ca8 --- /dev/null +++ b/src/containers/write/PublishCaptchaContainer.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../modules'; +import PublishCaptcha from './PublishCaptcha'; + +export interface PublishCaptchaContainerProps {} + +const PublishCaptchaContainer: React.FC = () => { + const user = useSelector((state: RootState) => state.core.user); + + if (!user || user.is_trusted) return null; + return ; +}; + +export default PublishCaptchaContainer; diff --git a/src/containers/write/PublishSettings.tsx b/src/containers/write/PublishSettings.tsx index a6748d8d..33ad94c1 100644 --- a/src/containers/write/PublishSettings.tsx +++ b/src/containers/write/PublishSettings.tsx @@ -3,16 +3,18 @@ import PublishPrivacySettingContainer from './PublishPrivacySettingContainer'; import PublishURLSettingContainer from './PublishURLSettingContainer'; import PublishSeriesSectionContainer from './PublishSeriesSectionContainer'; import PublishActionButtonsContainer from './PublishActionButtonsContainer'; +import PublishCaptchaContainer from './PublishCaptchaContainer'; export interface PublishSettingsProps {} -const PublishSettings: React.FC = props => { +const PublishSettings: React.FC = (props) => { return ( <>
+
diff --git a/src/index.tsx b/src/index.tsx index 9a65dfaf..9cd65f95 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,6 +17,7 @@ import * as Sentry from '@sentry/browser'; import { HelmetProvider } from 'react-helmet-async'; import darkMode from './modules/darkMode'; import { UncachedApolloProvider } from './lib/graphql/UncachedApolloContext'; +import { ssrEnabled } from './lib/utils'; Sentry.init({ dsn: 'https://99d0ac3ca0f64b4d8709e385e7692893@sentry.io/1886813', @@ -45,8 +46,18 @@ const loadTheme = () => { document.body.dataset.theme = theme; }; +const loadTurnstile = () => { + if (ssrEnabled) return; + (window as any).onAppReady = function () { + (window as any).isTurnstileReady = true; + const event = new CustomEvent('isTurnstileReadyChange'); + window.dispatchEvent(event); + }; +}; + loadUser(); loadTheme(); +loadTurnstile(); if (process.env.NODE_ENV === 'production') { loadableReady(() => { diff --git a/src/lib/graphql/user.ts b/src/lib/graphql/user.ts index e11aad84..ede52c0b 100644 --- a/src/lib/graphql/user.ts +++ b/src/lib/graphql/user.ts @@ -29,6 +29,7 @@ export type User = { profile: UserProfile; velogConfig: VelogConfig | null; is_followed: boolean; + is_trusted: boolean; }; export const GET_CURRENT_USER = gql` @@ -37,6 +38,7 @@ export const GET_CURRENT_USER = gql` id username email + is_trusted profile { id thumbnail @@ -64,6 +66,7 @@ export type CurrentUser = { display_name: string; }; email: string; + is_trusted: boolean; }; export const GET_USER_PROFILE = gql` diff --git a/src/lib/hooks/useTurnstile.tsx b/src/lib/hooks/useTurnstile.tsx new file mode 100644 index 00000000..213a8ac3 --- /dev/null +++ b/src/lib/hooks/useTurnstile.tsx @@ -0,0 +1,74 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../modules'; +import { sleep } from '../utils'; + +const SITE_KEY = '0x4AAAAAAARRng9sFKpsJomI'; + +const useTurnstile = (isEnabled = true) => { + const theme = useSelector((state: RootState) => state.darkMode.theme); + const isTurnstileReady = !!(window as any).isTurnstileReady; + const [isLoading, setIsLoading] = useState(false); + const [isReady, setIsReady] = useState(isTurnstileReady); + const [isError, setError] = useState(false); + const [token, setToken] = useState(null); + const retryCountRef = useRef(0); + + const waitTurnstileContainer = async () => { + const container = document.getElementById('cf-turnstile'); + while (!container) { + if (container) return true; + await sleep(500); + } + return true; + }; + + const checkBot = useCallback(async () => { + setIsLoading(true); + const turnstile = (window as any).turnstile; + await waitTurnstileContainer(); + turnstile.render('#cf-turnstile', { + sitekey: SITE_KEY, + theme: theme === 'dark' ? 'dark' : 'light', + callback: (token: string) => { + setIsLoading(false); + setToken(token); + }, + 'error-callback': () => { + retryCountRef.current += 1; + setError(true); + if (retryCountRef.current < 5) { + setTimeout(() => { + checkBot(); + }, 1000); + } + }, + }); + }, [theme]); + + useEffect(() => { + const checkTurnstileReady = () => { + const isTurnstileReady = (window as any).isTurnstileReady; + setIsReady(isTurnstileReady); + }; + window.addEventListener('isTurnstileReadyChange', checkTurnstileReady); + return () => { + window.removeEventListener('isTurnstileReadyChange', checkTurnstileReady); + }; + }, []); + + useEffect(() => { + if (!isEnabled) return; + checkBot(); + }, [checkBot, isEnabled]); + + return { + isError, + isReady, + isLoading, + token, + isEnabled, + }; +}; + +export default useTurnstile; From f34ec20d39334759183a54aabd6cda33383ab268 Mon Sep 17 00:00:00 2001 From: carrick Date: Tue, 13 Feb 2024 16:58:28 +0900 Subject: [PATCH 02/10] fix: write/edit post query --- src/containers/write/MarkdownEditorContainer.tsx | 3 +++ src/containers/write/PublishPreviewContainer.tsx | 1 + src/lib/graphql/post.ts | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/containers/write/MarkdownEditorContainer.tsx b/src/containers/write/MarkdownEditorContainer.tsx index bae00814..c0ddc531 100644 --- a/src/containers/write/MarkdownEditorContainer.tsx +++ b/src/containers/write/MarkdownEditorContainer.tsx @@ -171,6 +171,7 @@ const MarkdownEditorContainer: React.FC = () => { thumbnail: null, meta: {}, series_id: null, + token: null, }, }); if (!response || !response.data) return; @@ -194,6 +195,7 @@ const MarkdownEditorContainer: React.FC = () => { meta: {}, series_id: null, tags, + token: null, }, }); notifySuccess(); @@ -259,6 +261,7 @@ const MarkdownEditorContainer: React.FC = () => { thumbnail: null, meta: {}, series_id: null, + token: null, }, }); if (!response || !response.data) return; diff --git a/src/containers/write/PublishPreviewContainer.tsx b/src/containers/write/PublishPreviewContainer.tsx index 5c3f976e..19367112 100644 --- a/src/containers/write/PublishPreviewContainer.tsx +++ b/src/containers/write/PublishPreviewContainer.tsx @@ -91,6 +91,7 @@ const PublishPreviewContainer: React.FC = ({ thumbnail: null, meta: {}, series_id: null, + token: null, }, }); diff --git a/src/lib/graphql/post.ts b/src/lib/graphql/post.ts index 1d1b1909..49a8478f 100644 --- a/src/lib/graphql/post.ts +++ b/src/lib/graphql/post.ts @@ -528,6 +528,7 @@ export const WRITE_POST = gql` $thumbnail: String $meta: JSON $series_id: ID + $token: String ) { writePost( title: $title @@ -540,6 +541,7 @@ export const WRITE_POST = gql` thumbnail: $thumbnail meta: $meta series_id: $series_id + token: $token ) { id user { @@ -575,6 +577,7 @@ export const EDIT_POST = gql` $thumbnail: String $meta: JSON $series_id: ID + $token: String ) { editPost( id: $id @@ -588,6 +591,7 @@ export const EDIT_POST = gql` thumbnail: $thumbnail meta: $meta series_id: $series_id + token: $token ) { id title From 20f23d22fdd718e8d6f5822754f77b44787bca49 Mon Sep 17 00:00:00 2001 From: carrick Date: Tue, 20 Feb 2024 17:09:12 +0900 Subject: [PATCH 03/10] fix: editPost query result --- src/lib/graphql/post.ts | 66 ++++++----------------------------------- 1 file changed, 9 insertions(+), 57 deletions(-) diff --git a/src/lib/graphql/post.ts b/src/lib/graphql/post.ts index 49a8478f..25b36d78 100644 --- a/src/lib/graphql/post.ts +++ b/src/lib/graphql/post.ts @@ -594,72 +594,24 @@ export const EDIT_POST = gql` token: $token ) { id - title - released_at - updated_at - tags - body - short_description - is_markdown - is_private - is_temp - thumbnail - comments_count - url_slug user { id username - profile { - id - display_name - thumbnail - short_bio - } - velog_config { - title - } - } - comments { - id - user { - id - username - profile { - id - thumbnail - display_name - } - } - text - replies_count - level - created_at - level - deleted - } - series { - id - name - url_slug - series_posts { - id - post { - id - title - url_slug - user { - id - username - } - } - } } + url_slug } } `; export type EditPostResult = { - editPost: SinglePost; + editPost: { + id: string; + user: { + id: string; + username: string; + }; + url_slug: string; + }; }; export const WRITE_COMMENT = gql` From 99775b8cb779593e762b48a1aaac2e14f16e1181 Mon Sep 17 00:00:00 2001 From: carrick Date: Wed, 21 Feb 2024 17:07:47 +0900 Subject: [PATCH 04/10] fix: prevent duplicate submissions for write post --- .../write/MarkdownEditorContainer.tsx | 22 +++++++++++++------ .../write/PublishActionButtonsContainer.tsx | 18 ++++++++++----- .../write/PublishPreviewContainer.tsx | 11 ++++++---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/containers/write/MarkdownEditorContainer.tsx b/src/containers/write/MarkdownEditorContainer.tsx index c0ddc531..e2b8201e 100644 --- a/src/containers/write/MarkdownEditorContainer.tsx +++ b/src/containers/write/MarkdownEditorContainer.tsx @@ -61,17 +61,21 @@ const MarkdownEditorContainer: React.FC = () => { tags, } = useSelector((state: RootState) => state.write); const uncachedClient = useUncachedApolloClient(); - const [writePost] = useMutation(WRITE_POST, { - client: uncachedClient, - }); + const [writePost, { loading: writePostLoading }] = + useMutation(WRITE_POST, { + client: uncachedClient, + }); const bodyRef = useRef(initialBody); const titleRef = useRef(title); const [createPostHistory] = useMutation(CREATE_POST_HISTORY); - const [editPost] = useMutation(EDIT_POST, { - client: uncachedClient, - }); + const [editPost, { loading: editPostLoading }] = useMutation( + EDIT_POST, + { + client: uncachedClient, + }, + ); const [lastSavedData, setLastSavedData] = useState({ title: initialTitle, @@ -148,6 +152,7 @@ const MarkdownEditorContainer: React.FC = () => { const onTempSave = useCallback( async (notify?: boolean) => { + if (writePostLoading || editPostLoading) return; if (!title || !markdown) { toast.error('제목 또는 내용이 비어있습니다.'); return; @@ -233,6 +238,8 @@ const MarkdownEditorContainer: React.FC = () => { tags, title, writePost, + writePostLoading, + editPostLoading, ], ); @@ -244,6 +251,7 @@ const MarkdownEditorContainer: React.FC = () => { const uploadWithPostId = useCallback( async (file: File) => { if (!file) return; + if (writePostLoading || editPostLoading) return; let id = postIdRef.current; if (!id) { const title = titleRef.current || 'Temp Title'; @@ -278,7 +286,7 @@ const MarkdownEditorContainer: React.FC = () => { toast.error('이미지 업로드 실패! 잠시 후 다시 시도하세요.'); }); }, - [cfUpload, writePost, dispatch, history], + [cfUpload, writePost, dispatch, history, writePostLoading, editPostLoading], ); const onDragDropUpload = useCallback( diff --git a/src/containers/write/PublishActionButtonsContainer.tsx b/src/containers/write/PublishActionButtonsContainer.tsx index ee159670..403fed68 100644 --- a/src/containers/write/PublishActionButtonsContainer.tsx +++ b/src/containers/write/PublishActionButtonsContainer.tsx @@ -59,12 +59,16 @@ const PublishActionButtonsContainer: React.FC< const uncachedClient = useUncachedApolloClient(); - const [writePost] = useMutation(WRITE_POST, { - client: uncachedClient, - }); - const [editPost] = useMutation(EDIT_POST, { - client: uncachedClient, - }); + const [writePost, { loading: writePostLoading }] = + useMutation(WRITE_POST, { + client: uncachedClient, + }); + const [editPost, { loading: editPostLoading }] = useMutation( + EDIT_POST, + { + client: uncachedClient, + }, + ); const variables = { title: options.title, @@ -86,6 +90,7 @@ const PublishActionButtonsContainer: React.FC< }; const onPublish = async () => { + if (writePostLoading || editPostLoading) return; if (options.title.trim() === '') { toast.error('제목이 비어있습니다.'); return; @@ -104,6 +109,7 @@ const PublishActionButtonsContainer: React.FC< }; const onEdit = async () => { + if (editPostLoading) return; const response = await editPost({ variables: { id: options.postId, diff --git a/src/containers/write/PublishPreviewContainer.tsx b/src/containers/write/PublishPreviewContainer.tsx index 19367112..86452b2d 100644 --- a/src/containers/write/PublishPreviewContainer.tsx +++ b/src/containers/write/PublishPreviewContainer.tsx @@ -47,9 +47,10 @@ const PublishPreviewContainer: React.FC = ({ [changeDescription], ); const uncachedClient = useUncachedApolloClient(); - const [writePost] = useMutation(WRITE_POST, { - client: uncachedClient, - }); + const [writePost, { loading: writePostLoading }] = + useMutation(WRITE_POST, { + client: uncachedClient, + }); const [upload, file] = useUpload(); const { upload: cfUpload, image } = useCFUpload(); @@ -103,6 +104,7 @@ const PublishPreviewContainer: React.FC = ({ const uploadWithPostId = useCallback( async (file: File) => { + if (!file) return; const id = await getValidPostId(); if (!id) return; cfUpload(file, { type: 'post', refId: id }); @@ -112,9 +114,10 @@ const PublishPreviewContainer: React.FC = ({ useEffect(() => { if (!file) return; + if (writePostLoading) return; uploadWithPostId(file); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [file]); + }, [file, writePostLoading]); useEffect(() => { if (!image) return; From 5a6a1337efd6f75947633630b8ad2a4412f987c8 Mon Sep 17 00:00:00 2001 From: carrick Date: Wed, 21 Feb 2024 21:43:36 +0900 Subject: [PATCH 05/10] fix: createPostHistory bug --- .../write/MarkdownEditorContainer.tsx | 21 ++++++++----- .../write/PublishActionButtonsContainer.tsx | 31 ++++++++++++------- src/index.tsx | 8 +++-- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/containers/write/MarkdownEditorContainer.tsx b/src/containers/write/MarkdownEditorContainer.tsx index e2b8201e..064551d7 100644 --- a/src/containers/write/MarkdownEditorContainer.tsx +++ b/src/containers/write/MarkdownEditorContainer.tsx @@ -179,12 +179,14 @@ const MarkdownEditorContainer: React.FC = () => { token: null, }, }); + if (!response || !response.data) return; const { id } = response.data.writePost; dispatch(setWritePostId(id)); history.replace(`/write?id=${id}`); notifySuccess(); } + // tempsaving unreleased post: if (isTemp) { await editPost({ @@ -212,14 +214,16 @@ const MarkdownEditorContainer: React.FC = () => { if (shallowEqual(lastSavedData, { title, body: markdown })) { return; } - await createPostHistory({ - variables: { - post_id: postId, - title, - body: markdown, - is_markdown: true, - }, - }); + if (postId) { + await createPostHistory({ + variables: { + post_id: postId, + title, + body: markdown, + is_markdown: true, + }, + }); + } setLastSavedData({ title, body: markdown, @@ -272,6 +276,7 @@ const MarkdownEditorContainer: React.FC = () => { token: null, }, }); + if (!response || !response.data) return; id = response.data.writePost.id; dispatch(setWritePostId(id)); diff --git a/src/containers/write/PublishActionButtonsContainer.tsx b/src/containers/write/PublishActionButtonsContainer.tsx index 403fed68..48132a35 100644 --- a/src/containers/write/PublishActionButtonsContainer.tsx +++ b/src/containers/write/PublishActionButtonsContainer.tsx @@ -90,7 +90,7 @@ const PublishActionButtonsContainer: React.FC< }; const onPublish = async () => { - if (writePostLoading || editPostLoading) return; + if (writePostLoading) return; if (options.title.trim() === '') { toast.error('제목이 비어있습니다.'); return; @@ -99,27 +99,34 @@ const PublishActionButtonsContainer: React.FC< const response = await writePost({ variables: variables, }); + if (!response || !response.data) return; const { user, url_slug } = response.data.writePost; await client.resetStore(); history.push(`/@${user.username}/${url_slug}`); - } catch (e) { + } catch (error) { + console.log('writePost error', error); toast.error('포스트 작성 실패'); } }; const onEdit = async () => { if (editPostLoading) return; - const response = await editPost({ - variables: { - id: options.postId, - ...variables, - }, - }); - if (!response || !response.data) return; - const { user, url_slug } = response.data.editPost; - await client.resetStore(); - history.push(`/@${user.username}/${url_slug}`); + try { + const response = await editPost({ + variables: { + id: options.postId, + ...variables, + }, + }); + if (!response || !response.data) return; + const { user, url_slug } = response.data.editPost; + await client.resetStore(); + history.push(`/@${user.username}/${url_slug}`); + } catch (error) { + console.log('editPost error', error); + toast.error('포스트 수정 실패'); + } }; return ( diff --git a/src/index.tsx b/src/index.tsx index 9cd65f95..f505febe 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -19,9 +19,11 @@ import darkMode from './modules/darkMode'; import { UncachedApolloProvider } from './lib/graphql/UncachedApolloContext'; import { ssrEnabled } from './lib/utils'; -Sentry.init({ - dsn: 'https://99d0ac3ca0f64b4d8709e385e7692893@sentry.io/1886813', -}); +if (process.env.NODE_ENV === 'production') { + Sentry.init({ + dsn: 'https://99d0ac3ca0f64b4d8709e385e7692893@sentry.io/1886813', + }); +} const store = createStore( rootReducer, From 56f67c8ae6041f387adb77241e841893d8faaf51 Mon Sep 17 00:00:00 2001 From: carrick Date: Thu, 22 Feb 2024 13:37:16 +0900 Subject: [PATCH 06/10] add cleartimout --- src/components/write/PublishSeriesCreate.tsx | 24 ++++++++++++++++--- .../write/MarkdownEditorContainer.tsx | 4 ++-- .../write/PublishActionButtonsContainer.tsx | 15 ++++++++---- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/components/write/PublishSeriesCreate.tsx b/src/components/write/PublishSeriesCreate.tsx index 3f65ff79..516edfbe 100644 --- a/src/components/write/PublishSeriesCreate.tsx +++ b/src/components/write/PublishSeriesCreate.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, FormEvent } from 'react'; +import React, { useState, useEffect, FormEvent, useRef } from 'react'; import styled, { css, keyframes } from 'styled-components'; import { themedPalette } from '../../lib/styles/themes'; import OutsideClickHandler from 'react-outside-click-handler'; @@ -110,8 +110,8 @@ const PublishSeriesCreate: React.FC = ({ urlSlug: '', }); const [editing, setEditing] = useState(false); - const [defaultUrlSlug, setDefaultUrlSlug] = useState(''); + const hideTimeoutId = useRef(null); useEffect(() => { let timeoutId: ReturnType | null = null; @@ -137,15 +137,33 @@ const PublishSeriesCreate: React.FC = ({ setEditing(true); }, [form.urlSlug]); + useEffect(() => { + return () => { + if (hideTimeoutId.current) { + clearTimeout(hideTimeoutId.current); + } + }; + }, [hideTimeoutId]); + const onHide = () => { setDisappear(true); - setTimeout(() => { + const timeout = setTimeout(() => { setOpen(false); setDisappear(false); setShowOpenBlock(false); }, 125); + const timeoutId = timeout; + hideTimeoutId.current = timeoutId; }; + useEffect(() => { + return () => { + if (hideTimeoutId.current) { + clearTimeout(hideTimeoutId.current); + } + }; + }, []); + const submit = (e: FormEvent) => { e.preventDefault(); if (form.name.trim() === '') { diff --git a/src/containers/write/MarkdownEditorContainer.tsx b/src/containers/write/MarkdownEditorContainer.tsx index 064551d7..9365d1f1 100644 --- a/src/containers/write/MarkdownEditorContainer.tsx +++ b/src/containers/write/MarkdownEditorContainer.tsx @@ -180,7 +180,7 @@ const MarkdownEditorContainer: React.FC = () => { }, }); - if (!response || !response.data) return; + if (!response.data?.writePost) return; const { id } = response.data.writePost; dispatch(setWritePostId(id)); history.replace(`/write?id=${id}`); @@ -277,7 +277,7 @@ const MarkdownEditorContainer: React.FC = () => { }, }); - if (!response || !response.data) return; + if (!response.data?.writePost) return; id = response.data.writePost.id; dispatch(setWritePostId(id)); history.replace(`/write?id=${id}`); diff --git a/src/containers/write/PublishActionButtonsContainer.tsx b/src/containers/write/PublishActionButtonsContainer.tsx index 48132a35..246df2a2 100644 --- a/src/containers/write/PublishActionButtonsContainer.tsx +++ b/src/containers/write/PublishActionButtonsContainer.tsx @@ -100,12 +100,15 @@ const PublishActionButtonsContainer: React.FC< variables: variables, }); - if (!response || !response.data) return; + if (!response.data?.writePost) { + toast.error('포스트 작성 실패'); + return; + } + const { user, url_slug } = response.data.writePost; await client.resetStore(); history.push(`/@${user.username}/${url_slug}`); } catch (error) { - console.log('writePost error', error); toast.error('포스트 작성 실패'); } }; @@ -119,12 +122,16 @@ const PublishActionButtonsContainer: React.FC< ...variables, }, }); - if (!response || !response.data) return; + + if (!response.data?.editPost) { + toast.error('포스트 수정 실패'); + return; + } + const { user, url_slug } = response.data.editPost; await client.resetStore(); history.push(`/@${user.username}/${url_slug}`); } catch (error) { - console.log('editPost error', error); toast.error('포스트 수정 실패'); } }; From 78902f9ad7d5b1fd112e06d5a330e9ab918ab9c5 Mon Sep 17 00:00:00 2001 From: carrick Date: Mon, 26 Feb 2024 06:05:18 +0900 Subject: [PATCH 07/10] fix: duplicated image upload bug --- src/containers/write/MarkdownEditorContainer.tsx | 5 +++-- src/containers/write/PublishPreviewContainer.tsx | 1 - src/lib/api/files.ts | 4 +--- src/lib/hooks/useUpload.tsx | 2 -- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/containers/write/MarkdownEditorContainer.tsx b/src/containers/write/MarkdownEditorContainer.tsx index 9365d1f1..a0a3a87a 100644 --- a/src/containers/write/MarkdownEditorContainer.tsx +++ b/src/containers/write/MarkdownEditorContainer.tsx @@ -214,6 +214,7 @@ const MarkdownEditorContainer: React.FC = () => { if (shallowEqual(lastSavedData, { title, body: markdown })) { return; } + if (postId) { await createPostHistory({ variables: { @@ -224,6 +225,7 @@ const MarkdownEditorContainer: React.FC = () => { }, }); } + setLastSavedData({ title, body: markdown, @@ -255,7 +257,6 @@ const MarkdownEditorContainer: React.FC = () => { const uploadWithPostId = useCallback( async (file: File) => { if (!file) return; - if (writePostLoading || editPostLoading) return; let id = postIdRef.current; if (!id) { const title = titleRef.current || 'Temp Title'; @@ -291,7 +292,7 @@ const MarkdownEditorContainer: React.FC = () => { toast.error('이미지 업로드 실패! 잠시 후 다시 시도하세요.'); }); }, - [cfUpload, writePost, dispatch, history, writePostLoading, editPostLoading], + [cfUpload, writePost, dispatch, history], ); const onDragDropUpload = useCallback( diff --git a/src/containers/write/PublishPreviewContainer.tsx b/src/containers/write/PublishPreviewContainer.tsx index 86452b2d..dced3516 100644 --- a/src/containers/write/PublishPreviewContainer.tsx +++ b/src/containers/write/PublishPreviewContainer.tsx @@ -114,7 +114,6 @@ const PublishPreviewContainer: React.FC = ({ useEffect(() => { if (!file) return; - if (writePostLoading) return; uploadWithPostId(file); // eslint-disable-next-line react-hooks/exhaustive-deps }, [file, writePostLoading]); diff --git a/src/lib/api/files.ts b/src/lib/api/files.ts index 1698d8e5..b5e1a9f7 100644 --- a/src/lib/api/files.ts +++ b/src/lib/api/files.ts @@ -35,9 +35,7 @@ export async function uploadImage( headers: { 'Content-Type': 'multipart/form-data', }, - onUploadProgress(e) { - console.log(e); - }, + onUploadProgress(event) {}, }, ); diff --git a/src/lib/hooks/useUpload.tsx b/src/lib/hooks/useUpload.tsx index a8698b39..d803d502 100644 --- a/src/lib/hooks/useUpload.tsx +++ b/src/lib/hooks/useUpload.tsx @@ -11,8 +11,6 @@ const useUpload = () => { input.type = 'file'; input.onchange = () => { clearTimeout(timeout); - console.log('onchange'); - console.log(input.files); if (!input.files) return reject(); const file = input.files[0]; setFile(file); From bcf6fff55f7f12952bea381c5a9899eeb83aa8a4 Mon Sep 17 00:00:00 2001 From: carrick Date: Mon, 26 Feb 2024 08:32:10 +0900 Subject: [PATCH 08/10] fix: show toast when post api loading --- .../write/MarkdownEditorContainer.tsx | 1 - .../write/PublishActionButtonsContainer.tsx | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/containers/write/MarkdownEditorContainer.tsx b/src/containers/write/MarkdownEditorContainer.tsx index a0a3a87a..40e7e74f 100644 --- a/src/containers/write/MarkdownEditorContainer.tsx +++ b/src/containers/write/MarkdownEditorContainer.tsx @@ -230,7 +230,6 @@ const MarkdownEditorContainer: React.FC = () => { title, body: markdown, }); - notifySuccess(); }, [ createPostHistory, diff --git a/src/containers/write/PublishActionButtonsContainer.tsx b/src/containers/write/PublishActionButtonsContainer.tsx index 246df2a2..d4d5d264 100644 --- a/src/containers/write/PublishActionButtonsContainer.tsx +++ b/src/containers/write/PublishActionButtonsContainer.tsx @@ -90,11 +90,16 @@ const PublishActionButtonsContainer: React.FC< }; const onPublish = async () => { - if (writePostLoading) return; + if (writePostLoading) { + toast.info('포스트 작성 중입니다.'); + return; + } + if (options.title.trim() === '') { toast.error('제목이 비어있습니다.'); return; } + try { const response = await writePost({ variables: variables, @@ -114,7 +119,16 @@ const PublishActionButtonsContainer: React.FC< }; const onEdit = async () => { - if (editPostLoading) return; + if (editPostLoading) { + toast.info('포스트 수정 중입니다.'); + return; + } + + if (options.title.trim() === '') { + toast.error('제목이 비어있습니다.'); + return; + } + try { const response = await editPost({ variables: { From 284a454acf7c5c031648e2d9e1cceb1982440e3d Mon Sep 17 00:00:00 2001 From: carrick Date: Mon, 26 Feb 2024 09:54:04 +0900 Subject: [PATCH 09/10] fix: show not found page when new write post --- src/containers/write/ActiveEditor.tsx | 1 + src/containers/write/PublishActionButtonsContainer.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/containers/write/ActiveEditor.tsx b/src/containers/write/ActiveEditor.tsx index fa1c9b59..080f9cf0 100644 --- a/src/containers/write/ActiveEditor.tsx +++ b/src/containers/write/ActiveEditor.tsx @@ -132,6 +132,7 @@ const ActiveEditor: React.FC = () => { }, [dispatch, lastPostHistory, post]); if ( + id && !newPost && ((!readPostForEdit.loading && post === null) || (post && post.user.id !== userId)) diff --git a/src/containers/write/PublishActionButtonsContainer.tsx b/src/containers/write/PublishActionButtonsContainer.tsx index d4d5d264..ec1721c3 100644 --- a/src/containers/write/PublishActionButtonsContainer.tsx +++ b/src/containers/write/PublishActionButtonsContainer.tsx @@ -114,6 +114,7 @@ const PublishActionButtonsContainer: React.FC< await client.resetStore(); history.push(`/@${user.username}/${url_slug}`); } catch (error) { + console.log('write post failed', error); toast.error('포스트 작성 실패'); } }; @@ -146,6 +147,7 @@ const PublishActionButtonsContainer: React.FC< await client.resetStore(); history.push(`/@${user.username}/${url_slug}`); } catch (error) { + console.log('edit post failed', error); toast.error('포스트 수정 실패'); } }; From f04ce88aed5c05432432d7649b9684a7b11cf203 Mon Sep 17 00:00:00 2001 From: carrick Date: Mon, 26 Feb 2024 13:08:39 +0900 Subject: [PATCH 10/10] fix: header custom logo style --- src/components/base/HeaderLogo.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/base/HeaderLogo.tsx b/src/components/base/HeaderLogo.tsx index 2bb27f47..7e7e29ed 100644 --- a/src/components/base/HeaderLogo.tsx +++ b/src/components/base/HeaderLogo.tsx @@ -37,7 +37,7 @@ const HeaderLogo: React.FC = ({ - {userLogo.title || createFallbackTitle(username)} + {userLogo.title || createFallbackTitle(username)} ); @@ -69,7 +69,7 @@ const HeaderLogoBlock = styled.div` .user-logo { display: block; - max-width: calc(100vw - 200px); + max-width: calc(100vw - 250px); ${ellipsis}; } `;