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

feat(recruit): 내 공고 생성 모달 구현 #20

Merged
merged 6 commits into from
Aug 7, 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
331 changes: 207 additions & 124 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
"node": "20.2.0"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@tanstack/react-query": "^5.51.1",
"@tanstack/react-query-devtools": "^5.51.1",
"@tippyjs/react": "^4.2.6",
Expand Down Expand Up @@ -67,12 +69,14 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cookies-next": "^4.2.1",
"date-fns": "^3.6.0",
"framer-motion": "^11.3.8",
"lowlight": "^3.1.0",
"lucide-react": "^0.411.0",
"next": "14.2.4",
"react": "^18",
"react-colorful": "^5.6.1",
"react-day-picker": "8.10.1",
"react-dom": "^18",
"tailwind-merge": "^2.4.0",
"tailwind-variants": "^0.2.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { If } from '@/components/If';
import { ComponentProps, ReactNode } from 'react';

interface Props extends ComponentProps<'input'> {
required?: boolean;
right?: ReactNode;
value: string;
}

export function InputField({ required = false, right, value, ...inputProps }: Props) {
return (
<div className="w-full flex justify-between items-center p-12 bg-neutral-1 border-neutral-20 rounded-[8px] border-[1px]">
<If condition={required}>
<div className="text-mint-40 text-label1">*</div>
</If>
<input value={value} className="flex-1 outline-none bg-transparent" {...inputProps} />
<If condition={right != null}>
<div className="ml-[12px]">{right}</div>
</If>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { Spacing } from '@/components/Spacing';
import { TouchButton } from '@/components/TouchButton';
import { Dialog } from '@/system/components/Dialog/ShadcnDialog';
import { color } from '@/system/token/color';
import { InputField } from './InputField';
import { useState } from 'react';
import { Icon } from '@/system/components';
import { getCurrentYearAndHalf, getNextYearAndHalf } from './date';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/system/components/DropdownMenu/DropdownMenu';
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { Popover, PopoverContent, PopoverTrigger } from '@/system/components/Popover/Popover';
import { Calendar } from '@/system/components/Calendar/Calendar';
import { format } from 'date-fns/format';
import { If } from '@/components/If';

interface Props {
onSubmit: () => void;
}

const TITLE_MAX_LENGTH = 30;
// FIXME: 서버쪽에서 전달해주는 데이터로 교체
const DEFAULT_DROPDOWN_PERIOD = [getCurrentYearAndHalf(), getNextYearAndHalf()];

export function NewRecruitDialogContent() {
const [title, setTitle] = useState('');
const [selectedPeriod, setSelectedPeriod] = useState(getCurrentYearAndHalf());
const [link, setLink] = useState('');
const [selectedDate, setSelectedDate] = useState<Date>();

const isButtonActivated = title.length !== 0;
const isDateSelected = selectedDate != null;

return (
<div className="p-20">
<Dialog.Title>
<span className="text-neutral-95 text-body1 font-semibold">새 공고 추가하기</span>
</Dialog.Title>
<Spacing size={4} />
<span className="text-caption1 text-neutral-35 font-regular">공고를 등록하고 정보를 모아보세요!</span>
<Spacing size={24} />

{/* 공고 제목 입력 */}
<InputField
required
value={title}
placeholder="공고 제목을 입력해주세요"
maxLength={TITLE_MAX_LENGTH}
right={<span className="text-neutral-60 text-caption2">{`${title.length}/${TITLE_MAX_LENGTH}`}</span>}
onChange={(event) => setTitle(event.target.value)}
/>
<Spacing size={8} />

{/* 지원 시기 입력 */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="w-full flex justify-between items-center p-12 bg-neutral-1 border-neutral-20 rounded-[8px] border-[1px]">
<span>{selectedPeriod}</span>
<Icon name="down" size={24} color="#878A93" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-360 py-8">
{/* TODO: 이전 공고들의 연도들도 추가 */}
{[...DEFAULT_DROPDOWN_PERIOD].reverse().map((period) => (
<DropdownMenuItem
key={period}
className="w-full flex justify-between px-16 py-8"
onClick={() => setSelectedPeriod(period)}>
<span className={clsx('text-label1', period === selectedPeriod ? 'text-neutral-30' : 'text-neutral-80')}>
{period}
</span>
<If condition={period === selectedPeriod}>
<Icon size={16} name="check" color="#AEB0B6" />
</If>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<Spacing size={8} />

{/* 마감일 입력 */}
<div className="w-full flex justify-between items-center p-12 bg-neutral-1 border-neutral-20 rounded-[8px] border-[1px]">
<span className="text-label1 text-neutral-95">서류마감</span>
<Popover>
<PopoverTrigger>
<motion.div
initial="initial"
variants={{
initial: { backgroundColor: 'rgb(241, 242, 243, 0)' },
touch: { scale: 0.96, transition: { duration: 0.1 } },
hover: { backgroundColor: 'rgb(241, 242, 243, 1)' },
}}
whileTap="touch"
whileHover="hover"
className="px-8 py-4 flex items-center gap-[4px] rounded-[4px]">
<Icon name={isDateSelected ? 'calendarFill' : 'calendar'} size={20} color="#AEB0B6" />
<span className={clsx('text-label2', isDateSelected ? 'text-neutral-95' : 'text-neutral-40')}>
{isDateSelected ? format(selectedDate, 'yyyy.mm.dd') : '마감일을 선택해주세요'}
</span>
</motion.div>
</PopoverTrigger>
<PopoverContent className="w-200">
<Calendar mode="single" selected={selectedDate} onSelect={setSelectedDate} />
</PopoverContent>
</Popover>
</div>
<Spacing size={8} />

{/* 공고 링크 입력 */}
<InputField
value={link}
placeholder="공고 링크를 입력해주세요"
right={<Icon name={link.length === 0 ? 'unlink' : 'link'} size={16} color="#70737C" />}
onChange={(event) => setLink(event.target.value)}
/>
<Spacing size={20} />

{/* 제출 버튼 */}
<TouchButton
variants={{
inactive: {
backgroundColor: color.neutral5,
color: color.neutral30,
},
active: {
backgroundColor: color.neutral95,
color: color.white,
},
}}
animate={isButtonActivated ? 'active' : 'inactive'}
disabled={isButtonActivated === false}
className="w-full flex justify-center items-center h-48 rounded-[6px]">
공고 추가하기
qkrdmstlr3 marked this conversation as resolved.
Show resolved Hide resolved
</TouchButton>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// NOTE: 서버에서 넘어오는 값에 따라 변경될 예정입니다.
export function getCurrentYearAndHalf() {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;

const half = month <= 6 ? '상반기' : '하반기';

return `${year}년 ${half}`;
}

export function getNextYearAndHalf() {
const now = new Date();
let year = now.getFullYear();
const month = now.getMonth() + 1;

let half: string;
if (month <= 6) {
half = '하반기';
} else {
half = '상반기';
year += 1;
}

return `${year}년 ${half}`;
}
15 changes: 12 additions & 3 deletions src/app/(sidebar)/my-recruit/containers/AllRecruitment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/system/components/DropdownMenu/DropdownMenu';
import { motion } from 'framer-motion';
import { color } from '@/system/token/color';
import { Dialog } from '@/system/components/Dialog/ShadcnDialog';

export function AllRecruitment() {
return (
Expand All @@ -31,9 +34,15 @@ export function AllRecruitment() {
</DropdownMenuContent>
</DropdownMenu>
<Spacing size={24} />
<div className="w-full h-70 flex justify-center items-center rounded-[12px] border-neutral-30 border-dashed border-[1px]">
<span className="text-neutral-30 text-label1">등록된 공고가 없어요</span>
</div>
<Dialog.Trigger asChild>
<motion.button
initial="initial"
whileHover="hover"
variants={{ initial: { backgroundColor: 'transparent' }, hover: { backgroundColor: color.neutral5 } }}
className="w-full h-70 flex justify-center items-center rounded-[12px] border-neutral-30 border-dashed border-[1px]">
<span className="text-neutral-30 text-label1">등록된 공고가 없어요</span>
</motion.button>
</Dialog.Trigger>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Spacing } from '@/components/Spacing';
import { ShoeIcon } from './components/ShoeIcon';
import { Dialog } from '@/system/components/Dialog/ShadcnDialog';
import { motion } from 'framer-motion';
import { color } from '@/system/token/color';

export function ProgressingRecruitment() {
return (
Expand All @@ -9,9 +12,15 @@ export function ProgressingRecruitment() {
<span className="text-heading2 font-semibold">현재 진행중인 공고 모아보기</span>
</div>
<Spacing size={24} />
<div className="w-250 h-150 flex justify-center items-center rounded-[12px] border-neutral-30 border-dashed border-[1px]">
<span className="text-neutral-30 text-label1">진행중인 공고가 없어요</span>
</div>
<Dialog.Trigger asChild>
<motion.button
initial="initial"
whileHover="hover"
variants={{ initial: { backgroundColor: 'transparent' }, hover: { backgroundColor: color.neutral5 } }}
className="w-250 h-150 flex justify-center items-center rounded-[12px] border-neutral-30 border-dashed border-[1px]">
<span className="text-neutral-30 text-label1">진행중인 공고가 없어요</span>
</motion.button>
</Dialog.Trigger>
</>
);
}
48 changes: 31 additions & 17 deletions src/app/(sidebar)/my-recruit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
'use client';

import { Icon } from '@/system/components';
import { TouchButton } from '@/components/TouchButton';
import { Spacing } from '@/components/Spacing';
import { ProgressingRecruitment } from './containers/ProgressingRecruitment';
import { AllRecruitment } from './containers/AllRecruitment';
import { useState } from 'react';
import { Dialog } from '@/system/components/Dialog/ShadcnDialog';
import { NewRecruitDialogContent } from './components/NewRecruitDialogContent/NewRecruitDialogContent';

export default function MyRecruit() {
return (
<div className="max-w-[1700px] py-[64px] px-[80px] mx-auto">
<div className="flex justify-between">
<h1 className="text-title2 font-bold">내 공고 뽀각</h1>
<div className="flex gap-[16px]">
<TouchButton className="bg-white flex gap-[4px] py-[8px] px-[12px] rounded-[6px]">
<Icon name="copy" size={16} color="#CCCDD1" />
<span className="text-label1 font-semibold text-neutral-20">내 정보 가져오기</span>
</TouchButton>
<TouchButton className="bg-neutral-95 flex items-center gap-[4px] py-[8px] px-[16px] rounded-[6px]">
<Icon name="add" size={24} color="#20E79D" />
<span className="text-label1 text-white font-semibold">새 공고</span>
</TouchButton>
<Dialog>
<div className="max-w-[1700px] py-[64px] px-[80px] mx-auto">
<div className="flex justify-between">
<h1 className="text-title2 font-bold">내 공고 뽀각</h1>
<div className="flex gap-[16px]">
<TouchButton className="bg-white flex gap-[4px] py-[8px] px-[12px] rounded-[6px]">
<Icon name="copy" size={16} color="#CCCDD1" />
<span className="text-label1 font-semibold text-neutral-20">내 정보 가져오기</span>
</TouchButton>
<Dialog.Trigger asChild>
<div>
<TouchButton className="bg-neutral-95 flex items-center gap-[4px] py-[8px] px-[16px] rounded-[6px]">
<Icon name="add" size={24} color="#20E79D" />
<span className="text-label1 text-white font-semibold">새 공고</span>
</TouchButton>
</div>
</Dialog.Trigger>
</div>
</div>
<Spacing size={54} />
<ProgressingRecruitment />
<Spacing size={88} />
<AllRecruitment />
</div>
<Spacing size={54} />
<ProgressingRecruitment />
<Spacing size={88} />
<AllRecruitment />
</div>
<Dialog.Content className="w-400">
<NewRecruitDialogContent />
</Dialog.Content>
</Dialog>
);
}
7 changes: 4 additions & 3 deletions src/components/TouchButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { ComponentProps } from 'react';

type Props = ComponentProps<typeof motion.button>;

export function TouchButton({ children, ...restProps }: Props) {
export function TouchButton({ children, variants, disabled, ...restProps }: Props) {
return (
<motion.button
initial="idle"
whileTap="touch"
variants={{ idle: { scale: 1 }, touch: { scale: 0.96 } }}
whileTap={disabled ? 'idle' : 'touch'}
variants={{ idle: { scale: 1 }, touch: { scale: 0.96 }, ...variants }}
transition={{ duration: 0.1 }}
disabled={disabled}
{...restProps}>
{children}
</motion.button>
Expand Down
Loading
Loading