Skip to content

Commit

Permalink
✨ 피드 신고 UI 반영 (#70)
Browse files Browse the repository at this point in the history
* feat: siren 아이콘 추가
* feat: 숨김 관리 저장소 Map 타입 수정
* feat: 신고 후 피드 숨기기 기능
* feat: 숨김 취소 버튼 padding 으로 스타일 수정
* feat: 신고 완료 후 토스트 메시지 렌더링
* feat: 피드 숨기기 및 취소 기능 동기식 처리
* feat: 피드 숨기기 취소 API 동기식 처리
* feat: 숨김 처리 onMutate에서 처리
* feat: 신고하기 체크박스 체크되어 있지 않은 상태로 반영
* feat: 피드 신고 중 에러 발생 시 에러 토스트 메시지 표시
* feat: useReportCategories -> useReportForm
* feat: 신고 양식 유효성 검사 모델에서 진행
* feat: 신고하기 양식 body 생성 함수 생성
* feat: 피드 신고 양식 타입 분리
* feat: 피드 신고 실패 시 기존 상태 유지, 성공 시 상태 제거
* feat: 피드 신고 실패 시, 기존 상태 복구

Closes #65
  • Loading branch information
BangDori authored May 18, 2024
1 parent 7f229af commit e53c16c
Show file tree
Hide file tree
Showing 21 changed files with 251 additions and 76 deletions.
19 changes: 19 additions & 0 deletions public/assets/sprites/common.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 33 additions & 9 deletions src/entitites/feed/hide-store.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

const HIDDEN = true;
const VISIBLE = false;
export type HiddenType = 'hidden' | 'siren';

const HiddenMessage = {
hidden: {
reasonMsg: '게시물이 숨겨졌어요',
cancleMsg: '취소',
},
siren: {
reasonMsg: '신고가 접수되었어요',
cancleMsg: '게시물 보기',
},
};

interface HiddenFeedState {
hiddenFeeds: Map<number, boolean>;
hiddenFeeds: Map<number, HiddenType>;
}

export const useHiddenFeedStore = create<HiddenFeedState>()(
devtools(
(): HiddenFeedState => ({
hiddenFeeds: new Map<number, boolean>(),
hiddenFeeds: new Map<number, HiddenType>(),
}),
{ name: 'feed-hidden-store' },
),
Expand All @@ -20,11 +30,12 @@ export const useHiddenFeedStore = create<HiddenFeedState>()(
/**
* 숨김 피드 목록에 피드를 추가합니다.
* @param feedId 피드 아이디
* @param type 숨김 타입 (hidden, siren)
*/
export function addHiddenFeed(feedId: number) {
export function addHiddenFeed(feedId: number, type: HiddenType) {
useHiddenFeedStore.setState(
({ hiddenFeeds: prevHiddenFeeds }) => ({
hiddenFeeds: new Map(prevHiddenFeeds).set(feedId, HIDDEN),
hiddenFeeds: new Map(prevHiddenFeeds).set(feedId, type),
}),
false,
'feed/addHiddenFeed',
Expand All @@ -37,10 +48,23 @@ export function addHiddenFeed(feedId: number) {
*/
export function cancleHiddenFeed(feedId: number) {
useHiddenFeedStore.setState(
({ hiddenFeeds: prevHiddenFeeds }) => ({
hiddenFeeds: new Map(prevHiddenFeeds).set(feedId, VISIBLE),
}),
({ hiddenFeeds: prevHiddenFeeds }) => {
prevHiddenFeeds.delete(feedId);

return {
hiddenFeeds: new Map(prevHiddenFeeds),
};
},
false,
'feed/cancleHiddenFeed',
);
}

/**
* 숨김 메시지를 반환합니다.
* @param {hidden | siren} type 숨김 타입
* @returns 숨김 사유 메시지, 숨김 해제 메시지
*/
export function getHiddenMessageByType(type: HiddenType) {
return HiddenMessage[type];
}
4 changes: 2 additions & 2 deletions src/features/feed-hides/api/useHideCancle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ async function requestHideCancelFeed(feedId: number) {
}

export const useHideCancel = (feedId: number) => {
const { mutateAsync: hideCancelFeed, isPending } = useMutation({
const { mutate: hideCancelFeed, isPending } = useMutation({
mutationFn: () => requestHideCancelFeed(feedId),
onSuccess: () => cancleHiddenFeed(feedId),
onMutate: () => cancleHiddenFeed(feedId),
});

return { hideCancelFeed, isPending };
Expand Down
6 changes: 3 additions & 3 deletions src/features/feed-hides/api/useHides.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ async function requestHideFeed(feedId: number) {
}

export const useHides = (feedId: number) => {
const { mutateAsync: hideFeedAsync, isPending } = useMutation({
const { mutate: hideFeed, isPending } = useMutation({
mutationFn: () => requestHideFeed(feedId),
onSuccess: () => addHiddenFeed(feedId),
onMutate: () => addHiddenFeed(feedId, 'hidden'),
});

return { hideFeedAsync, isPending };
return { hideFeed, isPending };
};
3 changes: 1 addition & 2 deletions src/features/feed-hides/ui/HiddenFeed.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@

.hidden-cancel-btn {
margin-top: 16px;
width: 39px;
height: 24px;
padding: 5px 10px;

border-radius: 30px;

Expand Down
10 changes: 6 additions & 4 deletions src/features/feed-hides/ui/HiddenFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { HiddenType, getHiddenMessageByType } from '@/entitites/feed';
import { Icon } from '@/shared/ui';

import './HiddenFeed.scss';
import { useHideCancel } from '../api';

interface HiddenFeedProps {
feedId: number;
message: string;
type: HiddenType;
}

export const HiddenFeed: React.FC<HiddenFeedProps> = ({ feedId, message }) => {
export const HiddenFeed: React.FC<HiddenFeedProps> = ({ feedId, type }) => {
const { hideCancelFeed, isPending } = useHideCancel(feedId);
const { reasonMsg, cancleMsg } = getHiddenMessageByType(type);

return (
<div className='feed-hidden-wrapper'>
<div className='feed-hidden-container'>
<Icon name='check_mint' width='24' height='24' />
<p className='hidden-reason-msg b2md'>{message}</p>
<p className='hidden-reason-msg b2md'>{reasonMsg}</p>
<button
className='hidden-cancel-btn b2md'
onClick={() => hideCancelFeed()}
disabled={isPending}
>
취소
{cancleMsg}
</button>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/features/feed-hides/ui/HideButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ interface HideButtonProps {
}

export const HideButton: React.FC<HideButtonProps> = ({ feedId, onClose }) => {
const { hideFeedAsync, isPending } = useHides(feedId);
const { hideFeed, isPending } = useHides(feedId);

const handleClickHideBtn = async () => {
await hideFeedAsync();
const handleClickHideBtn = () => {
hideFeed();
onClose();
};

Expand Down
30 changes: 19 additions & 11 deletions src/features/feed-reports/api/useSubmitReports.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import { useMutation } from '@tanstack/react-query';

import { addHiddenFeed } from '@/entitites/feed';
import { axiosInstance } from '@/shared/axios';

interface ReportBody {
category: string;
content: string;
isBlind: boolean;
}
import { FeedReportForm } from '../consts';
import { removeFeedReportForm, saveFeedReportForm } from '../store';

async function requestFeedReports(feedId: number, body: ReportBody) {
async function requestFeedReports(feedId: number, body: FeedReportForm) {
const { data } = await axiosInstance.post(`feeds/${feedId}/reports`, body);

return data;
}

export const useSubmitReports = (feedId: number) => {
const { mutateAsync: reportFeedAsync, isPending } = useMutation({
mutationFn: (body: ReportBody) => requestFeedReports(feedId, body),
onError: () => {},
onSuccess: () => {},
const { mutate: reportFeed, isPending } = useMutation({
mutationKey: ['feed-report'],
mutationFn: (body: FeedReportForm) => requestFeedReports(feedId, body),
onError: (_, body) => saveFeedReportForm(feedId, body),
onSuccess: (_, body) => {
const { isBlind } = body;

// 숨김 처리
if (isBlind) {
addHiddenFeed(feedId, 'siren');
}

removeFeedReportForm(feedId);
},
});

return { reportFeedAsync, isPending };
return { reportFeed, isPending };
};
1 change: 1 addition & 0 deletions src/features/feed-reports/consts/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './reports';
export * from './type';
2 changes: 1 addition & 1 deletion src/features/feed-reports/consts/reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export const REPORT_CATEGORIES = [
];

export const MAX_REPORT_CONTENT_LENGTH = 60;
export const DEFAULT_CLICKED_ID = 0;
export const UNCLICKED_STATUS_ID = -1;
5 changes: 5 additions & 0 deletions src/features/feed-reports/consts/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface FeedReportForm {
category: string;
content: string;
isBlind: boolean;
}
2 changes: 1 addition & 1 deletion src/features/feed-reports/model/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './useReportCategories';
export * from './useReportForm';
10 changes: 0 additions & 10 deletions src/features/feed-reports/model/useReportCategories.tsx

This file was deleted.

39 changes: 39 additions & 0 deletions src/features/feed-reports/model/useReportForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useState } from 'react';

import { useInput, useToggle } from '@/shared/hooks';

import { REPORT_CATEGORIES, UNCLICKED_STATUS_ID } from '../consts';
import { getFeedReportForm } from '../store/report-store';

export const useReportForm = (feedId: number) => {
const {
clickedId: prevClickedId,
content: prevContent,
isBlind: prevIsBlind,
} = getFeedReportForm(feedId);

const [clickedId, setClickedId] = useState<number>(prevClickedId);
const [content, handleInputContent] = useInput(prevContent);
const [isBlind, toggleBlind] = useToggle(prevIsBlind);

const isValidReportForm = clickedId !== UNCLICKED_STATUS_ID;
const isDisabledReportForm = !isValidReportForm;

const handleClickCategory = (id: number) => setClickedId(id);
const createReportBody = () => ({
category: REPORT_CATEGORIES[clickedId],
content,
isBlind,
});

return {
clickedId,
content,
isBlind,
isDisabledReportForm,
handleClickCategory,
handleInputContent,
toggleBlind,
createReportBody,
};
};
1 change: 1 addition & 0 deletions src/features/feed-reports/store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './report-store';
71 changes: 71 additions & 0 deletions src/features/feed-reports/store/report-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

import { FeedReportForm, REPORT_CATEGORIES } from '../consts';

interface FeedReportFailState {
[feedId: number]: FeedReportForm;
}

/**
* 피드 신고 실패시, 사용자가 작성한 정보에 대한 상태를 관리하는 스토어입니다.
*/
export const useFeedReportFailStore = create<FeedReportFailState>()(
devtools((): FeedReportFailState => ({}), { name: 'feed-report-store' }),
);

/**
* 피드 신고 실패시, 사용자가 작성한 정보를 저장합니다.
* @param feedId 피드 아이디
*/
export function saveFeedReportForm(feedId: number, body: FeedReportForm) {
useFeedReportFailStore.setState(
(prev) => ({
...prev,
[feedId]: body,
}),
false,
'feed/saveFeedReportForm',
);
}

/**
* 피드 신고 성공시, 사용자가 작성한 정보를 삭제합니다.
* @param feedId 피드 아이디
*/
export function removeFeedReportForm(feedId: number) {
if (!useFeedReportFailStore.getState()[feedId]) return;

useFeedReportFailStore.setState(
(prev) => {
const nextState = { ...prev };
delete nextState[feedId];
return nextState;
},
false,
'feed/removeFeedReportForm',
);
}

/**
* 사용자가 이전에 작성한 피드 신고 정보를 가져옵니다.
* @if 만약 없다면, 초기 상태를 반환합니다.
* @param feedId 피드 아이디
* @returns 피드 신고 양식
*/
export function getFeedReportForm(feedId: number) {
const body = useFeedReportFailStore.getState()[feedId];

if (!body) {
return {
clickedId: -1,
content: '',
isBlind: false,
};
}

return {
clickedId: REPORT_CATEGORIES.indexOf(body.category),
...body,
};
}
Loading

0 comments on commit e53c16c

Please sign in to comment.