Skip to content

Commit

Permalink
πŸš€ ver3.0.4
Browse files Browse the repository at this point in the history
변경사항
λͺ¨μž„ λ‚΄λΆ€ 17λͺ… 접속 μ‹œ 접속 μ œν•œ
  • Loading branch information
Changyu-Ryou authored Dec 20, 2021
2 parents 2919397 + 2835d20 commit 3efd02c
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 71 deletions.
57 changes: 57 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"recoil": "^0.4.1",
"slick-carousel": "^1.8.1",
"swiper": "^7.0.9",
"ts-node": "^10.2.1"
"ts-node": "^10.2.1",
"zod": "^3.11.6"
},
"devDependencies": {
"@types/agora-rtc-sdk": "^3.2.0",
Expand Down
131 changes: 76 additions & 55 deletions src/components/CreateMeetingPage/CreateMeetingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import styled from '@emotion/styled';
import { logEvent } from '@firebase/analytics';
import { useNavigator } from '@karrotframe/navigator';
import { CreateMeeting } from 'meeting';
import { z } from 'zod';

import { uploadImage } from '../../api/image';
import { createMeeting } from '../../api/meeting';
Expand All @@ -30,11 +31,39 @@ import ImageUploaderBox from './components/ImageUploaderBox';
import TimePicker from './components/TimePicker';
import WordCounter from './components/WordCounter';

const TitleMaxValid = z.string().max(40);
const TitleMinValid = z.string().min(1);
const TitleValid = z.intersection(TitleMaxValid, TitleMinValid);
const DescriptionMaxValid = z.string().max(140);
const DescriptionMinValid = z.string().min(1);
const DescriptionValid = z.intersection(
DescriptionMinValid,
DescriptionMaxValid,
);
const TypeValid = z.string().min(1);
const DateValid = z.string();
const TimeStringValid = z.string().min(2);
const TimeValid = z.object({
start_time: z.string(),
end_time: z.string(),
});

const ImageValid = z.instanceof(File).nullable();

export const FormValid = z.object({
title: TitleValid,
description: DescriptionValid,
type: TypeValid,
date: DateValid,
time: TimeValid,
image: ImageValid,
});

function CreateMeetingForm(): ReactElement {
const [form, setForm] = useState<CreateMeeting>({
const [form, setForm] = useState<z.infer<typeof FormValid>>({
title: '',
description: '',
type: undefined,
type: '',
date: '',
time: { start_time: '', end_time: '' },
image: null,
Expand All @@ -53,18 +82,14 @@ function CreateMeetingForm(): ReactElement {
return urlHashParams.get('ref');
}, []);

const isValid = useMemo(
() =>
form.title.length !== 0 &&
form.title.length <= 40 &&
form.description.length !== 0 &&
form.description.length <= 140 &&
form.type !== undefined &&
form.date.length != 0 &&
form.time.start_time.length !== 0 &&
form.time.end_time.length !== 0,
[form.date, form.description, form.time, form.title, form.type],
);
const isValid = useMemo(() => {
try {
FormValid.parse(form);
return true;
} catch (e) {
return false;
}
}, [form]);

const onSetImageHandler = useCallback(
async (e?: ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -137,23 +162,24 @@ function CreateMeetingForm(): ReactElement {
placeholder="λͺ¨μž„ 제λͺ©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”. (예. 같이 μ±… 읽고 λŒ€ν™” λ‚˜λˆ μš”.)"
height="8.6rem"
validation={
(submitState.state !== 'submit' && form.title.length < 40) ||
(submitState.state !== 'submit' &&
TitleMaxValid.safeParse(form.title).success) ||
(submitState.state === 'submit' &&
form.title.length !== 0 &&
form.title.length < 40)
TitleValid.safeParse(form.title).success)
}
formHandler={(value: string) =>
setForm(prevState => ({ ...prevState, title: value }))
}
/>
<ValidationInfoWarpper>
<ValidationInfo>
{form.title.length > 40 && (
{!TitleMaxValid.safeParse(form.title).success && (
<div>λͺ¨μž„ 제λͺ©μ€ μ΅œλŒ€ 40μžκΉŒμ§€ μž…λ ₯ν•  수 μžˆμ–΄μš”.</div>
)}
{submitState.state === 'submit' && form.title.length === 0 && (
<div>λͺ¨μž„ 제λͺ©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.</div>
)}
{submitState.state === 'submit' &&
!TitleMinValid.safeParse(form.title).success && (
<div>λͺ¨μž„ 제λͺ©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.</div>
)}
</ValidationInfo>
<WordCounter maxWords={40} words={form.title} />
</ValidationInfoWarpper>
Expand All @@ -165,40 +191,38 @@ function CreateMeetingForm(): ReactElement {
height="15.5rem"
validation={
(submitState.state !== 'submit' &&
form.description.length <= 140) ||
DescriptionMaxValid.safeParse(form.description).success) ||
(submitState.state === 'submit' &&
form.description.length !== 0 &&
form.description.length <= 140)
DescriptionValid.safeParse(form.description).success)
}
formHandler={(value: string) =>
setForm(prevState => ({ ...prevState, description: value }))
}
/>
<ValidationInfoWarpper>
<ValidationInfo>
<div>
{form.description.length > 140 &&
'λͺ¨μž„ λ‚΄μš©μ€ μ΅œλŒ€ 140μžκΉŒμ§€ μž…λ ₯ν•  수 μžˆμ–΄μš”.'}
</div>
<div>
{submitState.state === 'submit' &&
form.description.length === 0 &&
'λͺ¨μž„ λ‚΄μš©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.'}
</div>
{!DescriptionMaxValid.safeParse(form.description).success && (
<div>'λͺ¨μž„ λ‚΄μš©μ€ μ΅œλŒ€ 140μžκΉŒμ§€ μž…λ ₯ν•  수 μžˆμ–΄μš”.'</div>
)}
{submitState.state === 'submit' &&
!DescriptionMinValid.safeParse(form.description).success && (
<div>'λͺ¨μž„ λ‚΄μš©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”.'</div>
)}
</ValidationInfo>
<WordCounter maxWords={140} words={form.description} />
</ValidationInfoWarpper>
</Description>

<MeetingTypeWrapper>
<TitleText>λͺ¨μž„ 진행 방식</TitleText>
{submitState.state === 'submit' && form.type === undefined && (
<ValidationInfoWarpper>
<ValidationInfo>
<div>λͺ¨μž„ 진행 방식을 μ„ νƒν•΄μ£Όμ„Έμš”.</div>
</ValidationInfo>
</ValidationInfoWarpper>
)}
{submitState.state === 'submit' &&
!TypeValid.safeParse(form.type).success && (
<ValidationInfoWarpper>
<ValidationInfo>
<div>λͺ¨μž„ 진행 방식을 μ„ νƒν•΄μ£Όμ„Έμš”.</div>
</ValidationInfo>
</ValidationInfoWarpper>
)}
<TypeBtnWrapper>
<TypeBtn
onClick={() =>
Expand Down Expand Up @@ -254,11 +278,10 @@ function CreateMeetingForm(): ReactElement {
/>
<ValidationInfoWarpper>
<ValidationInfo>
<div>
{submitState.state === 'submit' &&
(form.date === undefined || form.date.length === 0) &&
'λͺ¨μž„ λ‚ μ§œλ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”.'}
</div>
{submitState.state === 'submit' &&
!TypeValid.safeParse(form.date).success && (
<div>'λͺ¨μž„ λ‚ μ§œλ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”.'</div>
)}
</ValidationInfo>
</ValidationInfoWarpper>
</Date>
Expand All @@ -272,16 +295,14 @@ function CreateMeetingForm(): ReactElement {
/>
<ValidationInfoWarpper>
<ValidationInfo>
<div>
{submitState.state === 'submit' &&
form.time.start_time.length === 0 &&
'λͺ¨μž„ μ‹œμž‘ μ‹œκ°„μ„ μ„ νƒν•΄μ£Όμ„Έμš”.'}
</div>
<div>
{submitState.state === 'submit' &&
form.time.end_time.length === 0 &&
'λͺ¨μž„ μ’…λ£Œ μ‹œκ°„μ„ μ„ νƒν•΄μ£Όμ„Έμš”.'}
</div>
{submitState.state === 'submit' &&
!TimeStringValid.safeParse(form.time.start_time).success && (
<div>'λͺ¨μž„ μ‹œμž‘ μ‹œκ°„μ„ μ„ νƒν•΄μ£Όμ„Έμš”.'</div>
)}
{submitState.state === 'submit' &&
!TimeStringValid.safeParse(form.time.end_time).success && (
<div>'λͺ¨μž„ μ’…λ£Œ μ‹œκ°„μ„ μ„ νƒν•΄μ£Όμ„Έμš”.'</div>
)}
</ValidationInfo>
</ValidationInfoWarpper>
</Time>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function EditableTextarea({
placeholder={placeholder}
validation={validation}
onInput={(e: React.ChangeEvent<HTMLDivElement>) => {
formHandler && formHandler(e.target.innerText);
formHandler && formHandler(e.target.textContent || '');
}}
/>
);
Expand Down
12 changes: 7 additions & 5 deletions src/components/CreateMeetingPage/components/TimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import React, { ReactElement, useCallback, useEffect, useState } from 'react';

import styled from '@emotion/styled';
import dayjs from 'dayjs';
import { CreateMeeting, TimeType } from 'meeting';
import { TimeType } from 'meeting';
import { z } from 'zod';

import nav_close from '../../../assets/icon/common/nav_close.svg';
import { COLOR } from '../../../constant/color';
import { FormValid } from '../CreateMeetingForm';

interface Props {
date: string;
time: TimeType;
setForm: React.Dispatch<React.SetStateAction<CreateMeeting>>;
setForm: React.Dispatch<React.SetStateAction<z.infer<typeof FormValid>>>;
trySubmit: boolean;
}

Expand Down Expand Up @@ -49,7 +51,7 @@ function TimePicker({ date, time, setForm, trySubmit }: Props): ReactElement {
useEffect(() => {
setStartList([]);
setEndList([]);
setForm((prevState: CreateMeeting) => ({
setForm(prevState => ({
...prevState,
time: { start_time: '', end_time: '' },
}));
Expand All @@ -68,7 +70,7 @@ function TimePicker({ date, time, setForm, trySubmit }: Props): ReactElement {
<SelectorStyle
className="start_time_selector"
onChange={e => {
setForm((prevState: CreateMeeting) => ({
setForm(prevState => ({
...prevState,
time: { start_time: e.target.value, end_time: '' },
}));
Expand All @@ -95,7 +97,7 @@ function TimePicker({ date, time, setForm, trySubmit }: Props): ReactElement {
<SelectorStyle
className="end_time_selector"
onChange={e => {
setForm((prevState: CreateMeeting) => ({
setForm(prevState => ({
...prevState,
time: { ...prevState.time, end_time: e.target.value },
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { MeetingList } from 'meeting';
import { COLOR } from '../../../../constant/color';
import Divider from '../../../common/Divider';
import MeetingCard from '../MeetingCard/MeetingCard';
import SkeletonCard from '../MeetingCard/SkeletonCard';

interface Props {
className?: string;
Expand All @@ -20,7 +19,7 @@ interface Props {
function MeetingList({
className,
meetings,
hasMeetings,

setMeetings,
}: Props): ReactElement {
const [dateList, setDateList] = useState<string[]>([]);
Expand Down Expand Up @@ -66,8 +65,8 @@ function MeetingList({
</DateWrapper>
);
})}

{!hasMeetings && <SkeletonCard />}
{/*
{!hasMeetings && <SkeletonCard />} */}
</MeetingListWrapper>
);
}
Expand Down
Loading

0 comments on commit 3efd02c

Please sign in to comment.