Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f42bb4a
✨ feat: SVG 컴포넌트 width, height, fill 동적으로 설정 가능하도록 수정
10hajin15 Dec 31, 2024
7f42e99
🚚 chore: react-md-editor 패키지 설치
10hajin15 Dec 31, 2024
dc58c88
✨ feat: 새로운 글 작성 경로 /community/new 설정
10hajin15 Dec 31, 2024
c861f32
💄 style: react-md-editor 스타일 수정
10hajin15 Dec 31, 2024
d289eb8
✨ feat: BoardDropdown 컴포넌트 구현
10hajin15 Dec 31, 2024
bf9a18b
✨ feat: 커뮤니티 새로운 글 작성 페이지 구현
10hajin15 Dec 31, 2024
0518a5b
🐛 fix: 제목 입력 칸 세로로 중앙 정렬되도록 수정!
10hajin15 Dec 31, 2024
9495443
채용공고 작성 페이지(1) (#29)
junhakjh Jan 1, 2025
46eac53
🚚 chore: react-md-editor 패키지 설치
10hajin15 Dec 31, 2024
60e3269
✨ feat: 새로운 글 작성 경로 /community/new 설정
10hajin15 Dec 31, 2024
663fe11
💄 style: react-md-editor 스타일 수정
10hajin15 Dec 31, 2024
da94c4d
✨ feat: BoardDropdown 컴포넌트 구현
10hajin15 Dec 31, 2024
adfe56e
✨ feat: 커뮤니티 새로운 글 작성 페이지 구현
10hajin15 Dec 31, 2024
a76f4e8
🐛 fix: 제목 입력 칸 세로로 중앙 정렬되도록 수정!
10hajin15 Dec 31, 2024
64969ba
Merge branch 'feature/NewPost' of https://github.com/CheeUp/CheeUp-FE…
10hajin15 Jan 2, 2025
d004ab4
♻️ refactor: ArrowIcon 타입 수정
10hajin15 Jan 2, 2025
ab05816
♻️ refactor: 커뮤니티 새 글 등록 경로 수정
10hajin15 Jan 2, 2025
1239a21
♻️ refactor: ArrowIcon props 수정
10hajin15 Jan 2, 2025
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@uiw/react-md-editor": "^4.0.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
Expand Down
8 changes: 7 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ import RecruitPage from '@/components/pages/recruit';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import PortfolioPage from '@/components/pages/portfolio';
import NewPortfolioPage from '@/components/pages/portfolio/new';
import NewPostPage from '@/components/pages/community/new';
import NewRecruitPage from '@/components/pages/recruit/new';

const App: React.FC = () => {
return (
<BrowserRouter>
<Header />
<Routes>
<Route path='/' element={<h1>Home</h1>} />
<Route path='/recruit' element={<RecruitPage />} />
<Route path='/recruit'>
<Route index element={<RecruitPage />} />
<Route path='new' element={<NewRecruitPage />} />
</Route>
<Route path='/portfolio' element={<PortfolioPage />} />
<Route path='/portfolio/new' element={<NewPortfolioPage />} />
<Route path='/community/new' element={<NewPostPage />} />
</Routes>
</BrowserRouter>
);
Expand Down
45 changes: 45 additions & 0 deletions src/components/community/BoardDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useState } from 'react';
import { DownArrowIcon, UpArrowIcon } from '@/components/ui/icons/ArrowIcon';

interface PBoardDropdown {
boards: string[];
selectedBoard: string;
onSelectBoard: (board: string) => void;
}

const BoardDropdown: React.FC<PBoardDropdown> = ({ boards, selectedBoard, onSelectBoard }) => {
const [isOpen, setIsOpen] = useState(false);

const handleBoardSelect = (board: string) => {
onSelectBoard(board);
setIsOpen(false);
};

return (
<div className='relative'>
<div className='flex h-[38px] w-[200px] items-center justify-between rounded-[8px] border border-border bg-white px-[16px] py-[12px]'>
<div className={`text-body2 ${selectedBoard ? 'text-deepgray' : 'text-disabled'}`}>
{selectedBoard || '카테고리'}
</div>
<div className='cursor-pointer' onClick={() => setIsOpen(!isOpen)}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BoardDropdown 바깥을 클릭하면 close 되도록 하는 코드도 추가되면 좋을 것 같습니다! ☺️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

원래는 그런 방식으로 구현을 하였다가, 화살표 아이콘이 명확하게 있어서 화살표 아이콘만 선택해서 드롭다운을 열고 닫고 하는 방식으로 바꾸었습니다. 추후에 공통으로 재사용 가능한 드롭다운을 만들 예정이라 그때 이 부분 반영해서 구현하겠습니다 :)
리뷰 주셔서 감사해요 ☺️

{isOpen ? <UpArrowIcon size={18} color='#e9e9e9' /> : <DownArrowIcon size={18} color='#e9e9e9' />}
</div>
</div>
{isOpen && (
<div className='absolute z-10 mt-[4px] w-[200px] rounded-[6px] border border-border bg-white shadow-sm'>
{boards.map((board) => (
<div
key={board}
onClick={() => handleBoardSelect(board)}
className='cursor-pointer px-[16px] py-[8px] text-body2 text-deepgray hover:bg-background'
>
{board}
</div>
))}
</div>
)}
</div>
);
};

export default BoardDropdown;
84 changes: 84 additions & 0 deletions src/components/pages/community/new/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState } from 'react';
import MDEditor from '@uiw/react-md-editor';
import Button from '@/components/ui/button/Button';
import BoardDropdown from '@/components/community/BoardDropdown';

const boards = ['공지사항', '자유게시판', '질문게시판', '취업정보'];

const NewPostPage: React.FC = () => {
const [title, setTitle] = useState<string>('');
const [content, setContent] = useState<string>('');
const [selectedBoard, setSelectedBoard] = useState<string>('카테고리');

const handleChange = (setter: React.Dispatch<React.SetStateAction<string>>) => {
return (newValue?: string) => {
setter(newValue || '');
};
};

const handleCancel = () => {};

const handleSubmit = () => {};

return (
<div className='flex h-[calc(100vh-64px)] justify-center'>
<div className='flex w-base flex-col'>
<div className='mt-[42px]'>
<BoardDropdown boards={boards} selectedBoard={selectedBoard} onSelectBoard={setSelectedBoard} />
</div>
<div className='mt-[10px] flex h-3/4 justify-center'>
<div className='w-1/2'>
<div className='title-section h-[44px]'>
<MDEditor
textareaProps={{
placeholder: '제목을 입력하세요.',
maxLength: 30,
}}
minHeight={44}
visibleDragbar={false}
value={title}
onChange={handleChange(setTitle)}
extraCommands={[]}
hideToolbar={true}
enableScroll={false}
preview='edit'
className='border border-b-0 border-border shadow-none'
/>
</div>
<div className='h-[calc(100%-44px)]'>
<MDEditor
textareaProps={{
placeholder: '내용을 작성하세요.',
}}
visibleDragbar={false}
height='100%'
value={content}
onChange={handleChange(setContent)}
extraCommands={[]}
preview='edit'
className='border border-border shadow-none'
/>
</div>
</div>
<div className='border-#ccc h-full w-1/2 rounded-[4px] border border-l-0'>
<MDEditor.Markdown source={`### ${title}`} className='h-[44px] rounded-t-[3px] px-[20px] py-[20px]' />
<MDEditor.Markdown
source={content}
className='h-[calc(100%-44px)] overflow-auto rounded-b-[3px] px-[20px] py-[10px]'
/>
</div>
</div>
<div className='mt-[32px] flex justify-end'>
<Button className='mr-[10px]' type='outlined' onClick={handleCancel}>
취소
</Button>
<Button type='filled' onClick={handleSubmit}>
등록
</Button>
</div>
</div>
</div>
);
};

export default NewPostPage;
13 changes: 13 additions & 0 deletions src/components/pages/recruit/new/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import RecruitForm from '@/components/recruit/new/RecruitForm';
import { H1 } from '@/components/ui/typography/Heading';

const NewRecruitPage: React.FC = () => {
return (
<div className='mx-auto w-base'>
<H1 className='mb-6 mt-9'>채용공고 등록</H1>
<RecruitForm />
</div>
);
};

export default NewRecruitPage;
8 changes: 4 additions & 4 deletions src/components/recruit/RecruitFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Dropdown from '@/components/ui/dropdown/Dropdown';
import LabeledDropdown from '@/components/ui/dropdown/LabeledDropdown';
import SearchIcon from '@/components/ui/icons/SearchIcon';
import LabeledContent from '@/components/ui/LabeledContent';
import useClickOutsideRef from '@/hooks/useClickOutsideRef';
Expand Down Expand Up @@ -32,7 +32,7 @@ const RecruitFilter: React.FC = () => {
<div className='mt-8 flex h-20 items-center divide-x-2 divide-border rounded-md border-2 border-border bg-white'>
<div ref={dropdownRef} className='flex w-3/5 items-center divide-x-2 divide-border'>
<div className='w-1/3'>
<Dropdown
<LabeledDropdown
label='기업 규모'
placeholder='기업 규모 선택'
options={businessScaleList}
Expand All @@ -45,7 +45,7 @@ const RecruitFilter: React.FC = () => {
/>
</div>
<div className='w-1/2'>
<Dropdown
<LabeledDropdown
label='직무'
placeholder='직무 선택'
options={jobList}
Expand All @@ -56,7 +56,7 @@ const RecruitFilter: React.FC = () => {
/>
</div>
<div className='w-1/6'>
<Dropdown
<LabeledDropdown
label='채용 형태'
placeholder='채용 형태 선택'
options={recruitTypeList}
Expand Down
23 changes: 15 additions & 8 deletions src/components/recruit/calendar/CalendarNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import { LeftArrowIcon, RightArrowIcon } from '@/components/ui/icons/ArrowIcon';
import { H2 } from '@/components/ui/typography/Heading';
import { H2, H4 } from '@/components/ui/typography/Heading';
import { subMonths } from 'date-fns';

interface PCalendarNavigation {
currentDate: Date;
setCurrentDate: React.Dispatch<React.SetStateAction<Date>>;
size?: 'sm' | 'lg';
}

const CalendarNavigation: React.FC<PCalendarNavigation> = ({ currentDate, setCurrentDate }) => {
const CalendarNavigation: React.FC<PCalendarNavigation> = ({ currentDate, setCurrentDate, size = 'lg' }) => {
return (
<header className='my-8 flex w-full items-center justify-center gap-4'>
<header className={'flex w-full items-center justify-center ' + (size === 'sm' ? 'my-2 gap-0.5' : 'my-8 gap-4')}>
<button onClick={() => setCurrentDate(subMonths(currentDate, 1))}>
<LeftArrowIcon />
<LeftArrowIcon size={size === 'sm' ? 24 : 32} />
</button>
<H2>
{currentDate.getFullYear()}. {currentDate.getMonth() + 1}
</H2>
{size === 'sm' ? (
<H4>
{currentDate.getFullYear()}. {currentDate.getMonth() + 1}
</H4>
) : (
<H2>
{currentDate.getFullYear()}. {currentDate.getMonth() + 1}
</H2>
)}
<button onClick={() => setCurrentDate(subMonths(currentDate, -1))}>
<RightArrowIcon />
<RightArrowIcon size={size === 'sm' ? 24 : 32} />
</button>
</header>
);
Expand Down
40 changes: 40 additions & 0 deletions src/components/recruit/calendar/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import SmallCalendar from '@/components/recruit/calendar/SmallCalendar';
import CalendarIcon from '@/components/ui/icons/CalendarIcon';
import useClickOutsideRef from '@/hooks/useClickOutsideRef';
import { useState } from 'react';

interface PDatePicker {
placeholder: string;
value: Date | null;
id?: string;
setDate?: React.Dispatch<React.SetStateAction<Date | null>>;
startDate?: Date | null;
}

const DatePicker: React.FC<PDatePicker> = ({ placeholder, value, id, setDate, startDate }) => {
const [isOpen, setIsOpen] = useState<boolean>(false);

const ref = useClickOutsideRef<HTMLDivElement>(() => setIsOpen(false));

return (
<div className='relative w-full' ref={ref}>
<button
id={id}
className='flex w-full items-center gap-2 rounded-sm border border-input px-4 py-2.5 text-body2 focus:outline-activate'
onClick={() => setIsOpen((prev) => !prev)}
>
<p className={`flex-grow text-start ${value ? '' : 'text-disabled'}`}>
{value ? `${value.getFullYear()}. ${value.getMonth() + 1}. ${value.getDate()}` : placeholder}
</p>
<CalendarIcon />
</button>
{isOpen && (
<div className='absolute right-0 top-12 z-10'>
<SmallCalendar selectedDate={value} setDate={setDate} startDate={startDate} />
</div>
)}
</div>
);
};

export default DatePicker;
4 changes: 2 additions & 2 deletions src/components/recruit/calendar/RecruitCalendar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import useCalendar from '@/hooks/useCalendar';
import Calendar from '@/components/recruit/calendar/Calendar';
import CalendarNavigation from '@/components/recruit/calendar/CalendarNavigation';
import useRecruitCalendar from '@/hooks/useRecruitCalendar';

const RecruitCalendar: React.FC = () => {
const { currentDate, setCurrentDate, weekCalendarList } = useCalendar();
const { currentDate, setCurrentDate, weekCalendarList } = useRecruitCalendar();

return (
<section className='flex w-full flex-col items-center'>
Expand Down
84 changes: 84 additions & 0 deletions src/components/recruit/calendar/SmallCalendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import CalendarNavigation from '@/components/recruit/calendar/CalendarNavigation';
import useCalendar from '@/hooks/useCalendar';
import { useEffect } from 'react';

interface PSmallCalendar {
selectedDate?: Date | null;
setDate?: React.Dispatch<React.SetStateAction<Date | null>>;
startDate?: Date | null;
}

const SmallCalendar: React.FC<PSmallCalendar> = ({ selectedDate, setDate, startDate }) => {
const { weekCalendarList, currentDate, setCurrentDate } = useCalendar();

useEffect(() => {
if (startDate) {
if (startDate > currentDate) setCurrentDate(startDate);
} else if (selectedDate) {
setCurrentDate(selectedDate);
}
}, [selectedDate, setCurrentDate, startDate]);

const isSelectedDate = (date: number) => {
if (!selectedDate) return false;
return (
currentDate.getFullYear() === selectedDate.getFullYear() &&
currentDate.getMonth() === selectedDate.getMonth() &&
selectedDate.getDate() === date
);
};

const canSelect = (date: number) => {
if (!startDate) return true;
return new Date(currentDate.getFullYear(), currentDate.getMonth(), date) > startDate;
};

const handleDateClick = (clickedDate: number) => {
if (!setDate) return;
const date = new Date(currentDate.getFullYear(), currentDate.getMonth(), clickedDate);
setDate(date);
};

return (
<div className='rounded-md border border-input bg-white px-2 pb-1'>
<CalendarNavigation currentDate={currentDate} setCurrentDate={setCurrentDate} size='sm' />
<table className='w-full'>
<thead>
<tr className='grid grid-cols-7 text-body2 font-bold'>
<th>일</th>
<th>월</th>
<th>화</th>
<th>수</th>
<th>목</th>
<th>금</th>
<th>토</th>
</tr>
</thead>
<tbody>
{weekCalendarList.map((week, rIdx) => (
<tr key={rIdx} className='grid grid-cols-7 justify-center'>
{week.map((date, cIdx) => (
<td key={cIdx} className='flex justify-center'>
{date === 0 || !canSelect(date) ? (
<button disabled className='h-8 w-8 text-center text-body2 text-disabled'>
{date !== 0 && date}
</button>
) : (
<button
className={`h-8 w-8 rounded-full text-center text-body2 transition-colors ${isSelectedDate(date) ? 'bg-primary-light' : ''} hover:bg-primary-light`}
onClick={() => handleDateClick(date)}
>
{date}
</button>
)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};

export default SmallCalendar;
Loading