Skip to content
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

[Feature/BAR-155] 참고하는 레이아웃 수정 및 폴더 저장 삭제 기능 추가 #63

Merged
merged 6 commits into from
Feb 16, 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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@svgr/webpack": "^8.1.0",
"@tanstack/react-query": "^5.17.9",
"@types/js-cookie": "^3.0.6",
"@types/react-responsive-masonry": "^2.1.3",
"@vanilla-extract/css": "^1.14.0",
"@vanilla-extract/dynamic": "^2.1.0",
"@vanilla-extract/recipes": "^0.5.1",
Expand All @@ -37,6 +38,7 @@
"react": "^18",
"react-dom": "^18",
"react-notion-x": "^6.16.0",
"react-responsive-masonry": "^2.1.7",
"zustand": "^4.4.7"
},
"devDependencies": {
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/components/Card/style.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const header = style([
export const body = style([
sprinkles({ typography: '15/Body/Regular' }),
{
whiteSpace: 'pre-wrap',
wordBreak: 'keep-all',
},
]);
Expand Down
20 changes: 20 additions & 0 deletions src/components/Responsive/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { type PropsWithChildren } from 'react';
import Masonry, { ResponsiveMasonry } from 'react-responsive-masonry';

interface ResponsiveProps {
className?: string;
}
const Responsive = ({
children,
className,
}: PropsWithChildren<ResponsiveProps>) => {
return (
<ResponsiveMasonry columnsCountBreakPoints={{ 768: 2, 1080: 3 }}>
<Masonry className={className} gutter="16px">
{children}
</Masonry>
</ResponsiveMasonry>
);
};

export default Responsive;
6 changes: 6 additions & 0 deletions src/domain/참고하는/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ export const postSaveTemplate = ({
memoFolderId,
}: PostSaveTemplateParams) =>
http.post(`/templates/${templateId}/archive`, { memoFolderId });

export const deleteTemplate = (templateId: number) =>
http.delete(`/templates/${templateId}/archive`);

export const patchTemplateCopyCount = (templateId: number) =>
http.patch(`/templates/${templateId}/copy`);
7 changes: 7 additions & 0 deletions src/domain/참고하는/components/FolderDialog.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@ export const hover = style({
fill: COLORS['Grey/600'],
},
});

export const hoverBlue = style({
transition: 'fill 100ms ease-in-out',
':hover': {
fill: COLORS['Blue/Default'],
},
});
33 changes: 28 additions & 5 deletions src/domain/참고하는/components/FolderDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
import { type Folder } from '@api/memoFolder/types';
import Button from '@components/Button';
import Dropdown from '@components/Dropdown';
import Icon from '@components/Icon';
import * as styles from '@domain/참고하는/components/FolderDialog.css';
import { useModalStore } from '@stores/modal';
import { COLORS } from '@styles/tokens';

import useDeleteTemplate from '../queries/useDeleteTemplate';
import useSaveTemplate from '../queries/useSaveTemplate';
import { type ReferContent } from '../types';

interface FolderDialogProps {
templateId: number;
interface FolderDialogProps
extends Pick<ReferContent, 'templateId' | 'isArchived'> {
memoFolders: Folder[];
}

const FolderDialog = ({ templateId, memoFolders }: FolderDialogProps) => {
const FolderDialog = ({
templateId,
isArchived,
memoFolders,
}: FolderDialogProps) => {
const { openModal } = useModalStore();
const { mutateAsync } = useSaveTemplate();
const { mutateAsync: saveTemplate } = useSaveTemplate();
const { mutateAsync: deleteTemplate } = useDeleteTemplate();

const handleFolderClick = (memoFolderId: number) => async () =>
await mutateAsync({ templateId, memoFolderId });
await saveTemplate({ templateId, memoFolderId });

const handleDeleteTemplateClick = async () => {
await deleteTemplate(templateId);
};

if (isArchived)
return (
<Button onClick={handleDeleteTemplateClick}>
<Icon
icon="bookmarkRefer"
className={styles.hoverBlue}
color={COLORS['Grey/300']}
/>
</Button>
);

return (
<Dropdown size="medium" placement="bottom-center">
Expand Down
15 changes: 9 additions & 6 deletions src/domain/참고하는/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from 'react';

import Responsive from '@components/Responsive';
import FilterButtons from '@domain/참고하는/components/FilterButtons';
import FilterHeader from '@domain/참고하는/components/FilterHeader';
import * as styles from '@domain/참고하는/components/ReferTab.css';
Expand All @@ -17,7 +18,7 @@ const 참고하는TabContent = () => {
const [selectedFilterButton, setSelectedFilterButton] =
useState<FilterButton>('new');

const { data } = useTemplate({
const { data: templates } = useTemplate({
category: selectedFilterHeader,
sort: selectedFilterButton,
});
Expand All @@ -28,6 +29,8 @@ const 참고하는TabContent = () => {
const handleFilterButtonSelect = (type: FilterButton) => () =>
setSelectedFilterButton(type);

if (!templates) return null;

return (
<div className={styles.referPageTabWrapper}>
<FilterHeader
Expand All @@ -38,15 +41,15 @@ const 참고하는TabContent = () => {
selectedFilterButton={selectedFilterButton}
handleFilterButtonSelect={handleFilterButtonSelect}
/>
<ul className={styles.referCardsWrapper}>
{data?.content.map((data) => (
<Responsive className={styles.referCardsWrapper}>
{templates?.content.map((template) => (
<참고하는TemplateCard
key={data.templateId}
data={data}
key={template.templateId}
data={template}
memoFolders={memoFoldersData}
/>
))}
</ul>
</Responsive>
</div>
);
};
Expand Down
32 changes: 25 additions & 7 deletions src/domain/참고하는/components/참고하는TemplateCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useQueryClient } from '@tanstack/react-query';

import { type Folder } from '@api/memoFolder/types';
import Badge from '@components/Badge';
import Button from '@components/Button';
Expand All @@ -6,15 +8,16 @@ import Icon from '@components/Icon';
import { CATEGORY_COLOR } from '@constants/config';
import * as styles from '@domain/참고하는/components/참고하는TemplateCard.css';
import { CATEGORY } from '@domain/참고하는/models';
import { type Refer } from '@domain/참고하는/types';
import { type ReferContent } from '@domain/참고하는/types';
import { getNumToK } from '@domain/참고하는/utils';
import { useToastStore } from '@stores/toast';
import { COLORS } from '@styles/tokens';

import useCopyTemplate from '../queries/useCopyTemplate';
import FolderDialog from './FolderDialog';

interface 참고하는TemplateCardProps {
data: Refer;
data: ReferContent;
memoFolders: Folder[];
}

Expand All @@ -29,16 +32,27 @@ const 참고하는TemplateCard = ({
content,
copiedCount,
savedCount,
isArchived,
} = data;

const { showToast } = useToastStore();
const queryClient = useQueryClient();
const { mutateAsync: copyTemplate } = useCopyTemplate();

const categoryNameKr = CATEGORY[category];

const handleCopyClick = () => {
navigator.clipboard.writeText(content);
showToast({
message: '글이 복사되었어요. 원하는 곳에 붙여넣기(Ctrl+V)를 해주세요!',
const handleCopyClick = async () => {
await copyTemplate(templateId, {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['templates'],
}),
navigator.clipboard.writeText(content);
showToast({
message:
'글이 복사되었어요. 원하는 곳에 붙여넣기(Ctrl+V)를 해주세요!',
});
},
});
};

Expand All @@ -52,7 +66,11 @@ const 참고하는TemplateCard = ({
color={COLORS['Grey/300']}
/>
</Button>
<FolderDialog templateId={templateId} memoFolders={memoFolders} />
<FolderDialog
templateId={templateId}
isArchived={isArchived}
memoFolders={memoFolders}
/>
</Card.Menu>
<Card.Header>
<Badge color={CATEGORY_COLOR[categoryNameKr]}>{categoryNameKr}</Badge>
Expand Down
10 changes: 10 additions & 0 deletions src/domain/참고하는/queries/useCopyTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useMutation } from '@tanstack/react-query';

import { patchTemplateCopyCount } from '../api';

const useCopyTemplate = () =>
useMutation({
mutationFn: patchTemplateCopyCount,
});

export default useCopyTemplate;
17 changes: 17 additions & 0 deletions src/domain/참고하는/queries/useDeleteTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { deleteTemplate } from '../api';

const useDeleteTemplate = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: deleteTemplate,
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: ['templates'],
}),
});
};

export default useDeleteTemplate;
19 changes: 16 additions & 3 deletions src/domain/참고하는/queries/useSaveTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { useToastStore } from '@stores/toast';

import { postSaveTemplate } from '../api';

const useSaveTemplate = () =>
useMutation({
const useSaveTemplate = () => {
const queryClient = useQueryClient();

const { showToast } = useToastStore();

return useMutation({
mutationFn: postSaveTemplate,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['templates'],
});
showToast({ message: '글이 저장됐어요.' });
},
});
};

export default useSaveTemplate;
14 changes: 3 additions & 11 deletions src/domain/참고하는/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,18 @@ import { type CATEGORY, type FILTER_BUTTONS } from '@domain/참고하는/models'

export type Category = keyof typeof CATEGORY;

export interface Refer {
export interface ReferContent {
templateId: number;
category: Category;
subCategory: string;
content: string;
savedCount: number;
copiedCount: number;
isArchived: boolean;
}

export type FilterButton = keyof typeof FILTER_BUTTONS;

export interface Content {
templateId: number;
category: Category;
subCategory: string;
content: string;
savedCount: number;
copiedCount: number;
}

export interface Templates {
content: Content[];
content: ReferContent[];
}
2 changes: 1 addition & 1 deletion src/styles/sprinkles.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const width = {
const responsiveProperties = defineProperties({
conditions: {
small: { '@media': 'screen and (min-width: 768px)' },
middle: { '@media': 'screen and (min-width: 1024px)' },
middle: { '@media': 'screen and (min-width: 1080px)' },
large: { '@media': 'screen and (min-width: 1440px)' },
},
defaultCondition: 'large',
Expand Down