Skip to content

Commit

Permalink
Feat: #96 크루 리스트 조회, 크루 상세페이지 조회, 크루 생성 (#132)
Browse files Browse the repository at this point in the history
* Refactor: Tabs 컴포넌트 공통화

* Feat: 크루 헤더 및 탭 추가

* Fix: 수정된 Tabs 컴포넌트에 맞게 props 및 경로 수정

* Chore : 이미지 호스트 변경

* Feat: 크루 리스트 아이템 구현

* Feat : 정렬 옵션 구현

* Feat : 크루 페이지 구현

* Feat: 크루 api 수정

* Feat :  크루 상세페이지 조회

* Move :  BottomSheet 공용 컴포넌트로 이동 및 수정

내부에 사용하는 Form을 prop으로 받아 사용합니다

* Feat : 크루 가입신청 기능

* Feat : 가입 질문 렌더링

* Feat : 전체/내크루 리스트 아이템 컴포넌트 분리

* Feat : 가입요청 후 바텀시트 닫기

* Fix : 무한스크롤 페이징 수정

* Feat : 채팅 경로 및 UI

* Feat : 크루 생성 멀티스텝 폼

* Fix : 인풋 값 입력시 다음버튼 활성화

* Chore : 콘솔 제거

* Feat : 지역 선택 체크박스

* Fix : 타입수정

* Feat : 크루 생성 컴포넌트 구조 수정

* Refactor : 중복코드 수정

* Feat : 단계별 컴포넌트 정의

* Feat :  number input 디폴트 ui 삭제

* Feat : 크루 상세정보 데이터 추가

* Feat : 크루생성 폼 컴포넌트화

* Feat : 크루 이름, 소개, 태그 입력

* Feat : 크루 기반 지역 선택

* Feat : 가입형식 지정

* Feat : 인원, 연령대, 성별 선택

* Feat : 크루생성 api 설정

* Fix : 크루리스트 무한스크롤 로직 수정

* Feat : 크루 대표 이미지 추가 컴포넌트

* Fix : api 전송 형식 변경

* Feat : 렌더되는 정보 추가

* Feat : 옵션 배열 추가

* Feat : 태그선택 필수 및 최대 3개 제한

* Chore : 라벨 스타일 수정

* Feat : number 커스텀 입력 UX 개선

* Feat : 크루리스트 옵션 선택 추가

* Feat : 크루 생성 성공 후 리스트로 이동

* Fix : api 스키마에 일치

* Feat : 지역 배열 렌더링 수정

* Feat : 가입질문 UI 수정

* Feat : 엔드포인트 조건 수정

* Update .eslintrc.json

* Chore : EsLint Rule 재설정

* Fix: 빌드에러 수정

* Remove : 미사용 컴포넌트 삭제

* Feat : 가입정보 UI 수정

* Design : 간격 및 이미지 높이 수정

* Feat : 크루 생성 후 리스트 데이터 mutate

* Design : 크루목록 아이템 간격 및 렌더방식 수정

* Feat : 일정 옵션 버튼 컴포넌트 생성

* Feat : 멤버 목록 구현

* Fix : RESTful 규칙에 맞게 경로 수정

* Fix : 배열 map key값 수정

* Fix : RESTful 규칙에 맞게 경로 수정

* Chore : 상수 컨벤션 수정

* Feat : 크루멤버목록 api

* Feat : 크루 생성 후 크루목록 갱신 (mutate)

* Feat : 크루 수정 페이지

* Feat : 선택된 태그 유지

* Chore : 임포트 수정

* Chore : 상수 컨벤션으로 변경

* Fix : 컴포넌트명 파일명과 일치

* Feat : 챕터 변경 시 입력값 유지

* Chore : 임포트 수정

* Chore : 미완성 기능 비활성화
  • Loading branch information
joanShim authored Apr 24, 2024
1 parent 4f75b44 commit dfcf56f
Show file tree
Hide file tree
Showing 68 changed files with 2,684 additions and 72 deletions.
32 changes: 2 additions & 30 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"es2021": true,
"jest": true
},
"extends": ["next/core-web-vitals", "airbnb", "airbnb-typescript"],
"extends": ["next/core-web-vitals"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
Expand All @@ -17,36 +17,8 @@
"plugins": ["react", "@typescript-eslint"],
"rules": {
"react-hooks/exhaustive-deps": "off",
"react/jsx-props-no-spreading": "off",
"linebreak-style": 0,
"import/no-extraneous-dependencies": "off",
"import/prefer-default-export": ["off", { "target": "single" }],
"object-curly-newline": "off",
"arrow-parens": "off",
"arrow-body-style": "off",
"@typescript-eslint/no-shadow": "off",
"operator-linebreak": "off",
"react/require-default-props": "off",
"implicit-arrow-linebreak": "off",
"@typescript-eslint/naming-convention": "off",
"consistent-return": "off",
"@typescript-eslint/indent": "off",
"jsx-a11y/control-has-associated-label": "off",
"react/jsx-wrap-multilines": "off",
"react/jsx-indent": "off",
"react/no-invalid-html-attribute": "off",
"eslint-plugin-import/no-cycle": "off",
"import/no-cycle":"off",
"no-param-reassign":"off",
"@typescript-eslint/no-unused-vars": "warn", //사용안한 변수는 경고처리
"react/jsx-curly-newline": "off", // jsx안에 }를 새로운 라인에 사용할 수 있다.
"@typescript-eslint/no-use-before-define": ["warn"], // 선언하기 전에 사용 한다면 경고
"jsx-a11y/label-has-associated-control": [
2,
{
"labelAttributes": ["htmlFor"]
}
]
"react/display-name": "off"
},
"globals": {
"React": "writable"
Expand Down
Binary file added assets/images/crewLeader.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@mui/material": "^5.15.2",
"@mui/material-nextjs": "^5.15.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useRef } from 'react';
import useCss from '@/hooks/common/useCss';
import { BottomSheet, BottomSheetRef } from 'react-spring-bottom-sheet';
import type { BottonSheetProps } from '@/types/community/bottomSheet';
import Form from '@/app/_components/common/Form';
import type { BottomSheetProps } from '@/types/community/bottomSheet';

function BottomSheetContainer({ ...props }: BottonSheetProps) {
function BottomSheetContainer({
ContentComponent,
isPost,
...props
}: BottomSheetProps) {
useCss('https://unpkg.com/react-spring-bottom-sheet/dist/style.css');

const { bottomSheetTitle, isSheetOpen, setIsSheetOpen, onDisMiss } = props;
Expand All @@ -31,7 +34,10 @@ function BottomSheetContainer({ ...props }: BottonSheetProps) {
onDismiss={onDisMiss}
expandOnContentDrag={isSheetOpen}
>
<Form setIsSheetOpen={setIsSheetOpen} isPost />
<ContentComponent
setIsSheetOpen={setIsSheetOpen}
{...(isPost && { isPost })}
/>
</BottomSheet>
);
}
Expand Down
36 changes: 28 additions & 8 deletions src/app/_components/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ import * as M from '@/app/_components/ui/menubars';

import { useState } from 'react';
import { useRouter, useParams } from 'next/navigation';
import { ChevronLeft, MoreVertical, X } from 'lucide-react';
import { ChevronLeft, MoreVertical, X, Plus } from 'lucide-react';
import { usePostsState } from '@/store/community/postsStore';
import { MODAL } from '@/constants/ui/common/modal';
import { useDebouncedCallback } from 'use-debounce';
import { useToast } from '@/app/_components/ui/use-toast';

function Header({ ...props }: Partial<HeaderProps>) {
const { title, isBack, isExit, isEllipsis, editHandler, exitHandler } = props;
const {
title,
isBack,
isExit,
isEllipsis,
editHandler,
routeTo,
} = props;
const { toast } = useToast();
const { categoryId } = usePostsState();
const { mutate } = usePostListApi.useGetPostList(categoryId);
Expand Down Expand Up @@ -55,7 +62,7 @@ function Header({ ...props }: Partial<HeaderProps>) {
};

return (
<header className="sticky top-0 flex items-center justify-center h-12 bg-white z-10">
<header className="sticky top-0 flex items-center justify-center h-12 bg-white z-10 relative">
<button
type="button"
onClick={onBackHandler}
Expand Down Expand Up @@ -97,12 +104,25 @@ function Header({ ...props }: Partial<HeaderProps>) {
)}
</M.Menubar>
)}
{isExit && (
<button type="button" onClick={exitHandler}>
<X />
</button>
)}
</div>
{isExit && (
<button
type="button"
className={buttonClassName(isBack)}
onClick={onBackHandler}
>
<X />
</button>
)}
{routeTo && (
<button
type="button"
className={buttonClassName(isBack)}
onClick={() => router.push(routeTo)}
>
<Plus color="#855AFF" />
</button>
)}
</header>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
'use client';

import clsx from 'clsx';
import TABS from '@/constants/community/tabs';
import { usePostsState } from '@/store/community/postsStore';
import { Tab, TabsProps } from '@/types/common/tabs';

function Tabs() {
const { categoryId, setCategoryId } = usePostsState();
function Tabs({ tabs, useStateHook }: TabsProps) {
const { categoryId, setCategoryId } = useStateHook();

const liClassName = (id: number) => {
return clsx('flex border-b-4', {
return clsx('flex flex-1 border-b-4', {
'border-b-purple-600': categoryId === id,
'border-b-white': categoryId !== id,
});
};

const buttonClassName = (id: number) => {
return clsx('w-full h-full flex justify-center items-center', {
return clsx('w-full h-full flex justify-center items-center bg-white', {
'text-disabled': categoryId !== id,
});
};
Expand All @@ -25,8 +24,8 @@ function Tabs() {
};

return (
<ul className="grid grid-cols-3 h-12 pt-1">
{Object.values(TABS).map(tab => (
<ul className="flex h-12">
{Object.values(tabs).map((tab: Tab) => (
<li key={tab.title} className={liClassName(tab.id)}>
<button
type="button"
Expand Down
30 changes: 30 additions & 0 deletions src/app/_components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client"

import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"

import { cn } from "@/lib/utils"

const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName

export { Checkbox }
11 changes: 8 additions & 3 deletions src/app/service/community/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import { useState } from 'react';
import PostList from '@/app/service/community/_components/PostList';
import SearchBar from '@/app/service/community/_components/SearchBar';
import Tabs from '@/app/service/community/_components/Tabs';
import Tabs from '@/app/_components/common/Tabs';
import Floating from '@/app/_components/buttons/Floating';
import Header from '@/app/_components/common/Header';
import HEADER from '@/constants/ui/common/header';
import BottomSheetContainer from '@/app/service/community/_components/BottomSheetContainer';
import BottomSheetContainer from '@/app/_components/common/BottomSheetContainer';
import CommunityModal from '@/app/service/community/_components/CommunityModal';
import TABS_COMMUNITY from '@/constants/community/tabs';
import { usePostsState } from '@/store/community/postsStore';
import Form from '@/app/_components/common/Form';

function Page() {
const bottomSheetTitle = '게시글 작성';
Expand Down Expand Up @@ -38,14 +41,16 @@ function Page() {
setIsSearchingFocus={setIsSearchingFocus}
setSearchText={setSearchText}
/>
<Tabs />
<Tabs tabs={TABS_COMMUNITY} useStateHook={usePostsState} />
{!isSearchingFocus && <PostList />}
{isSearchingFocus && <CommunityModal searchText={searchText} />}
<BottomSheetContainer
ContentComponent={Form}
bottomSheetTitle={bottomSheetTitle}
isSheetOpen={isSheetOpen}
setIsSheetOpen={setIsSheetOpen}
onDisMiss={onDisMiss}
isPost
/>
{!isSearchingFocus && <Floating setIsSheetOpen={setIsSheetOpen} />}
</div>
Expand Down
89 changes: 89 additions & 0 deletions src/app/service/crew/[id]/_components/MemberList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { ChevronLeft } from 'lucide-react';
import { useEffect, useState } from 'react';
import getCrewMembers from '../api/getCrewMembers';
import { CrewMembersProps } from '@/types/crew/crew';
import Image from 'next/image';
import crewLeader from '$/images/crewLeader.png';

type MemberListProps = {
crewId: number;
setIsMemberListClicked: React.Dispatch<React.SetStateAction<boolean>>;
};

function MemberList({ setIsMemberListClicked, crewId }: MemberListProps) {
const [crewMembers, setCrewMembers] = useState<CrewMembersProps[]>([]);
useEffect(() => {
const fetchCrewMembers = async () => {
try {
const response = await getCrewMembers(crewId);
if (response) {
setCrewMembers(response);
}
} catch (error) {
console.error('Failed to fetch crew members:', error);
}
};

fetchCrewMembers();
}, [crewId]);

console.log(crewMembers);

const sortedCrewMembers = [...crewMembers].sort((a, b) => {
if (a.is_crew_creator && !b.is_crew_creator) {
return -1;
} else if (!a.is_crew_creator && b.is_crew_creator) {
return 1;
}
return 0;
});

return (
<div className="h-dvh">
<header className="sticky top-0 flex items-center justify-center h-12 bg-white z-10 relative">
<button
type="button"
onClick={() => {
setIsMemberListClicked(false);
}}
className={'absolute left-4'}
>
<ChevronLeft />
</button>
크루원 목록
</header>
<ul className="p-[16px] flex flex-col gap-[20px]">
{sortedCrewMembers &&
sortedCrewMembers.map((member) => (
<li key={member.id}>
<div className="flex w-full h-fit gap-[10px]">
<div className="size-[40px] relative shrink-0">
<Image
src={member.profile_image}
alt="크루 멤버 프로필 이미지"
fill={true}
className="rounded-full my-auto"
/>
</div>
<div className="flex flex-col">
<span className="text-[14px] font-medium flex items-center">
{member.is_crew_creator && (
<div className="relative size-[18px] mr-[6px]">
<Image src={crewLeader} fill={true} alt="크루장" />
</div>
)}
{member.nickname}
</span>
<span className="text-[#C4C4C4] text-sm line-clamp-1">
안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요
</span>
</div>
</div>
</li>
))}
</ul>
</div>
);
}

export default MemberList;
Loading

0 comments on commit dfcf56f

Please sign in to comment.