Yuki Takei 4 лет назад
Родитель
Сommit
e905723637

+ 6 - 13
packages/app/src/components/LikeButtons.tsx

@@ -9,12 +9,13 @@ import AppContainer from '~/client/services/AppContainer';
 import { IUser } from '../interfaces/user';
 
 type LikeButtonsProps = {
-  appContainer: AppContainer,
 
   hideTotalNumber?: boolean,
   sumOfLikers: number,
-  isLiked: boolean,
   likers: IUser[],
+
+  isGuestUser?: boolean,
+  isLiked?: boolean,
   onLikeClicked?: ()=>void,
 }
 
@@ -27,30 +28,22 @@ const LikeButtons: FC<LikeButtonsProps> = (props: LikeButtonsProps) => {
     setIsPopoverOpen(!isPopoverOpen);
   };
 
-  const handleClick = () => {
-    if (props.onLikeClicked == null) {
-      return;
-    }
-    props.onLikeClicked();
-  };
-
   const {
-    appContainer, hideTotalNumber, isLiked, sumOfLikers,
+    hideTotalNumber, isGuestUser, isLiked, sumOfLikers, onLikeClicked,
   } = props;
-  const { isGuestUser } = appContainer;
 
   return (
     <div className="btn-group" role="group" aria-label="Like buttons">
       <button
         type="button"
         id="like-button"
-        onClick={handleClick}
+        onClick={onLikeClicked}
         className={`btn btn-like border-0
             ${isLiked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
       >
         <i className={`fa ${isLiked ? 'fa-heart' : 'fa-heart-o'}`}></i>
       </button>
-      {isGuestUser && (
+      { isGuestUser && (
         <UncontrolledTooltip placement="top" target="like-button" fade={false}>
           {t('Not available for guest')}
         </UncontrolledTooltip>

+ 65 - 30
packages/app/src/components/Navbar/SubNavButtons.tsx

@@ -1,38 +1,68 @@
 import React, { useCallback } from 'react';
 
-import SubscribeButton from '../SubscribeButton';
-import PageReactionButtons from '../PageReactionButtons';
-import { useSWRPageInfo } from '../../stores/page';
-import { useSWRBookmarkInfo } from '../../stores/bookmark';
 import { toastError } from '../../client/util/apiNotification';
 import { apiv3Put } from '../../client/util/apiv3-client';
-import { useSWRxLikerList } from '../../stores/user';
+
+import { IPageInfo, isExistPageInfo } from '~/interfaces/page';
+
+import { useSWRPageInfo } from '../../stores/page';
+import { useSWRBookmarkInfo } from '../../stores/bookmark';
+import { useSWRxUsersList } from '../../stores/user';
 import { useIsGuestUser } from '~/stores/context';
 
+import SubscribeButton from '../SubscribeButton';
+import LikeButtons from '../LikeButtons';
+import BookmarkButtons from '../BookmarkButtons';
+import { SubscriptionStatusType } from '~/interfaces/subscription';
 
-type SubNavButtonsSubstanceProps= {
+
+type CommonProps = {
   isCompactMode?: boolean,
   showPageControlDropdown?: boolean,
 }
-const SubNavButtonsSubstance = (props: { pageId: string } & SubNavButtonsSubstanceProps): JSX.Element => {
+
+type SubNavButtonsSubstanceProps= CommonProps & {
+  pageId: string,
+  pageInfo: IPageInfo,
+}
+
+const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element => {
   const {
-    isCompactMode, pageId, showPageControlDropdown,
+    pageInfo, pageId, isCompactMode, showPageControlDropdown,
   } = props;
 
   const { data: isGuestUser } = useIsGuestUser();
 
-  const { data: pageInfo, error: pageInfoError, mutate: mutatePageInfo } = useSWRPageInfo(pageId);
-  const { data: likers } = useSWRxLikerList(pageInfo?.likerIds);
+  const { mutate: mutatePageInfo } = useSWRPageInfo(pageId);
+
+  const { data: likers } = useSWRxUsersList(pageInfo.likerIds);
   const { data: bookmarkInfo, error: bookmarkInfoError, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(pageId, true);
 
+  const subscribeClickhandler = useCallback(async() => {
+    if (isGuestUser == null || isGuestUser) {
+      return;
+    }
+
+    const newStatus = pageInfo.subscriptionStatus === SubscriptionStatusType.SUBSCRIBE
+      ? SubscriptionStatusType.UNSUBSCRIBE
+      : SubscriptionStatusType.SUBSCRIBE;
+
+    try {
+      await apiv3Put('/page/subscribe', { pageId, status: newStatus });
+      mutatePageInfo();
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [isGuestUser, mutatePageInfo, pageId, pageInfo]);
+
   const likeClickhandler = useCallback(async() => {
     if (isGuestUser == null || isGuestUser) {
       return;
     }
 
     try {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      await apiv3Put('/page/likes', { pageId, bool: !pageInfo!.isLiked });
+      await apiv3Put('/page/likes', { pageId, bool: !pageInfo.isLiked });
       mutatePageInfo();
     }
     catch (err) {
@@ -41,12 +71,11 @@ const SubNavButtonsSubstance = (props: { pageId: string } & SubNavButtonsSubstan
   }, [isGuestUser, mutatePageInfo, pageId, pageInfo]);
 
   const bookmarkClickHandler = useCallback(async() => {
-    if (isGuestUser == null || isGuestUser) {
+    if (isGuestUser == null || isGuestUser || bookmarkInfo == null) {
       return;
     }
     try {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      await apiv3Put('/bookmarks', { pageId, bool: !bookmarkInfo!.isBookmarked });
+      await apiv3Put('/bookmarks', { pageId, bool: !bookmarkInfo.isBookmarked });
       mutateBookmarkInfo();
     }
     catch (err) {
@@ -55,10 +84,6 @@ const SubNavButtonsSubstance = (props: { pageId: string } & SubNavButtonsSubstan
   }, [bookmarkInfo, isGuestUser, mutateBookmarkInfo, pageId]);
 
 
-  if (pageInfoError != null || pageInfo == null) {
-    return <></>;
-  }
-
   if (bookmarkInfoError != null || bookmarkInfo == null) {
     return <></>;
   }
@@ -69,21 +94,25 @@ const SubNavButtonsSubstance = (props: { pageId: string } & SubNavButtonsSubstan
   return (
     <div className="d-flex" style={{ gap: '2px' }}>
       <span>
-        <SubscribeButton pageId={props.pageId} />
+        <SubscribeButton
+          status={pageInfo.subscriptionStatus}
+          onClick={subscribeClickhandler}
+        />
       </span>
-      <PageReactionButtons
-        isCompactMode={isCompactMode}
+      <LikeButtons
+        hideTotalNumber={isCompactMode}
+        onLikeClicked={likeClickhandler}
         sumOfLikers={sumOfLikers}
         isLiked={isLiked}
-        likers={likers || []}
-        onLikeClicked={likeClickhandler}
+        likers={likers}
+      />
+      <BookmarkButtons
+        hideTotalNumber={isCompactMode}
         sumOfBookmarks={sumOfBookmarks}
         isBookmarked={isBookmarked}
         bookmarkedUsers={bookmarkedUsers}
         onBookMarkClicked={bookmarkClickHandler}
-      >
-      </PageReactionButtons>
-
+      />
       { showPageControlDropdown && (
         /*
           TODO:
@@ -104,16 +133,22 @@ const SubNavButtonsSubstance = (props: { pageId: string } & SubNavButtonsSubstan
   );
 };
 
-type SubNavButtonsProps= SubNavButtonsSubstanceProps & {
+type SubNavButtonsProps= CommonProps & {
   pageId?: string | null,
 };
 
 export const SubNavButtons = (props: SubNavButtonsProps): JSX.Element => {
   const { pageId, isCompactMode } = props;
 
-  if (pageId == null) {
+  const { data: pageInfo, error } = useSWRPageInfo(pageId ?? null);
+
+  if (pageId == null || pageInfo == null || error != null) {
+    return <></>;
+  }
+
+  if (!isExistPageInfo(pageInfo)) {
     return <></>;
   }
 
-  return <SubNavButtonsSubstance pageId={pageId} isCompactMode={isCompactMode} />;
+  return <SubNavButtonsSubstance pageInfo={pageInfo} pageId={pageId} isCompactMode={isCompactMode} />;
 };

+ 0 - 49
packages/app/src/components/PageReactionButtons.tsx

@@ -1,49 +0,0 @@
-import React, { FC } from 'react';
-import LikeButtons from './LikeButtons';
-import { IUser } from '../interfaces/user';
-import BookmarkButtons from './BookmarkButtons';
-
-type Props = {
-  isCompactMode?: boolean,
-
-  isLiked: boolean,
-  sumOfLikers: number,
-  likers: IUser[],
-  onLikeClicked?: ()=>void,
-
-  isBookmarked: boolean,
-  sumOfBookmarks: number,
-  bookmarkedUsers: IUser[]
-  onBookMarkClicked: ()=>void,
-}
-
-
-const PageReactionButtons : FC<Props> = (props: Props) => {
-  const {
-    isCompactMode, sumOfLikers, isLiked, likers, onLikeClicked, sumOfBookmarks, isBookmarked, bookmarkedUsers, onBookMarkClicked,
-  } = props;
-
-
-  return (
-    <>
-      <LikeButtons
-        hideTotalNumber={isCompactMode}
-        onLikeClicked={onLikeClicked}
-        sumOfLikers={sumOfLikers}
-        isLiked={isLiked}
-        likers={likers}
-      >
-      </LikeButtons>
-      <BookmarkButtons
-        hideTotalNumber={isCompactMode}
-        sumOfBookmarks={sumOfBookmarks}
-        isBookmarked={isBookmarked}
-        bookmarkedUsers={bookmarkedUsers}
-        onBookMarkClicked={onBookMarkClicked}
-      >
-      </BookmarkButtons>
-    </>
-  );
-};
-
-export default PageReactionButtons;

+ 9 - 41
packages/app/src/components/SubscribeButton.tsx

@@ -2,62 +2,30 @@ import React, { FC } from 'react';
 
 import { useTranslation } from 'react-i18next';
 import { UncontrolledTooltip } from 'reactstrap';
-import { useSWRxSubscriptionStatus } from '../stores/page';
+import { SubscriptionStatusType } from '~/interfaces/subscription';
 
 
-import { toastError } from '~/client/util/apiNotification';
-import { apiv3Put } from '~/client/util/apiv3-client';
-import { useIsGuestUser } from '~/stores/context';
-
 type Props = {
-  pageId: string,
+  isGuestUser?: boolean,
+  status?: SubscriptionStatusType,
+  onClick?: () => Promise<void>,
 };
 
 const SubscribeButton: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
-  const { pageId } = props;
-
-  const { data: isGuestUser } = useIsGuestUser();
-  const { data: subscriptionData, mutate } = useSWRxSubscriptionStatus(pageId);
-
-  let isSubscribed;
-
-  switch (subscriptionData?.status) {
-    case true:
-      isSubscribed = true;
-      break;
-    case false:
-      isSubscribed = false;
-      break;
-    default:
-      isSubscribed = null;
-  }
-
-  const buttonClass = `${isSubscribed ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`;
-  const iconClass = isSubscribed || isSubscribed == null ? 'fa fa-eye' : 'fa fa-eye-slash';
+  const { isGuestUser, status } = props;
 
-  const handleClick = async() => {
-    if (isGuestUser) {
-      return;
-    }
+  const isSubscribing = status === SubscriptionStatusType.SUBSCRIBE;
 
-    try {
-      const res = await apiv3Put('/page/subscribe', { pageId, status: !isSubscribed });
-      if (res) {
-        mutate();
-      }
-    }
-    catch (err) {
-      toastError(err);
-    }
-  };
+  const buttonClass = `${isSubscribing ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`;
+  const iconClass = isSubscribing === false ? 'fa fa-eye-slash' : 'fa fa-eye';
 
   return (
     <>
       <button
         type="button"
         id="subscribe-button"
-        onClick={handleClick}
+        onClick={props.onClick}
         className={`btn btn-subscribe border-0 ${buttonClass}`}
       >
         <i className={iconClass}></i>

+ 14 - 3
packages/app/src/interfaces/page.ts

@@ -3,6 +3,7 @@ import { IUser } from './user';
 import { IRevision } from './revision';
 import { ITag } from './tag';
 import { HasObjectId } from './has-object-id';
+import { SubscriptionStatusType } from './subscription';
 
 export type IPage = {
   path: string,
@@ -35,18 +36,28 @@ export type IPageHasId = IPage & HasObjectId;
 
 export type IPageForItem = Partial<IPageHasId & {isTarget?: boolean}>;
 
-export type IPageInfo = {
+export type IPageInfoCommon = {
+  isEmpty: boolean,
+  isDeletable: boolean,
+  isAbleToDeleteCompletely: boolean,
+}
+
+export type IPageInfo = IPageInfoCommon & {
   bookmarkCount: number,
   sumOfLikers: number,
   likerIds: string[],
   sumOfSeenUsers: number,
   seenUserIds: string[],
-  isDeletable: boolean,
-  isAbleToDeleteCompletely: boolean,
+
   isBookmarked?: boolean,
   isLiked?: boolean,
+  subscriptionStatus?: SubscriptionStatusType,
 }
 
+export const isExistPageInfo = (pageInfo: IPageInfoCommon | IPageInfo | undefined): pageInfo is IPageInfo => {
+  return pageInfo != null && !pageInfo.isEmpty;
+};
+
 export type IPageWithMeta<M = Record<string, unknown>> = {
   pageData: IPageHasId,
   pageMeta?: Partial<IPageInfo> & M,

+ 3 - 4
packages/app/src/stores/page.tsx

@@ -2,12 +2,11 @@ import useSWR, { SWRResponse } from 'swr';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 
-import { IPageInfo, IPageHasId } from '~/interfaces/page';
+import { IPageInfo, IPageInfoCommon, IPageHasId } from '~/interfaces/page';
 import { IPagingResult } from '~/interfaces/paging-result';
 import { apiGet } from '../client/util/apiv1-client';
 
 import { IPageTagsInfo } from '../interfaces/pageTagsInfo';
-import { useIsGuestUser } from './context';
 
 
 export const useSWRxPageByPath = (path: string, initialData?: IPageHasId): SWRResponse<IPageHasId, Error> => {
@@ -47,10 +46,10 @@ export const useSWRxPageList = (
   );
 };
 
-export const useSWRPageInfo = (pageId: string | null): SWRResponse<IPageInfo, Error> => {
+export const useSWRPageInfo = (pageId: string | null): SWRResponse<IPageInfoCommon | IPageInfo, Error> => {
   return useSWR(
     pageId != null ? `/page/info?pageId=${pageId}` : null,
-    endpoint => apiv3Get<IPageInfo>(endpoint).then(response => response.data),
+    endpoint => apiv3Get<IPageInfoCommon | IPageInfo>(endpoint).then(response => response.data),
   );
 };
 

+ 1 - 11
packages/app/src/stores/user.tsx

@@ -5,17 +5,7 @@ import { IUserHasId } from '~/interfaces/user';
 
 import { checkAndUpdateImageUrlCached } from '~/stores/middlewares/user';
 
-import { apiGet } from '../client/util/apiv1-client';
-
-export const useSWRxLikerList = (likerIds: string[] = []): SWRResponse<IUserHasId[], Error> => {
-  const shouldFetch = likerIds.length > 0;
-  return useSWR(shouldFetch ? ['/users.list', [...likerIds].join(',')] : null, (endpoint:string, userIds:string) => {
-    return apiGet(endpoint, { user_ids: userIds }).then((response:any) => response.users);
-  });
-};
-
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const useSWRxUsersList = <Data, Error>(userIds: string[]): SWRResponse<IUserHasId[], Error> => {
+export const useSWRxUsersList = (userIds: string[]): SWRResponse<IUserHasId[], Error> => {
   const distinctUserIds = userIds.length > 0 ? Array.from(new Set(userIds)).sort() : [];
   return useSWR(
     distinctUserIds.length > 0 ? ['/users/list', distinctUserIds] : null,