Skip to content

Commit

Permalink
feat: edit source post moderation entity (#3796)
Browse files Browse the repository at this point in the history
* feat: edit source post moderation entity

* fix: query param to understand if it is a moderation

* refactor: naming convention for loading states
  • Loading branch information
sshanzel authored Nov 14, 2024
1 parent 33935a8 commit 219e654
Show file tree
Hide file tree
Showing 20 changed files with 318 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const FreeformList = forwardRef(function SharePostCard(
[post.contentHtml],
);

const { title } = useTruncatedSummary(post, content);
const { title } = useTruncatedSummary(post?.title, content);

return (
<FeedItemContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const ArticleList = forwardRef(function ArticleList(

const { showFeedback } = usePostFeedback({ post });
const isFeedPreview = useFeedPreviewMode();
const { title } = useTruncatedSummary(post);
const { title } = useTruncatedSummary(post?.title);

return (
<FeedItemContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const CollectionList = forwardRef(function CollectionCard(
ref: Ref<HTMLElement>,
) {
const image = usePostImage(post);
const { title } = useTruncatedSummary(post);
const { title } = useTruncatedSummary(post?.title);

return (
<FeedItemContainer
Expand Down
5 changes: 1 addition & 4 deletions packages/shared/src/components/cards/share/ShareList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ export const ShareList = forwardRef(function ShareList(
const containerRef = useRef<HTMLDivElement>();
const isFeedPreview = useFeedPreviewMode();
const isVideoType = isVideoPost(post);
const { title } = useTruncatedSummary({
...post,
...(!post.title && { title: post.sharedPost.title }),
});
const { title } = useTruncatedSummary(post?.title || post?.sharedPost?.title);

return (
<FeedItemContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function PostModerationModal({
content,
contentHtml,
sharedPost,
sourceId,
source,
} = data;
const onReadArticle = useReadArticle({
origin: Origin.ArticleModal,
Expand All @@ -70,8 +70,8 @@ function PostModerationModal({
<Modal.Body className="gap-6">
{isModerator && (
<SquadModerationActions
onReject={() => onReject([id], sourceId)}
onApprove={() => onApprove([id], sourceId)}
onReject={() => onReject([id], source.id)}
onApprove={() => onApprove([id], source.id)}
/>
)}
<div className="flex flex-row items-center justify-between">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import { CameraIcon } from '../../../icons';
import { TextField } from '../../../fields/TextField';
import MarkdownInput from '../../../fields/MarkdownInput';
import { WritePageMain } from './common';
import {
EditPostProps,
Post,
imageSizeLimitMB,
} from '../../../../graphql/posts';
import { EditPostProps, imageSizeLimitMB } from '../../../../graphql/posts';
import { formToJson } from '../../../../lib/form';
import useDebounceFn from '../../../../hooks/useDebounceFn';
import AlertPointer, { AlertPlacement } from '../../../alert/AlertPointer';
import { useActions, useViewSize, ViewSize } from '../../../../hooks';
import { ActionType } from '../../../../graphql/actions';
import useSidebarRendered from '../../../../hooks/useSidebarRendered';
import { base64ToFile } from '../../../../lib/base64';
import { useWritePostContext, WriteForm } from '../../../../contexts';
import {
MergedWriteObject,
useWritePostContext,
WriteForm,
} from '../../../../contexts';
import { defaultMarkdownCommands } from '../../../../hooks/input';
import { WriteFooter } from '../../write';

Expand All @@ -32,7 +32,7 @@ export const checkSavedProperty = (
prop: keyof WriteForm,
form: EditPostProps,
draft: Partial<WriteForm>,
post?: Post,
post?: MergedWriteObject,
): boolean =>
!form[prop] || (form[prop] === draft?.[prop] && form[prop] !== post?.[prop]);

Expand All @@ -53,7 +53,7 @@ export function WriteFreeformContent({
onSubmitForm,
isPosting,
squad,
post,
fetchedPost,
draft,
updateDraft,
isUpdatingDraft,
Expand Down Expand Up @@ -115,7 +115,7 @@ export function WriteFreeformContent({
closeable
fileSizeLimitMB={imageSizeLimitMB}
name="image"
initialValue={draft?.image ?? post?.image}
initialValue={draft?.image ?? fetchedPost?.image}
onChange={(base64, file) =>
updateDraft({
...draft,
Expand Down Expand Up @@ -157,7 +157,7 @@ export function WriteFreeformContent({
label="Post Title*"
placeholder="Give your post a title"
required
defaultValue={draft?.title ?? post?.title}
defaultValue={draft?.title ?? fetchedPost?.title}
onInput={onFormUpdate}
maxLength={MAX_TITLE_LENGTH}
/>
Expand All @@ -166,7 +166,7 @@ export function WriteFreeformContent({
className={{ container: 'mt-4' }}
sourceId={squad?.id}
onValueUpdate={onFormUpdate}
initialContent={draft?.content ?? post?.content ?? ''}
initialContent={draft?.content ?? fetchedPost?.content ?? ''}
textareaProps={{ name: 'content' }}
enabledCommand={{ ...defaultMarkdownCommands, upload: true }}
isUpdatingDraft={isUpdatingDraft}
Expand Down
63 changes: 48 additions & 15 deletions packages/shared/src/components/post/write/ShareLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,32 @@ import { WriteFooter } from './WriteFooter';
import { SubmitExternalLink } from './SubmitExternalLink';
import { usePostToSquad } from '../../../hooks';
import { useToastNotification } from '../../../hooks/useToastNotification';
import { Post } from '../../../graphql/posts';
import { Post, PostType } from '../../../graphql/posts';
import { WriteLinkPreview } from './WriteLinkPreview';
import { generateDefaultSquad } from './SquadsDropdown';
import { useSquadCreate } from '../../../hooks/squads/useSquadCreate';
import { useAuthContext } from '../../../contexts/AuthContext';
import useSourcePostModeration from '../../../hooks/source/useSourcePostModeration';
import { SourcePostModeration } from '../../../graphql/squads';

interface ShareLinkProps {
squad: Squad;
post?: Post;
moderated?: SourcePostModeration;
className?: string;
onPostSuccess: (post: Post, url: string) => void;
}

export function ShareLink({
squad,
post,
className,
onPostSuccess,
post,
moderated,
}: ShareLinkProps): ReactElement {
const fetchedPost = post || moderated;
const { displayToast } = useToastNotification();
const [commentary, setCommentary] = useState(post?.title ?? '');
const [commentary, setCommentary] = useState(fetchedPost?.title ?? '');
const { squads, user } = useAuthContext();
const {
getLinkPreview,
Expand All @@ -37,10 +42,23 @@ export function ShareLink({
onSubmitPost,
onUpdatePost,
onUpdatePreview,
} = usePostToSquad({ onPostSuccess, initialPreview: post?.sharedPost });
} = usePostToSquad({
onPostSuccess,
initialPreview:
fetchedPost?.sharedPost ||
(moderated && {
url: moderated.externalLink,
title: moderated.title,
image: moderated.image,
}),
});
const { push } = useRouter();
const { onUpdatePostModeration, isPending: isPostingModeration } =
useSourcePostModeration({
onSuccess: async () => push(squad.permalink),
});

const { onCreateSquad, isLoading } = useSquadCreate({
const { onCreateSquad, isLoading: isCreatingSquad } = useSquadCreate({
onSuccess: (newSquad) => {
onSubmitPost(null, newSquad, commentary);
return push(newSquad.permalink);
Expand All @@ -51,19 +69,34 @@ export function ShareLink({
const onSubmit: FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();

if (isPosting || isLoading) {
if (isPosting || isPostingModeration || isCreatingSquad) {
return null;
}

if (!squad) {
return displayToast('You must select a Squad to post to!');
}

if (post?.id) {
if (commentary !== post?.title) {
onUpdatePost(e, post.id, commentary);
if (fetchedPost?.id) {
if (
commentary === fetchedPost?.title ||
commentary === moderated?.content
) {
return push(squad.permalink);
}
return push(squad.permalink);

if (moderated) {
return onUpdatePostModeration({
type: PostType.Share,
sourceId: squad.id,
id: moderated.id,
title: moderated.sharedPost ? commentary : preview.title,
content: moderated.sharedPost ? null : commentary,
imageUrl: moderated.sharedPost ? null : preview.image,
});
}

return onUpdatePost(e, fetchedPost.id, commentary);
}

if (squads.some(({ id }) => squad.id === id)) {
Expand All @@ -80,23 +113,23 @@ export function ShareLink({
onSubmit={onSubmit}
id="write-post-link"
>
{post?.id ? (
{fetchedPost?.sharedPost ? (
<WriteLinkPreview
link={post.sharedPost.permalink}
preview={post.sharedPost}
link={fetchedPost.sharedPost.permalink}
preview={fetchedPost.sharedPost}
showPreviewLink={false}
/>
) : (
<SubmitExternalLink
preview={preview || post?.sharedPost}
preview={preview || fetchedPost?.sharedPost}
getLinkPreview={getLinkPreview}
isLoadingPreview={isLoadingPreview}
onSelectedHistory={onUpdatePreview}
/>
)}

<MarkdownInput
initialContent={commentary || post?.title || ''}
initialContent={commentary || fetchedPost?.title || ''}
enabledCommand={{ mention: true }}
showMarkdownGuide={false}
onValueUpdate={setCommentary}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,9 @@ export function SquadModerationItem(
status === SourcePostModerationStatus.Rejected ? WarningIcon : TimerIcon;

const post = data.sharedPost || data.post;
const { title } = useTruncatedSummary({
...data,
...post,
...(!post?.title && !data.title && { title: post?.sharedPost?.title }),
});
const { title } = useTruncatedSummary(
data?.title || data.sharedPost?.title || data.post?.title,
);

return (
<div className="relative flex flex-col gap-4 p-6 hover:bg-surface-hover">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { ReactElement, useId } from 'react';
import { SourcePostModeration } from '../../../graphql/posts';
import { useRouter } from 'next/router';
import OptionsButton from '../../buttons/OptionsButton';
import ContextMenu from '../../fields/ContextMenu';
import { TrashIcon } from '../../icons';
import { EditIcon, TrashIcon } from '../../icons';
import useContextMenu from '../../../hooks/useContextMenu';
import { usePrompt } from '../../../hooks/usePrompt';
import { SourcePostModeration } from '../../../graphql/squads';

interface SquadModerationItemContextMenuProps
extends Pick<SourcePostModeration, 'id'> {
Expand All @@ -18,6 +19,7 @@ export const SquadModerationItemContextMenu = ({
const contextMenuId = useId();
const { isOpen, onMenuClick } = useContextMenu({ id: contextMenuId });
const { showPrompt } = usePrompt();
const router = useRouter();

const handleDelete = async () => {
const confirm = await showPrompt({
Expand All @@ -39,11 +41,11 @@ export const SquadModerationItemContextMenu = ({
/>
<ContextMenu
options={[
// {
// label: 'Edit post',
// action: () => console.log('Edit'), // todo: implement after SourcePostModeration edit page is created
// icon: <EditIcon aria-hidden />,
// },
{
label: 'Edit post',
action: () => router.push(`/posts/${id}/edit?moderation=true`),
icon: <EditIcon aria-hidden />,
},
{
label: 'Delete post',
action: handleDelete,
Expand Down
7 changes: 7 additions & 0 deletions packages/shared/src/components/squads/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ export const createModerationPromptProps: PromptOptions = {
icon: <TimerIcon size={IconSize.XXXLarge} />,
};

export const editModerationPromptProps: PromptOptions = {
...createModerationPromptProps,
title: 'Your edit has been submitted for review',
description:
'Your edit is now waiting for the admin’s approval. We’ll notify you once it’s been reviewed.',
};

export interface SquadSettingsProps {
handle: string;
}
12 changes: 11 additions & 1 deletion packages/shared/src/contexts/WritePostContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import React, {
useContext,
} from 'react';
import { useRouter } from 'next/router';
import { Post, SourcePostModeration } from '../graphql/posts';
import { Post, SharedPost } from '../graphql/posts';
import { Squad } from '../graphql/sources';
import ConditionalWrapper from '../components/ConditionalWrapper';
import { useViewSize, ViewSize } from '../hooks';
import { FormWrapper } from '../components/fields/form';
import { SourcePostModeration } from '../graphql/squads';

export interface WriteForm {
title: string;
Expand All @@ -19,6 +20,13 @@ export interface WriteForm {
filename?: string;
}

export interface MergedWriteObject
extends Partial<Pick<WriteForm, 'title' | 'content' | 'image'>> {
id?: string;
type?: string;
sharedPost?: SharedPost;
}

export interface WritePostProps {
onSubmitForm: (
e: FormEvent<HTMLFormElement>,
Expand All @@ -27,6 +35,8 @@ export interface WritePostProps {
isPosting: boolean;
squad: Squad;
post?: Post;
moderated?: SourcePostModeration;
fetchedPost?: MergedWriteObject;
enableUpload: boolean;
formRef?: MutableRefObject<HTMLFormElement>;
draft?: Partial<WriteForm>;
Expand Down
Loading

0 comments on commit 219e654

Please sign in to comment.