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

Refactor/#150/project form #158

Merged
merged 36 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
41a8474
[Refactoring ⚙️] close버튼 클릭시에만 이벤트 발생하도록 수정
Mar 19, 2024
47edf38
[Change 🚜 ] styles 폴더 생성 및 Container 파일 이동
Mar 19, 2024
5e42927
[Feat ✏️] useDebounce hook 추가
Mar 19, 2024
2420e10
[Feat ✏️] useInput debounce 적용
Mar 19, 2024
d7e5915
[Refactoring ⚙️] SearchBar 비제어방식으로 사용 가능하게 타입 수정
Mar 19, 2024
419a28d
[Refactoring ⚙️] import 경로 수정
Mar 19, 2024
3e70b43
[Feat ✏️] SearchResultContainer 컴포넌트 생성
Mar 19, 2024
a49033b
[Feat ✏️] 필드 remove 메서드 추가
Mar 19, 2024
f71cf63
[Feat ✏️] 필드 삭제 버튼 추가 및 스타일 적용
Mar 19, 2024
4e1e76c
[Refactoring ⚙️] debounce 적용 위해 비제어 방식으로 변경
Mar 19, 2024
75e4b76
[Design 🎨] 레이아웃 div -> flex로 변경
Mar 19, 2024
0e45cd7
[Refactoring ⚙️] 타입 수정
Mar 19, 2024
0dfae9f
[Feat ✏️] fields data 배열 중 빈 배열 조사하는 유틸함수 추가
Mar 19, 2024
5addf8e
[Design 🎨] 불필요한 스타일 제거
Mar 19, 2024
12e6c2d
[Feat ✏️] 필드 기본값 수정
Mar 19, 2024
94738d9
[Refactoring ⚙️] 기술스택 선택 필드 에러 및 유효성 검사 추가
Mar 19, 2024
a8aea35
[Design 🎨] MemberAvatarCard로 컴포넌트 분리
Mar 19, 2024
cd8a384
[Refactoring ⚙️] UserSearchBox 리팩토링 적용
Mar 19, 2024
fb896e0
[Design 🎨] CloseButton 컴포넌트 생성
Mar 19, 2024
91605ce
[Refactoring ⚙️] MemberFields 리팩토링 적용
Mar 19, 2024
933ff9d
[Design 🎨] cursor pointer 추가
Mar 19, 2024
02430b8
[Design 🎨] 에러메시지 색상 추가
Mar 19, 2024
bff53c0
[Refactoring ⚙️] Field 컴포넌트 라벨 등록
Mar 19, 2024
661ad97
[Feat ✏️] startDate , endDate input 추가
Mar 19, 2024
eecf7e9
[Fix 🪛] register validate 수정
Mar 19, 2024
255919d
[Feat ✏️] startDate endDate 유효성 옵션 추가
Mar 19, 2024
7313bcc
[Feat ✏️] github link 유효성 정규표현식 추가
Mar 20, 2024
c9e24da
[Fix 🪛] 주석 제거
Mar 20, 2024
5d31de1
[Fix 🪛] 파일 변경으로 인한 타입오류 수정
Mar 20, 2024
3fcf02c
[Fix 🪛] 타입 오류 수정
Mar 20, 2024
1b9aaae
[Fix 🪛] 콘솔 삭제
Mar 20, 2024
cb39067
[Feat ✏️] regExp 폴더 생성 및 url 정규표현식 추가
Mar 20, 2024
5777e73
[Design 🎨] ErrorText 컴포넌트 추가
Mar 20, 2024
5b0509b
[Refactoring ⚙️] 유효성 옵션 추가 및 type 변경
Mar 20, 2024
ac5495c
[Refactoring ⚙️] 유효성 적용 변경 및 스타일 일부적용
Mar 20, 2024
b2a009c
Merge branch 'dev' of https://github.com/side-peek/sidepeek_frontend …
Mar 20, 2024
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
2 changes: 1 addition & 1 deletion src/components/Search/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ChangeEventHandler } from "react"
import { Input, InputProps } from "@chakra-ui/react"

interface SearchBarProps extends Omit<InputProps, "onChange" | "onInput"> {
value: string
value?: string
onChange: ChangeEventHandler<HTMLInputElement>
}

Expand Down
1 change: 1 addition & 0 deletions src/components/Search/components/SearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const SearchResult = ({
overflow="hidden"
zIndex="dropdown"
flexDir="column"
cursor="pointer"
{...props}>
{children}
</Flex>
Expand Down
10 changes: 6 additions & 4 deletions src/components/Search/hooks/useInput.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ChangeEventHandler, useCallback, useState } from "react"
import { ChangeEventHandler, useState } from "react"

export const useInput = (initialValue = "") => {
import { useDebounce } from "@hooks/useDebounce"

export const useInput = (initialValue = "", timer = 500) => {
const [inputValue, setInputValue] = useState(initialValue)

const onInput: ChangeEventHandler<HTMLInputElement> = useCallback((e) => {
const onInput: ChangeEventHandler<HTMLInputElement> = useDebounce((e) => {
const inputValue = e.target.value.trim()
setInputValue(inputValue)
}, [])
}, timer)

return [inputValue, onInput] as const
}
6 changes: 5 additions & 1 deletion src/components/Tag/components/CloseButtonTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { MdCancel } from "react-icons/md"
import { CommonTagProps } from "../types/commonTagProps"
import CommonTag from "./CommonTag"

interface CloseButtonTagProps extends Omit<CommonTagProps, "rightElement"> {
interface CloseButtonTagProps
extends Omit<CommonTagProps, "rightElement" | "onClick"> {
onClickCloseButton: MouseEventHandler
}

Expand All @@ -17,6 +18,9 @@ const CloseButtonTag = ({
return (
<CommonTag
leftElement={leftElement}
onClick={(e) => {
e.preventDefault()
}}
label={label}
rightElement={
<MdCancel
Expand Down
2 changes: 2 additions & 0 deletions src/constants/regExp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const URL_REGEX =
/^(https?):\/\/([^:/\s]+)(:([^/]*))?((\/[^\s/]+)*)?\/?([^#\s?]*)(\?([^#\s]*))?(#(\w*))?$/
17 changes: 17 additions & 0 deletions src/hooks/useDebounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useRef } from "react"

export const useDebounce = <T extends any[]>(
callback: (...params: T) => void,
time: number,
) => {
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
return (...params: T) => {
if (timer.current) clearTimeout(timer.current)

timer.current = setTimeout(() => {
callback(...params)
timer.current = null
}, time)
}
}
12 changes: 6 additions & 6 deletions src/pages/ProfileEditPage/hooks/useExample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,25 @@ export const useExample = () => {
})

const appendNewFields = () => {
append({ category: "", stacks: [] })
append({ category: "", data: [] })
}

const setCategory = (index: number, value: string) => {
setValue(`techStacks.${index}.category`, value)
}

const appendStack = (index: number, element: Skill) => {
const stacks = getValues(`techStacks.${index}.stacks`)
setValue(`techStacks.${index}.stacks`, [...stacks, element])
const stacks = getValues(`techStacks.${index}.data`)
setValue(`techStacks.${index}.data`, [...stacks, element])
}

const removeStack = (index: number, element: Skill) => {
const stacks = getValues(`techStacks.${index}.stacks`)
const stacks = getValues(`techStacks.${index}.data`)
const filtered = stacks.filter((stack) => stack.id !== element.id)
setValue(`techStacks.${index}.stacks`, [...filtered])
setValue(`techStacks.${index}.data`, [...filtered])
}

const selectedStacks = (index: number) => watch(`techStacks.${index}.stacks`)
const selectedStacks = (index: number) => watch(`techStacks.${index}.data`)

return {
fields,
Expand Down
96 changes: 63 additions & 33 deletions src/pages/ProjectEditPage/components/MemberFields/MemberFields.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { IoCloseCircle } from "react-icons/io5"
import { Box, Button, Flex, Input } from "@chakra-ui/react"

import { Button, Flex, Input } from "@chakra-ui/react"
import { ErrorMessage } from "@components/ErrorMessage/ErrorMessage"

import AvatarCard from "@components/AvatarCard/AvatarCard"

import FieldContainer from "../FieldContainer"
import CloseButton from "../styles/CloseButton"
import ErrorText from "../styles/ErrorText"
import FieldContainer from "../styles/FieldContainer"
import MemberAvatarCard from "./components/MemberAvatarCard"
import UserSearchBox from "./components/UserSearchBox"
import { useMemberFieldsMethods } from "./hooks/useMemberFieldsMethods"

Expand All @@ -17,47 +18,76 @@ const MemberFields = () => {
appendMembers,
removeMembers,
getSelectedMembers,
errors,
trigger,
} = useMemberFieldsMethods()

return (
<Flex
flexDir="column"
gap="10px">
gap="8px">
{fields.map((field, idx) => {
const selectedMembers = getSelectedMembers(idx)
register(`members.${idx}.data` as const, {
validate: (data) =>
data.length !== 0 || "팀원을 한명 이상 선택해주세요",
})

return (
<FieldContainer key={field.id}>
<IoCloseCircle
size="20"
onClick={() => deleteFields(idx)}
/>
<Input
placeholder="카테고리를 입력해주세요"
width="20rem"
{...register(`members.${idx}.category`, { required: "true" })}
/>
<UserSearchBox
onClick={({ id, nickname, profileImageUrl }) => {
appendMembers({ id, nickname, profileImageUrl }, idx)
}}
selectedMembers={getSelectedMembers(idx)}
/>
{selectedMembers?.map((member, idx) => {
return (
<AvatarCard key={idx}>
<IoCloseCircle
size="20"
onClick={() => removeMembers(member, idx)}
<Box>
<Input
placeholder="카테고리를 입력해주세요"
width="20rem"
{...register(`members.${idx}.category` as const, {
required: "분야 입력은 필수입니다",
})}
/>
<ErrorMessage
name={`members.${idx}.category` as const}
errors={errors}
render={({ message }) => <ErrorText message={message} />}
/>
</Box>

<Box>
<ErrorMessage
name={`members.${idx}.data`}
errors={errors}
render={({ message }) => <ErrorText message={message} />}
/>
<UserSearchBox
onClick={({ id, nickname, profileImageUrl }) => {
appendMembers({ id, nickname, profileImageUrl }, idx)
trigger(`members.${idx}.data` as const)
}}
selectedMembers={getSelectedMembers(idx)}
/>
</Box>
<Box>
{selectedMembers?.map((member, idx) => {
return (
<MemberAvatarCard
key={idx}
image={member.profileImageUrl || ""}
text={member.nickname}
onClick={() => {
removeMembers(member, idx)
}}
/>
<AvatarCard.Image src={member.profileImageUrl || ""} />
<AvatarCard.Header text={member.nickname} />
</AvatarCard>
)
})}
)
})}
</Box>
{idx >= 1 && <CloseButton onClick={() => deleteFields(idx)} />}
</FieldContainer>
)
})}
<Button onClick={appendNewFields}>팀원 추가하기</Button>
<Button
border="2px solid"
borderColor="blue.200"
onClick={appendNewFields}>
팀원 추가
</Button>
</Flex>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { IoCloseCircle } from "react-icons/io5"

import AvatarCard from "@components/AvatarCard/AvatarCard"

const MemberAvatarCard = ({
image,
text,
onClick,
}: {
image: string
text: string
onClick: () => void
}) => {
return (
<AvatarCard>
<IoCloseCircle
size="20"
onClick={onClick}
cursor="pointer"
/>
<AvatarCard.Image src={image || ""} />
<AvatarCard.Header text={text} />
</AvatarCard>
)
}

export default MemberAvatarCard
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Suspense } from "react"

import { Box, BoxProps, Center, Spinner } from "@chakra-ui/react"
import {
Avatar,
Box,
BoxProps,
Center,
Flex,
Spinner,
Text,
} from "@chakra-ui/react"
import { UserSummary } from "api-models"
import { useOutsideClick } from "src/components/Search/hooks/useOutsideClick"

Expand All @@ -9,6 +17,7 @@ import { useInput } from "@components/Search/hooks/useInput"

import { filterSelectedId } from "@pages/ProjectEditPage/utils/filterSelectedId"

import SearchResultContainer from "../../styles/SearchResultContainer"
import UserListFetcher from "./UserListFetcher"

interface UserSearchBoxProps extends Omit<BoxProps, "onClick" | "ref"> {
Expand Down Expand Up @@ -43,10 +52,9 @@ const UserSearchBox = ({
width="20rem"
{...props}>
<SearchBox.Input
value={inputValue}
onChange={onInput}
placeholder="닉네임을 검색해보세요"
variant="flushed"
variant="outline"
/>
<Suspense
fallback={
Expand All @@ -58,21 +66,33 @@ const UserSearchBox = ({
value={inputValue}
render={(data) => (
<SearchBox.Result isFocused={isFocused}>
{filterSelectedId(data, selectedMembers)?.map(
({ id, nickname, profileImageUrl }) => (
<Box
key={id}
fontWeight={700}
onClick={() => onClick({ id, nickname, profileImageUrl })}>
#{nickname}
<SearchResultContainer>
{inputValue && (
<Box onClick={() => handleClickNonUser(inputValue)}>
{inputValue} 추가하기
</Box>
),
)}
{inputValue && (
<Box onClick={() => handleClickNonUser(inputValue)}>
{inputValue} 추가하기
</Box>
)}
)}
{filterSelectedId(data, selectedMembers)?.map(
({ id, nickname, profileImageUrl }, idx) => (
<Flex
key={idx}
border="none"
padding="5px"
gap="10px"
onClick={() =>
onClick({ id, nickname, profileImageUrl })
}>
<Avatar
src={profileImageUrl || ""}
boxSize="10"
borderRadius="full"
objectFit="cover"
/>
<Text>{nickname}</Text>
</Flex>
),
)}
</SearchResultContainer>
</SearchBox.Result>
)}
/>
Expand Down
Loading
Loading