Skip to content

Feature/turnstile #501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', 'G-8D0MD2S4PK');
</script>
<!-- Cloudflare (turnstile) -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onAppReady" defer></script>


<title>React App</title>
Expand Down
4 changes: 2 additions & 2 deletions src/components/base/HeaderLogo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const HeaderLogo: React.FC<HeaderLogoProps> = ({
<VelogIcon />
</VelogLogoLink>
<VLink to={velogPath} className="user-logo">
{userLogo.title || createFallbackTitle(username)}
<span>{userLogo.title || createFallbackTitle(username)}</span>
</VLink>
</HeaderLogoBlock>
);
Expand Down Expand Up @@ -69,7 +69,7 @@ const HeaderLogoBlock = styled.div`

.user-logo {
display: block;
max-width: calc(100vw - 200px);
max-width: calc(100vw - 250px);
${ellipsis};
}
`;
Expand Down
10 changes: 9 additions & 1 deletion src/components/write/PublishActionButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -15,12 +16,14 @@ export interface PublishActionButtonsProps {
onCancel: () => void;
onPublish: () => void;
edit: boolean;
isLoading: boolean;
}

const PublishActionButtons: React.FC<PublishActionButtonsProps> = ({
onCancel,
onPublish,
edit,
isLoading,
}) => {
return (
<PublishActionButtonsBlock>
Expand All @@ -32,7 +35,12 @@ const PublishActionButtons: React.FC<PublishActionButtonsProps> = ({
>
취소
</Button>
<Button size="large" data-testid="publish" onClick={onPublish}>
<Button
size="large"
data-testid="publish"
onClick={onPublish}
disabled={isLoading}
>
{edit ? '수정하기' : '출간하기'}
</Button>
</PublishActionButtonsBlock>
Expand Down
24 changes: 21 additions & 3 deletions src/components/write/PublishSeriesCreate.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -110,8 +110,8 @@ const PublishSeriesCreate: React.FC<PublishSeriesCreateProps> = ({
urlSlug: '',
});
const [editing, setEditing] = useState<boolean>(false);

const [defaultUrlSlug, setDefaultUrlSlug] = useState('');
const hideTimeoutId = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
Expand All @@ -137,15 +137,33 @@ const PublishSeriesCreate: React.FC<PublishSeriesCreateProps> = ({
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() === '') {
Expand Down
1 change: 1 addition & 0 deletions src/containers/write/ActiveEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const ActiveEditor: React.FC<ActiveEditorProps> = () => {
}, [dispatch, lastPostHistory, post]);

if (
id &&
!newPost &&
((!readPostForEdit.loading && post === null) ||
(post && post.user.id !== userId))
Expand Down
50 changes: 33 additions & 17 deletions src/containers/write/MarkdownEditorContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,21 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
tags,
} = useSelector((state: RootState) => state.write);
const uncachedClient = useUncachedApolloClient();
const [writePost] = useMutation<WritePostResponse>(WRITE_POST, {
client: uncachedClient,
});
const [writePost, { loading: writePostLoading }] =
useMutation<WritePostResponse>(WRITE_POST, {
client: uncachedClient,
});

const bodyRef = useRef(initialBody);
const titleRef = useRef(title);
const [createPostHistory] =
useMutation<CreatePostHistoryResponse>(CREATE_POST_HISTORY);
const [editPost] = useMutation<EditPostResult>(EDIT_POST, {
client: uncachedClient,
});
const [editPost, { loading: editPostLoading }] = useMutation<EditPostResult>(
EDIT_POST,
{
client: uncachedClient,
},
);

const [lastSavedData, setLastSavedData] = useState({
title: initialTitle,
Expand Down Expand Up @@ -148,6 +152,7 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {

const onTempSave = useCallback(
async (notify?: boolean) => {
if (writePostLoading || editPostLoading) return;
if (!title || !markdown) {
toast.error('제목 또는 내용이 비어있습니다.');
return;
Expand All @@ -171,14 +176,17 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
thumbnail: null,
meta: {},
series_id: null,
token: null,
},
});
if (!response || !response.data) return;

if (!response.data?.writePost) return;
const { id } = response.data.writePost;
dispatch(setWritePostId(id));
history.replace(`/write?id=${id}`);
notifySuccess();
}

// tempsaving unreleased post:
if (isTemp) {
await editPost({
Expand All @@ -194,6 +202,7 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
meta: {},
series_id: null,
tags,
token: null,
},
});
notifySuccess();
Expand All @@ -205,19 +214,22 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
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,
});
notifySuccess();
},
[
createPostHistory,
Expand All @@ -231,6 +243,8 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
tags,
title,
writePost,
writePostLoading,
editPostLoading,
],
);

Expand Down Expand Up @@ -259,9 +273,11 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
thumbnail: null,
meta: {},
series_id: null,
token: null,
},
});
if (!response || !response.data) return;

if (!response.data?.writePost) return;
id = response.data.writePost.id;
dispatch(setWritePostId(id));
history.replace(`/write?id=${id}`);
Expand Down
79 changes: 61 additions & 18 deletions src/containers/write/PublishActionButtonsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};

Expand All @@ -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(
Expand Down Expand Up @@ -54,12 +59,16 @@ const PublishActionButtonsContainer: React.FC<

const uncachedClient = useUncachedApolloClient();

const [writePost] = useMutation<WritePostResponse>(WRITE_POST, {
client: uncachedClient,
});
const [editPost] = useMutation<EditPostResult>(EDIT_POST, {
client: uncachedClient,
});
const [writePost, { loading: writePostLoading }] =
useMutation<WritePostResponse>(WRITE_POST, {
client: uncachedClient,
});
const [editPost, { loading: editPostLoading }] = useMutation<EditPostResult>(
EDIT_POST,
{
client: uncachedClient,
},
);

const variables = {
title: options.title,
Expand All @@ -77,44 +86,78 @@ const PublishActionButtonsContainer: React.FC<
short_description: options.description,
},
series_id: safe(() => options.selectedSeries!.id),
token,
};

const onPublish = async () => {
if (writePostLoading) {
toast.info('포스트 작성 중입니다.');
return;
}

if (options.title.trim() === '') {
toast.error('제목이 비어있습니다.');
return;
}

try {
const response = await writePost({
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 (e) {
} catch (error) {
console.log('write post failed', error);
toast.error('포스트 작성 실패');
}
};

const onEdit = async () => {
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}`);
if (editPostLoading) {
toast.info('포스트 수정 중입니다.');
return;
}

if (options.title.trim() === '') {
toast.error('제목이 비어있습니다.');
return;
}

try {
const response = await editPost({
variables: {
id: options.postId,
...variables,
},
});

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('edit post failed', error);
toast.error('포스트 수정 실패');
}
};

return (
<PublishActionButtons
onCancel={onCancel}
onPublish={options.postId ? onEdit : onPublish}
edit={!!options.postId && !options.isTemp}
isLoading={isLoading}
/>
);
};
Expand Down
Loading