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

✨ 피드 신고 UI #57

Merged
merged 29 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
185d9c2
feat: 피드 케밥 메뉴 아이콘 분리
BangDori May 13, 2024
b3fe295
feat: 케밥 메뉴 상호작용 기능 연결
BangDori May 13, 2024
dfc2b4d
feat: 피드 케밥 메뉴 UI
BangDori May 13, 2024
468f400
feat: useToggle 공용 훅 추가
BangDori May 13, 2024
4ff328a
style: useToggle 주석 추가
BangDori May 13, 2024
4b85e69
feat: useToggle 훅 적용 및 신고하기 UI
BangDori May 13, 2024
cdb09d4
feat: ConfirmReportModal 위치 이동
BangDori May 13, 2024
0078000
feat: 피드 신고 양식 추가
BangDori May 13, 2024
b76103a
feat: ConfirmReportModal 스타일 적용
BangDori May 13, 2024
a79da57
feat: checkoff, checkon 아이콘 추가
BangDori May 13, 2024
5331df7
style: 피드 신고 모달창 너비값 수정
BangDori May 13, 2024
c421094
feat: 신고 아이템 리스트 추가
BangDori May 13, 2024
e4384e1
feat: 신고 아이템 체크박스 기능
BangDori May 13, 2024
618181c
feat: 신고 리스트 스타일 적용
BangDori May 13, 2024
c8bbd53
feat: textarea 전역 설정 추가
BangDori May 13, 2024
ba47bcf
feat: 신고 사유 입력창
BangDori May 14, 2024
3b01b35
feat: textarea 최대 길이 설정
BangDori May 14, 2024
1880f79
feat: checkbox 아이콘 추가 및 분리
BangDori May 14, 2024
1cac1f4
feat: 체크박스 숨기기 UI
BangDori May 14, 2024
ee90d3e
feat: isHiddenPost -> isBlind 상태명 수정
BangDori May 14, 2024
51afa06
feat: content.length 제거거
BangDori May 14, 2024
67c202c
refactor: useInput 훅스 적용
BangDori May 14, 2024
a16e12e
feat: useToggle 훅스 as const 적용
BangDori May 14, 2024
2f0ddb3
feat: 신고 사유 리스트 및 훅스 네이밍 수정
BangDori May 14, 2024
5224e2b
feat: 신고 카테고리 관리 훅스 타입 명시 및 유틸리티 함수 추가
BangDori May 14, 2024
a3ee67a
refactor: 피드 신고 양식 리팩토링
BangDori May 14, 2024
37bce5e
feat: CategoryId 타입 수정
BangDori May 14, 2024
6604d20
feat: ConfirmReportModal children 타입 수정
BangDori May 14, 2024
a048471
feat: useCallback 제거
BangDori May 14, 2024
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
64 changes: 64 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.
3 changes: 2 additions & 1 deletion src/app/styles/_reset.scss
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ summary,
time,
mark,
audio,
video {
video,
textarea {
margin: 0;
padding: 0;
border: 0;
Expand Down
1 change: 1 addition & 0 deletions src/features/feed-reports/consts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './reports';
18 changes: 18 additions & 0 deletions src/features/feed-reports/consts/reports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type ReportCategoryId = 1 | 2 | 3 | 4 | 5 | 6 | 7;

interface ReportCategory {
id: ReportCategoryId;
name: string;
}

export const REPORT_CATEOGRIES: ReportCategory[] = [
{ id: 1, name: '상업적/홍보성' },
{ id: 2, name: '음란/선정성' },
{ id: 3, name: '저작권 침해' },
{ id: 4, name: '개인정보 노출' },
{ id: 5, name: '욕설/인신공격' },
{ id: 6, name: '반복적인 내용' },
{ id: 7, name: '기타' },
];

export const MAX_REPORT_CONTENT_LENGTH = 100;
1 change: 1 addition & 0 deletions src/features/feed-reports/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FeedReportsForm } from './ui';
1 change: 1 addition & 0 deletions src/features/feed-reports/model/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useReportCategories';
26 changes: 26 additions & 0 deletions src/features/feed-reports/model/useReportCategories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useState } from 'react';

import { REPORT_CATEOGRIES, ReportCategoryId } from '../consts';

export const useReportCategories = () => {
const [categories, setCategories] = useState(
new Map<ReportCategoryId, boolean>(
REPORT_CATEOGRIES.map((item) => [item.id, false]),
),
BangDori marked this conversation as resolved.
Show resolved Hide resolved
);

const handleClickCategory = (id: ReportCategoryId) => {
setCategories((prev) => {
const newCheckedItem = new Map(prev);
newCheckedItem.set(id, !newCheckedItem.get(id));
return newCheckedItem;
});
};

return { categories, handleClickCategory };
};

export function getCategoryName(id: ReportCategoryId) {
const category = REPORT_CATEOGRIES.find((item) => item.id === id);
return category?.name ?? '';
}
21 changes: 21 additions & 0 deletions src/features/feed-reports/ui/ConfirmReportModal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.confirm-report-modal {
display: flex;
flex-direction: column;

border-radius: 8px;
border: none;
background-color: white;

width: 244px;
padding: 18px;

.title {
align-self: start;
}

.modal-btn-container {
display: flex;
justify-content: center;
gap: 8px;
}
}
39 changes: 39 additions & 0 deletions src/features/feed-reports/ui/ConfirmReportModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ActiveButton, BasicButton } from '@/shared/ui';
import { ModalOverlay } from '@/shared/ui/modal/ModalOverlay';

import './ConfirmReportModal.scss';

interface ConfirmReportModalProps {
onExecute: () => void;
onExecuteIsDisabled: boolean;
onClose: () => void;
children: JSX.Element;
}

export const ConfirmReportModal: React.FC<ConfirmReportModalProps> = ({
onExecute,
onExecuteIsDisabled,
onClose,
children,
}) => {
return (
<ModalOverlay styleClass='modal' onClose={onClose}>
<form className='confirm-report-modal'>
<h3 className='title h3semi'>신고하기</h3>
{children}
<div className='modal-btn-container'>
<BasicButton onClick={onClose} styleClass='confirm-cancle h4semi'>
취소
</BasicButton>
<ActiveButton
onClick={onExecute}
isDisabled={onExecuteIsDisabled}
styleClass='confirm h4semi'
>
신고하기
</ActiveButton>
</div>
</form>
</ModalOverlay>
);
};
68 changes: 68 additions & 0 deletions src/features/feed-reports/ui/FeedReportsForm.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
.reports-list {
margin-top: 16px;

display: grid;
grid-template-columns: repeat(2, 1fr);

row-gap: 10px;

.report-item {
display: flex;
align-items: center;

gap: 4px;

.checkbox-btn {
width: 20px;
height: 20px;
}

.item-name {
color: $gray5;
}
}
}

.report-textarea-container {
margin-top: 16px;
position: relative;

width: 244px;
height: 72px;

.report-textarea {
padding: 10px;

width: calc(100% - 20px);
height: calc(100% - 20px);

background-color: $gray1;

resize: none;

&:focus {
outline: none;
}
}

.textarea-text-count {
position: absolute;

right: 10px;
bottom: 8px;

color: $gray3;
}
}

.hide-checkbox-container {
margin: 12px 0 20px;

display: flex;
align-items: center;
gap: 4px;

.hide-checkbox-text {
color: $gray5;
}
}
76 changes: 76 additions & 0 deletions src/features/feed-reports/ui/FeedReportsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useInput, useToggle } from '@/shared/hooks';
import { Icon } from '@/shared/ui';

import { MAX_REPORT_CONTENT_LENGTH } from '../consts';
import { useReportCategories, getCategoryName } from '../model';

import { ConfirmReportModal } from './ConfirmReportModal';
import './FeedReportsForm.scss';

interface FeedReportsFormProps {
onClose: () => void;
}

export const FeedReportsForm: React.FC<FeedReportsFormProps> = ({
onClose,
}) => {
const { categories, handleClickCategory } = useReportCategories();
const [content, handleInputContent] = useInput();
const [isBlind, toggleBlind] = useToggle(false);

return (
<ConfirmReportModal
onExecute={() => {}} // API 연동 후 수정
onExecuteIsDisabled={false} // API 연동 후 수정
onClose={onClose}
>
<>
Legitgoons marked this conversation as resolved.
Show resolved Hide resolved
{/* 신고 카테고리 */}
<ul className='reports-list'>
{[...categories].map(([id, checked]) => (
<li key={id} className='report-item'>
<button
className='checkbox-btn'
type='button'
onClick={() => handleClickCategory(id)}
>
<Icon
name={checked ? 'checkbox-circle_on' : 'checkbox-circle_off'}
width='20'
height='20'
/>
</button>
<p className='item-name b1md'>{getCategoryName(id)}</p>
</li>
))}
</ul>

{/* 신고 사유 */}
<div className='report-textarea-container'>
<textarea
className='report-textarea b1md'
spellCheck={false}
value={content}
onChange={handleInputContent}
maxLength={MAX_REPORT_CONTENT_LENGTH}
/>
<span className='textarea-text-count b2md'>
{content.length}/{MAX_REPORT_CONTENT_LENGTH}
</span>
</div>

{/* 숨김 처리 체크박스 */}
<div className='hide-checkbox-container'>
<button className='checkbox-btn' type='button' onClick={toggleBlind}>
<Icon
name={isBlind ? 'checkbox-square_on' : 'checkbox-square_off'}
width='20'
height='20'
/>
</button>
<p className='hide-checkbox-text b1md'>해당 게시물 숨기기</p>
</div>
</>
</ConfirmReportModal>
);
};
1 change: 1 addition & 0 deletions src/features/feed-reports/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FeedReportsForm } from './FeedReportsForm';
2 changes: 2 additions & 0 deletions src/shared/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useToggle } from './useToggle';
export { useInput } from './useInput';
19 changes: 19 additions & 0 deletions src/shared/hooks/useInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useCallback, useState } from 'react';

/**
* useInput 훅은 input 태그의 value와 onChange 이벤트를 처리하는 함수를 반환하는 커스텀 훅입니다.
* @param defaultValue 초기값
* @returns [상태값, 이벤트 핸들러, 상태 설정 함수]
*/
export const useInput = (defaultValue: string = '') => {
const [value, setValue] = useState(defaultValue);

const handleInputContent = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
setValue(e.target.value);
},
[],
);

return [value, handleInputContent, setValue] as const; // 자리 고정
};
17 changes: 17 additions & 0 deletions src/shared/hooks/useToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useCallback, useState } from 'react';

/**
* reference: https://usehooks-ts.com/react-hook/use-toggle
* useToggle 훅은 boolean 값과 값을 토글할 수 있는 함수를 반환하는 커스텀 훅입니다.
* @param defaultValue 초기값
* @returns [상태값, 토글 함수, 상태 설정 함수]
*/
export function useToggle(defaultValue?: boolean) {
const [value, setValue] = useState(!!defaultValue);

const toggle = useCallback(() => {
setValue((x) => !x);
}, []);

return [value, toggle, setValue] as const;
}
6 changes: 5 additions & 1 deletion src/shared/ui/icon/consts/sprite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ export type IconName =
| 'chat'
| 'search'
| 'caution'
| 'no-profile';
| 'no-profile'
| 'checkbox-circle_off'
| 'checkbox-circle_on'
| 'checkbox-square_on'
| 'checkbox-square_off';
Loading
Loading