Skip to content

Commit

Permalink
[FEAT] 폴더 공유 다이얼로그 (#621)
Browse files Browse the repository at this point in the history
* [FIX] 사용하지 않는 기능 UI 제거 (#577)


* [FIX] 원래 코드 복구 (#568)

* fix: 기존 작업 코드 복구

* fix: build 위해 안쓰는 파일 주석

* [DESIGN] 픽 생성시 zIndex 추가 (#570)

* [FIX] 현재 완전히 구현되지 않은 기능 UI 제거 (#576)

---------

Co-authored-by: kimminkyeu <[email protected]>

* [FIX] 현재 PickRecord에서 조회 안되는 문제 수정 (#581)

* fix: 깃 충돌 방지를 위한 테스트 커믹

* fix: 충돌 방지를 위한 테스트 커밋

* fix: 깃 머지 테스트를 위한 commit

* fix: 깃 머지 테스트를 위한 commit

* fix: 깃 머지 테스트를 위한 commit

* fix: 깃 머지 테스트를 위한 commit

* [FIX] 원래 코드 복구 (#568)

* fix: 기존 작업 코드 복구

* fix: build 위해 안쓰는 파일 주석

* [DESIGN] 픽 생성시 zIndex 추가 (#570)

* [FIX] 현재 완전히 구현되지 않은 기능 UI 제거 (#576)

* [FIX] PickRecord에서 태그가 보이지 않는 문제 수정 (#580)

* refactor: PickTagPicker 이름 변경

* feat: PickRecord에서 수정시 더블 클릭되게 변경

* fix: 페이지 변경시마다 tagList 불러오기

---------

Co-authored-by: kimminkyeu <[email protected]>

* feat: add context menu item 공유하기

- icon, text 추가. 기능 x

* feat: share folder api

- 스키마 정의, res는 아직 수정중이라셔서 any로 받음
- api 호출, 에러핸들링 관련 명세가 없어서 로그만 남김

* refactor: shareFolder API 함수 콜 로직 변경

- 기존: store에 정의된 shareFolderById()를 호출하여 shareFolder API 콜
- 변경: 뷰에서 shareFolder API를 바로 호출
- 이유: 뷰의 props로 id만 주어진 줄 알았는데 requestDTO에 필요한 name도 함께 주어져 store에 비지니스 로직을 따로 둘 필요가 사라짐

* feat: Share Folder Dialog

- 공유하기 다이얼로그 팝업
- Copy 버튼 클릭시 uuid를 포함한 share link 복사
- "내설정" 링크 클릭시 공유된 폴더 링크로 이동(라우팅 경로 아직 안만듦)

* feat: routing share page

- /share/[uuid]로 라우팅 되도록 라우팅 디렉토리 생성

* fix: Share Folder Link 경로 수정

- 기존: domain/folder/uuid
- 변경: domain/share/uuid

* chore: import 순서

---------

Co-authored-by: dmdgpdi <[email protected]>
Co-authored-by: kimminkyeu <[email protected]>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent 9a8521c commit 4279abc
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 2 deletions.
80 changes: 80 additions & 0 deletions frontend/schema/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/shared": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["shareFolderList"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
Expand Down Expand Up @@ -495,7 +511,28 @@ export interface components {
* ] */
idList: number[];
};
"techpick.api.application.sharedFolder.dto.SharedFolderApiRequest$Create": {
name: string;
folderIdList?: number[];
};
//'techpick.api.domain.sharedFolder.dto.FolderNode': {
// folderId: number;
// name: string;
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// folders: any;
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// picks: any;
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// createdAt: any;
//};
"techpick.api.domain.sharedFolder.dto.SharedFolderResult$Create": {
uuid: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
folderNode: any;
//folderNode : components['schemas']['techpick.api.domain.sharedFolder.dto.FolderNode']
};
};

responses: never;
parameters: never;
requestBodies: never;
Expand Down Expand Up @@ -1133,4 +1170,47 @@ export interface operations {
};
};
};
shareFolderList: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": {
/** @description 공유할 폴더 이름 */
name: string;
/**
* @description 공유할 폴더 ID 목록
* @example [0]
*/
folderIdList: number[];
};
};
};
responses: {
/** @description 공유 성공 */
200: {
headers: {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["techpick.api.domain.sharedFolder.dto.SharedFolderResult$Create"];
};
};
/** @description 본인의 폴더만 공유할 수 있습니다. */
403: {
headers: {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["techpick.api.domain.sharedFolder.dto.SharedFolderResult$Create"];
};
};
};
};
}

//더블쿼터, 들여쓰기 4탭 마지막에 저장할 때 변경해서 저장하기
2 changes: 2 additions & 0 deletions frontend/techpick/src/apis/apiConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const API_ENDPOINTS = {
PICKS: 'picks',
TAGS: 'tags',
LINKS: 'links',
SHARED: 'shared',
LOGOUT: 'logout',
};

Expand Down Expand Up @@ -36,5 +37,6 @@ export const API_URLS = {
GET_TAGS: `${API_ENDPOINTS.TAGS}`,
GET_PICK_BY_URL: (url: string) => `${API_ENDPOINTS.PICKS}/link?link=${url}`,
GET_LINK_OG_DATA: (url: string) => `${API_ENDPOINTS.LINKS}?url=${url}`,
SHARE_FOLDER: API_ENDPOINTS.SHARED,
POST_LOGOUT: `${API_ENDPOINTS.LOGOUT}`,
};
23 changes: 23 additions & 0 deletions frontend/techpick/src/apis/folder/shareFolder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { HTTPError } from 'ky';
import { apiClient, returnErrorFromHTTPError } from '@/apis';
import { API_URLS } from '../apiConstants';
import { ShareFolderRequestType, ShareFolderResponseType } from '@/types';

export const shareFolder = async (shareFolderInfo: ShareFolderRequestType) => {
try {
const response = await apiClient.post<ShareFolderResponseType>(
API_URLS.SHARE_FOLDER,
{
json: shareFolderInfo,
}
);
const data = await response.json();
return data;
} catch (httpError) {
if (httpError instanceof HTTPError) {
const error = await returnErrorFromHTTPError(httpError);
throw error;
}
throw httpError;
}
};
5 changes: 5 additions & 0 deletions frontend/techpick/src/app/(unsigned)/share/[uuid]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default function page() {
return <div>shared page</div>;
}
12 changes: 10 additions & 2 deletions frontend/techpick/src/components/FolderTree/FolderContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { PropsWithChildren } from 'react';
import * as ContextMenu from '@radix-ui/react-context-menu';
import * as Dialog from '@radix-ui/react-dialog';
import { FolderPen, FolderX } from 'lucide-react';
import { FolderPen, FolderX, ScreenShare } from 'lucide-react';
import { getPortalContainer } from '@/utils';
import {
contextMenuContentLayout,
Expand All @@ -12,6 +12,7 @@ import {

export function FolderContextMenu({
showRenameInput,
shareFolder,
onShow = () => {},
children,
}: PropsWithChildren<FolderContextMenuProps>) {
Expand All @@ -37,13 +38,19 @@ export function FolderContextMenu({
<FolderPen />
<p>폴더명 변경</p>
</ContextMenu.Item>

<Dialog.Trigger asChild>
<ContextMenu.Item className={contextMenuItemStyle}>
<FolderX />
<p>휴지통으로 이동</p>
</ContextMenu.Item>
</Dialog.Trigger>
<ContextMenu.Item
className={contextMenuItemStyle}
onSelect={shareFolder}
>
<ScreenShare />
<p>공유하기</p>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
Expand All @@ -52,5 +59,6 @@ export function FolderContextMenu({

interface FolderContextMenuProps {
showRenameInput: () => void;
shareFolder: () => void;
onShow?: () => void;
}
17 changes: 17 additions & 0 deletions frontend/techpick/src/components/FolderTree/FolderListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { useState } from 'react';
import type { MouseEvent } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { FolderClosedIcon, FolderOpenIcon } from 'lucide-react';
import { shareFolder } from '@/apis/folder/shareFolder';
import { ROUTES } from '@/constants';
import { useShareDialogOpen } from '@/hooks/useShareDialogOpen';
import { useTreeStore } from '@/stores/dndTreeStore/dndTreeStore';
import { isSelectionActive } from '@/utils';
import { FolderContextMenu } from './FolderContextMenu';
Expand All @@ -15,6 +17,7 @@ import {
isSameParentFolder,
} from './folderListItem.util';
import { MoveFolderToRecycleBinDialog } from './MoveFolderToRecycleBinDialog';
import ShareFolderDialog from './ShareFolderDialog';
import type { FolderMapType } from '@/types';

export const FolderListItem = ({ id, name }: FolderInfoItemProps) => {
Expand All @@ -27,6 +30,8 @@ export const FolderListItem = ({ id, name }: FolderInfoItemProps) => {
updateFolderName,
selectSingleFolder,
} = useTreeStore();
const { isDialogOpen, uuid, handleDialogOpen, handleDialogClose } =
useShareDialogOpen();
const [isUpdate, setIsUpdate] = useState(false);
const isSelected = selectedFolderList.includes(id);
const isHover = id === hoverFolderId;
Expand Down Expand Up @@ -56,6 +61,14 @@ export const FolderListItem = ({ id, name }: FolderInfoItemProps) => {
setIsUpdate(false);
};

const handleShareFolder = async () => {
const response = await shareFolder({
name,
folderIdList: [id],
});
handleDialogOpen(response.uuid);
};

if (isUpdate) {
return (
<FolderInput
Expand All @@ -74,6 +87,7 @@ export const FolderListItem = ({ id, name }: FolderInfoItemProps) => {
showRenameInput={() => {
setIsUpdate(true);
}}
shareFolder={handleShareFolder}
onShow={() => {
selectSingleFolder(id);
}}
Expand All @@ -87,6 +101,9 @@ export const FolderListItem = ({ id, name }: FolderInfoItemProps) => {
onClick={(event) => handleClick(id, event)}
/>
</FolderContextMenu>
{isDialogOpen && (
<ShareFolderDialog onClose={handleDialogClose} uuid={uuid} />
)}
<MoveFolderToRecycleBinDialog deleteFolderId={id} />
</Dialog.Root>
);
Expand Down
86 changes: 86 additions & 0 deletions frontend/techpick/src/components/FolderTree/ShareFolderDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useState } from 'react';
import Link from 'next/link';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Settings } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/ui/Popover/Popover';
import { handleShareFolderLinkCopy } from '@/utils/handleShareFolderLinkCopy';
import * as styles from './shareFolderDialog.css';

export default function ShareFolderDialog({
uuid,
onClose,
}: ShareFolderDialogProps) {
const [showPopover, setshowPopover] = useState<boolean>(false);
const handleShowPopver = () => {
setshowPopover(true);
setTimeout(() => setshowPopover(false), 2000);
};
const shareFolderLink = `${window.location.origin}/share/${uuid}`;

return (
<DialogPrimitive.Root open={true}>
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay
className={styles.dialogOverlay}
onClick={onClose}
/>
<DialogPrimitive.Content className={styles.dialogContent}>
<DialogPrimitive.Title className={styles.dialogTitle}>
폴더가 공유되었습니다.
</DialogPrimitive.Title>
<DialogPrimitive.Description className={styles.dialogDescription}>
<Link href={`/share/${uuid}`} className={styles.myLinkPageLinkText}>
<span className={styles.linkContent}>
<Settings className={styles.icon} />
내설정
</span>
</Link>
에서 공유를 취소할 수 있습니다.
</DialogPrimitive.Description>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
{/**
* @description: 이벤트 버블링으로 인해 드래그시 폴더가 이동하면서 다이얼로그가 닫히는 현상을 방지하기 위해
* onMouseDown 이벤트에 event.stopPropagation()을 추가
*/}
<div
className={styles.sharedFolderLink}
onMouseDown={(event) => event.stopPropagation()}
id="shared-folder-link"
title={shareFolderLink}
>
{shareFolderLink}
</div>
<Popover open={showPopover}>
<PopoverTrigger asChild>
<button
className={styles.copyButton}
onClick={() => handleShareFolderLinkCopy(handleShowPopver)}
>
Copy
</button>
</PopoverTrigger>
<PopoverContent className={styles.popoverStyle}>
Copied
</PopoverContent>
</Popover>
</div>
<DialogPrimitive.Close className={styles.closeIcon} onClick={onClose}>
×
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
);
}

interface ShareFolderDialogProps {
uuid: string;
onClose: () => void;
}
Loading

0 comments on commit 4279abc

Please sign in to comment.