Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { UpdateJobPostingPage as default } from 'pages/business-job-posting'
1 change: 1 addition & 0 deletions app/(business)/business/job-posting/create/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CreateJobPostingPage as default } from 'pages/business-job-posting'
2 changes: 2 additions & 0 deletions src/features/job-posting-form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { JobPostingForm } from './ui/job-posting-form'
export { SidePanel } from './ui/side-panel'
131 changes: 131 additions & 0 deletions src/features/job-posting-form/ui/job-posting-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { fieldCss, Form } from 'shared/form'
import { cn, Slot } from 'shared/lib'
import { Checkbox, Label } from 'shared/ui'

type Props = {
className?: string
}

export const JobPostingForm = ({ className }: Props) => {
const studentTypeOptions = [
'All',
'Kindergarten',
'Elementary',
'MiddleSchool',
'HighSchool',
'Adult',
]

return (
<div className={cn('rounded-3xl border border-gray-300 p-10', className)}>
<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-8' })}>
<Label required>공고 제목</Label>
<Form.Control name="title">
<Form.TextField
placeholder="간결하고 명확하게 입력해 주세요. ex) Full-Time ESL Instructor"
fullWidth
/>
<Form.ErrorMessage />
</Form.Control>
</div>
<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-8' })}>
<Label required>주요 업무</Label>
<Form.Control name="jobDescription">
<Form.TextArea
placeholder={`입사 후 맡게되는 업무에 대해 자세히 알려주세요\nex) Teach [elementary-level] English classes [30 hours per week]. Develop lesson plans and class materials.`}
fullWidth
className="h-[128px]"
/>
<Form.ErrorMessage />
</Form.Control>
</div>
<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-8' })}>
<Label>자격 요건</Label>
<Form.Control name="requiredQualification">
<Form.TextArea
placeholder={`지원자의 필요 조건을 입력해 주세요\nex) Native or native-level English speaker. Valid TEFL/TESOL/CELTA certification (preferred).`}
fullWidth
className="h-[128px]"
/>
<Form.ErrorMessage />
</Form.Control>
</div>
<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-8' })}>
<Label>우대 사항</Label>
<Form.Control name="preferredQualification">
<Form.TextArea
placeholder={`채용시 우대되는 사항이 있다면 입력해 주세요\nex) Basic Korean language skills for simple communication.\nExperience teaching [elementary students/IELTS/TOEFL].`}
fullWidth
className="h-[128px]"
/>
<Form.ErrorMessage />
</Form.Control>
</div>
<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-8' })}>
<Label>혜택/복지</Label>
<Form.Control name="benefits">
<Form.TextArea
placeholder={`근무자에게 제공되는 혜택을 알려주세요\nex) Provided housing or housing allowance. Paid vacation days (ex. 10 days) + national holidays. Round-trip airfare reimbursement (depending on contract).`}
fullWidth
className="h-[128px]"
/>
<Form.ErrorMessage />
</Form.Control>
</div>
<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-8' })}>
<Label required>월급</Label>
<Form.Control name="salary">
<Form.TextField placeholder="숫자만 입력해 주세요" fullWidth>
<Slot name="right">
<span className="body-large text-gray-700">KRW</span>
</Slot>
</Form.TextField>
<Form.ErrorMessage />
</Form.Control>
<Form.Control name="salaryNegotiable">
<Form.CheckboxGroup options={['true']}>
<Form.Checkbox label="협의 가능" value="true" />
</Form.CheckboxGroup>
<Form.ErrorMessage />
</Form.Control>
</div>

<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-8' })}>
<Label required>대상 학생</Label>
<div className="flex gap-[30px]">
<Form.CheckboxGroup name="studentType" options={studentTypeOptions}>
<Form.Checkbox label="전체" value="All" />
<Form.Checkbox label="유치원" value="Kindergarten" />
<Form.Checkbox label="초등학생" value="Elementary" />
<Form.Checkbox label="중학생" value="MiddleSchool" />
<Form.Checkbox label="고등학생" value="HighSchool" />
<Form.Checkbox label="성인" value="Adult" />
</Form.CheckboxGroup>
</div>
</div>

<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-7' })}>
<Label>업무 시작 가능 날짜</Label>
<Form.Control name="jobStartDate">
<Form.DatePicker
placeholder="업무 시작 날짜를 선택해 주세요"
fullWidth
/>
<Form.ErrorMessage />
</Form.Control>
</div>

<div className={fieldCss.fieldWrapper({ className: 'not-last:mb-8' })}>
<Label required>공고 마감일</Label>
<Form.Control name="dueDate">
<Form.DatePicker
placeholder="공고 마감 날짜를 선택해 주세요"
fullWidth
/>
<Form.ErrorMessage />
</Form.Control>
<Checkbox label="상시 채용" value="true" className="-mt-[6px]" />
</div>
</div>
)
}
35 changes: 35 additions & 0 deletions src/features/job-posting-form/ui/side-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Button } from 'shared/ui'

type Props = {
type: 'register' | 'update'
onRegister?: () => void
onSave?: () => void
}

export const SidePanel = ({ type }: Props) => {
return (
<div className="h-fit w-[340px] shrink-0 rounded-2xl border border-gray-300 p-6">
<p className="body-large mb-2 text-blue-800">
공고 내용은 영어로 작성해주세요.
</p>
<div className="mb-6">
<p className="body-medium text-gray-700">
입력한 정보는 검색에 반영돼요.
</p>
<p className="body-medium text-gray-700">
중요한 정보를 빠뜨리지 않았는지 확인해 주세요.
</p>
</div>
<div className="space-y-2">
<Button variant="primary" size="large" fullWidth>
등록하기
</Button>
{type === 'register' && (
<Button variant="lined" size="large" fullWidth>
임시 저장
</Button>
)}
</div>
</div>
)
}
20 changes: 18 additions & 2 deletions src/features/preview-job-posting/ui/preview-job-posting-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ import { MouseEvent } from 'react'
import { getJobPost } from 'entities/job-post'
import { JobPostDetail } from 'entities/job-post'
import { colors } from 'shared/config'
import { cn } from 'shared/lib'
import { Button, Icon } from 'shared/ui'

import { setPreviewJobPosting } from '../lib/storage'

type Props = {
type: 'icon' | 'button'
type: 'icon' | 'button' | 'text-button'
jobPostId?: number
onLoad?: () => Promise<JobPostDetail>
disabled?: boolean
className?: string
}

export const PreviewJobPostingButton = ({
type,
jobPostId,
onLoad,
disabled,
className,
}: Props) => {
const handlePreviewButtonClick = async (
event: MouseEvent<HTMLButtonElement>,
Expand All @@ -40,7 +43,7 @@ export const PreviewJobPostingButton = ({
if (type === 'icon') {
return (
<button
className="flex h-10 w-10 items-center justify-center"
className={cn('flex h-10 w-10 items-center justify-center', className)}
disabled={disabled}
onClick={handlePreviewButtonClick}
>
Expand All @@ -53,13 +56,26 @@ export const PreviewJobPostingButton = ({
)
}

if (type === 'text-button') {
return (
<button
className={cn('body-large text-gray-700', className)}
disabled={disabled}
onClick={handlePreviewButtonClick}
>
미리 보기
</button>
)
}

return (
<Button
type="button"
variant="lined"
size="small"
onClick={handlePreviewButtonClick}
disabled={disabled}
className={className}
>
<Button.Icon name="DocumentSearch" />
미리 보기
Expand Down
2 changes: 2 additions & 0 deletions src/pages/business-job-posting/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { CreateJobPostingPage } from './ui/create-job-posting-page'
export { BusinessJobPostingListPage } from './ui/job-posting-list-page'
export { UpdateJobPostingPage } from './ui/update-job-posting-page'
30 changes: 30 additions & 0 deletions src/pages/business-job-posting/ui/create-job-posting-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import { useForm } from 'react-hook-form'

import { JobPostingForm, SidePanel } from 'features/job-posting-form'
import { PreviewJobPostingButton } from 'features/preview-job-posting'
import { Form } from 'shared/form'
import { Layout } from 'shared/ui'

export const CreateJobPostingPage = () => {
const form = useForm()

return (
<Layout wide>
<h1 className="display-small mb-10 text-center font-bold text-gray-900">
공고 등록
</h1>
<Form {...form} className="flex gap-[20px]">
<JobPostingForm className="flex-grow" />
<div className="space-y-2">
<SidePanel type="register" />
<PreviewJobPostingButton
type="text-button"
className="ml-auto block"
/>
</div>
</Form>
</Layout>
)
}
7 changes: 6 additions & 1 deletion src/pages/business-job-posting/ui/job-posting-list-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ export const BusinessJobPostingListPage = () => {
{t('tabs.closed')}
</Tabs.Trigger>
</Tabs.List>
<Button variant="lined" size="medium">
<Button
variant="lined"
size="medium"
as="a"
href="/business/job-posting/create"
>
<Button.Icon name="Plus" />
{t('button.register-job-posting')}
</Button>
Expand Down
30 changes: 30 additions & 0 deletions src/pages/business-job-posting/ui/update-job-posting-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import { useForm } from 'react-hook-form'

import { JobPostingForm, SidePanel } from 'features/job-posting-form'
import { PreviewJobPostingButton } from 'features/preview-job-posting'
import { Form } from 'shared/form'
import { Layout } from 'shared/ui'

export const UpdateJobPostingPage = () => {
const form = useForm()

return (
<Layout wide>
<h1 className="display-small mb-10 text-center font-bold text-gray-900">
공고 수정
</h1>
<Form {...form} className="flex gap-[20px]">
<JobPostingForm className="flex-grow" />
<div className="space-y-2">
<SidePanel type="update" />
<PreviewJobPostingButton
type="text-button"
className="ml-auto block"
/>
</div>
</Form>
</Layout>
)
}
3 changes: 3 additions & 0 deletions src/shared/form/ui/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ type FormCheckboxProps = {
label?: string
value?: CheckboxValue
indeterminate?: boolean
className?: string
}

const FormCheckboxItem = ({
label,
value,
indeterminate = false,
className,
}: FormCheckboxProps) => {
const { clearErrors } = useFormContext()
const {
Expand Down Expand Up @@ -101,6 +103,7 @@ const FormCheckboxItem = ({
error={!isEmpty(error)}
onChange={handleCheckboxChange}
value={field.value}
className={className}
/>
)
}
Expand Down
5 changes: 4 additions & 1 deletion src/shared/ui/text-area/variants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { cva, VariantProps } from 'class-variance-authority'

export const textarea = cva(
'body-large resize-none rounded-[10px] border border-gray-300 p-4 text-gray-900 transition-all',
[
'body-large resize-none rounded-[10px] border border-gray-300 p-4 text-gray-900 transition-all',
'placeholder:text-gray-500',
],
{
variants: {
fullWidth: {
Expand Down