Skip to content

Commit

Permalink
Merge pull request #152 from LEEJW1953/fe-post-form
Browse files Browse the repository at this point in the history
[Feat] 모각코 게시글 작성 폼 유효성 검사
  • Loading branch information
ttaerrim authored Nov 23, 2023
2 parents aace6a6 + b660c32 commit e748923
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 83 deletions.
43 changes: 43 additions & 0 deletions app/frontend/src/components/MogacoPost/MogacoPostTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Controller, Control } from 'react-hook-form';

import { MOGACO_POST } from '@/constants';
import { MogacoPostForm } from '@/types';

import * as styles from './index.css';

type MogacoPostTitleProps = {
control: Control<MogacoPostForm>;
};

export function MogacoPostTitle({ control }: MogacoPostTitleProps) {
return (
<Controller
control={control}
name="title"
rules={{
required: MOGACO_POST.TITLE.REQUIRED,
maxLength: {
value: MOGACO_POST.TITLE.MAX_LENGTH,
message: MOGACO_POST.TITLE.MAX,
},
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<div className={`${styles.titleContainer} ${error && styles.error}`}>
<div className={styles.titleContent}>
<input
type="text"
className={styles.title}
placeholder={MOGACO_POST.TITLE.REQUIRED}
maxLength={MOGACO_POST.TITLE.MAX_LENGTH}
onChange={onChange}
/>
<div className={styles.count}>
{value?.length || 0}/{MOGACO_POST.TITLE.MAX_LENGTH}
</div>
</div>
{error && <div className={styles.errorMessage}>{error.message}</div>}
</div>
)}
/>
);
}
84 changes: 67 additions & 17 deletions app/frontend/src/components/MogacoPost/index.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,88 @@ import { style } from '@vanilla-extract/css';

import { vars, fontStyle } from '@/styles';

export const container = style({
display: 'flex',
flexDirection: 'column',
gap: '24px',
maxWidth: '75rem',
margin: '0 auto',
padding: '1.5rem',
});

export const formContent = style({
display: 'flex',
flexDirection: 'column',
gap: '1.6rem',
width: '100%',
});
const error = style({});

export const title = style([
const title = style([
fontStyle.sansBold24,
{
padding: '0.8rem',
height: '4rem',
width: '100%',
color: vars.color.grayscale500,
borderBottom: '2px solid transparent',

selectors: {
'&:focus': {
outline: 'none',
borderBottom: `2px solid ${vars.color.morakGreen}`,
},
'&::placeholder': {
color: vars.color.grayscale200,
},
},
},
]);

const titleContent = style([
fontStyle.sansRegular12,
{
display: 'flex',
flex: '1 0 0',
alignItems: 'center',
gap: '1.6rem',
borderBottom: '2px solid transparent',

selectors: {
'&:focus-within': {
borderBottom: `2px solid ${vars.color.morakGreen}`,
},
[`${error} &`]: {
borderBottom: `2px solid ${vars.color.morakRed}`,
},
},
},
]);

export const container = style({
display: 'flex',
flexDirection: 'column',
gap: '24px',
maxWidth: '75rem',
margin: '0 auto',
padding: '1.5rem',
});

export const count = style([
fontStyle.sansRegular12,
{
visibility: 'hidden',

selectors: {
[`${titleContent}:focus-within &`]: {
visibility: 'visible',
},
},
},
]);

export { error, title, titleContent };

export const errorMessage = style([
fontStyle.sansRegular12,
{
color: vars.color.morakRed,
},
]);

export const formContent = style({
display: 'flex',
flexDirection: 'column',
gap: '1.6rem',
width: '100%',
});

export const titleContainer = style({
display: 'flex',
flexDirection: 'column',
gap: '0.4rem',
});
154 changes: 123 additions & 31 deletions app/frontend/src/components/MogacoPost/index.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,141 @@
import { useForm, Controller } from 'react-hook-form';

import dayjs from 'dayjs';

import { Input, Button, Textarea } from '@/components';
import { MOGACO_POST } from '@/constants';
import { MogacoPostForm } from '@/types';

import * as styles from './index.css';
import { MogacoPostTitle } from './MogacoPostTitle';

export function MogacoPostPage() {
const date = dayjs(new Date()).format('YYYY-MM-DD HH:mm');
const { control, handleSubmit } = useForm<MogacoPostForm>();

// POST 요청으로 수정 예정
const onSubmit = () => {};

return (
<form className={styles.container}>
<form className={styles.container} onSubmit={handleSubmit(onSubmit)}>
<MogacoPostTitle control={control} />
<div className={styles.formContent}>
<input
type="text"
className={styles.title}
placeholder="모각코 함께해요"
required
<Controller
control={control}
name="memberId"
render={({ field: { onChange, value } }) => (
<Input
label={MOGACO_POST.MEMBER.LABEL}
required
disabled
defaultValue="user" // 로그인한 유저 정보
onChange={onChange}
value={value}
/>
)}
/>
</div>
<div className={styles.formContent}>
<Input
label="작성자"
maxLength={64}
required
disabled
defaultValue="user"
<Controller
control={control}
name="groupId"
rules={{ required: true }}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<Input
label={MOGACO_POST.GROUP.LABEL}
placeholder={MOGACO_POST.GROUP.REQUIRED}
required
onChange={onChange}
value={value}
errorMessage={error && MOGACO_POST.GROUP.REQUIRED}
/>
)}
/>
<Controller
control={control}
name="maxHumanCount"
rules={{
required: MOGACO_POST.COUNT.REQUIRED,
pattern: {
value: /^[0-9]*$/,
message: MOGACO_POST.COUNT.PATTERN,
},
min: {
value: MOGACO_POST.COUNT.MIN_VALUE,
message: MOGACO_POST.COUNT.MIN,
},
max: {
value: MOGACO_POST.COUNT.MAX_VALUE,
message: MOGACO_POST.COUNT.MAX,
},
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<Input
label={MOGACO_POST.COUNT.LABEL}
type="number"
placeholder={MOGACO_POST.COUNT.REQUIRED}
required
onChange={onChange}
value={value}
errorMessage={error && error.message}
/>
)}
/>
<Controller
control={control}
name="address"
rules={{ required: true }}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<Input
label={MOGACO_POST.ADDRESS.LABEL}
placeholder={MOGACO_POST.ADDRESS.REQUIRED}
required
onChange={onChange}
value={value}
errorMessage={error && MOGACO_POST.ADDRESS.REQUIRED}
/>
)}
/>
<Input label="그룹" placeholder="그룹을 선택해주세요" required />
<Input
label="최대 인원 수"
type="number"
placeholder="20"
min={1}
max={20}
required
<Controller
control={control}
name="date"
rules={{
required: MOGACO_POST.DATE.REQUIRED,
min: { value: date, message: MOGACO_POST.DATE.MIN },
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<Input
label={MOGACO_POST.DATE.LABEL}
type="datetime-local"
required
min={date}
onChange={onChange}
value={value}
errorMessage={error && error.message}
/>
)}
/>
<Input label="장소" placeholder="장소를 검색해주세요" required />
<Input
label="날짜 및 시간"
type="datetime-local"
required
defaultValue={date}
min={date}
<Controller
control={control}
name="contents"
rules={{
required: MOGACO_POST.CONTENTS.REQUIRED,
maxLength: {
value: MOGACO_POST.CONTENTS.MAX_LENGTH,
message: MOGACO_POST.CONTENTS.MAX,
},
}}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<Textarea
label={MOGACO_POST.CONTENTS.LABEL}
placeholder={MOGACO_POST.CONTENTS.REQUIRED}
rows={MOGACO_POST.CONTENTS.ROWS}
maxLength={MOGACO_POST.CONTENTS.MAX_LENGTH}
required
onChange={onChange}
value={value}
errorMessage={error && error.message}
/>
)}
/>
<Textarea label="설명" maxLength={1000} rows={6} />
</div>
<div className={styles.formContent}>
<Button
Expand All @@ -51,7 +144,6 @@ export function MogacoPostPage() {
shape="fill"
size="large"
fullWidth
onClick={() => {}}
>
등록하기
</Button>
Expand Down
22 changes: 7 additions & 15 deletions app/frontend/src/components/commons/Input/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { useRef, useState } from 'react';

import * as styles from './Textarea.css';

type TextareaProps = {
Expand All @@ -11,6 +9,8 @@ type TextareaProps = {
required?: boolean;
fullWidth?: boolean;
rows?: number;
value?: string;
onChange?: () => void;
};

export function Textarea({
Expand All @@ -22,16 +22,9 @@ export function Textarea({
required = false,
fullWidth = false,
rows = 2,
value,
onChange,
}: TextareaProps) {
const [textCount, setTextCount] = useState<number>(0);
const textRef = useRef<HTMLTextAreaElement | null>(null);

const handleInput = () => {
if (textRef && textRef.current) {
setTextCount(textRef.current.value.length);
}
};

return (
<div
className={`${styles.container} ${errorMessage && styles.error} ${
Expand All @@ -44,18 +37,17 @@ export function Textarea({
{required && <span className={styles.required}>*</span>}
</span>
<span className={styles.count}>
{textCount}/{maxLength}
{value ? value.length : 0}/{maxLength}
</span>
</div>
<textarea
rows={rows}
ref={textRef}
className={styles.textarea}
placeholder={placeholder}
disabled={disabled}
maxLength={maxLength}
required={required}
onChange={handleInput}
value={value}
onChange={onChange}
/>
{!disabled && errorMessage && (
<p className={styles.errorMessage}>{errorMessage}</p>
Expand Down
Loading

0 comments on commit e748923

Please sign in to comment.