Skip to content

Commit

Permalink
Feature/edit list page (#27)
Browse files Browse the repository at this point in the history
* Feat: 콜라보레이터 20명 제한 정책 반영

* Fix: 1차QA 반영 - 페이지 이동시 카테고리 선택 풀림 해결

* Fix: 1차QA 반영 - 이메일로 콜라보레이터 검색이 불가능함을 반영

* Design: 1차QA 반영 - 리스트 생성페이지 헤더를 sticky하게 변경

* Design: 1차QA 반영 - IOS 15이상에서 button text가 blue인 이슈 해결

* Design: 1차QA 반영 - 카테고리 버튼들이 space-between이 아닌 고정간격 가지도록 수정

* Chore: 리스트 수정 경로를 위한 폴더 생성

* Chore: 팔로잉, 팔로워 목록 조회를 위한 폴더구조 만들기

* Feat: 팔로잉,팔로워 조회 페이지 공용 헤더

* Feat: 유저 프로필이미지 컴포넌트를 공용 컴포넌트로 변경

* Feat: 팔로잉, 팔로워 페이지 - 유저목록 컴포넌트 구현

* Feat: 팔로잉, 팔로워 조회 페이지 웹 퍼블리싱 완료

* Feat: 팔로잉,팔로우 페이지 - 헤더 뒤로가기 버튼 클릭시 마이피드로 이동

* Feat: 리스트 수정 페이지 - 아이템 타이틀 수정불가 정책 구현

* Feat: 헤더를 공용 컴포넌트로 분리

* Feat: 리스트 상세 조회 API

* Feat: 리스트 수정 페이지 - 리스트 수정 API 보내기 전까지 (리스트 상세조회 API로 default value와 form 채워놓기) 완료

* Style: api 함수 코드 컨벤션 반영

* Fix: 리스트 상세조회 관련 타입 충돌 해결
  • Loading branch information
Eugene-A-01 authored Feb 14, 2024
1 parent 62cc789 commit 6fd4773
Show file tree
Hide file tree
Showing 37 changed files with 834 additions and 120 deletions.
4 changes: 3 additions & 1 deletion src/app/_api/category/getCategories.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import axiosInstance from '@/lib/axios/axiosInstance';
import { CategoryType } from '@/lib/types/categoriesType';

export const getCategories = async () => {
const getCategories = async () => {
const response = await axiosInstance.get<CategoryType[]>('/categories');

return response.data;
};

export default getCategories;
4 changes: 3 additions & 1 deletion src/app/_api/list/createList.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import axiosInstance from '@/lib/axios/axiosInstance';
import { ListCreateType, ListIdType } from '@/lib/types/listType';

export const createList = async (data: ListCreateType) => {
const createList = async (data: ListCreateType) => {
const response = await axiosInstance.post<ListIdType>('/lists', data);

return response.data;
};

export default createList;
11 changes: 11 additions & 0 deletions src/app/_api/list/getListDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 리스트 조회 api
import axiosInstance from '@/lib/axios/axiosInstance';
import { ListDetailType } from '@/lib/types/listType';

//리스트 상세 페이지 리스트 조회 api
const getListDetail = async (listId?: number | undefined) => {
const response = await axiosInstance.get<ListDetailType>(`/lists/${listId}`);
return response.data;
};

export default getListDetail;
4 changes: 3 additions & 1 deletion src/app/_api/user/getUsers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import axiosInstance from '@/lib/axios/axiosInstance';
import { UserProfilesType } from '@/lib/types/userProfileType';

export const getUsers = async () => {
const getUsers = async () => {
const response = await axiosInstance.get<UserProfilesType>('/users');

return response.data;
};

export default getUsers;
10 changes: 9 additions & 1 deletion src/app/list/create/_components/CreateItem.css.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { style } from '@vanilla-extract/css';
import { style, styleVariants } from '@vanilla-extract/css';
import { body1, body3 } from '@/styles/font.css';
import { vars } from '@/styles/theme.css';

//header
export const baseButton = style([body1]);

export const headerNextButton = styleVariants({
active: [baseButton],
inactive: [baseButton, { color: vars.color.gray7, cursor: 'default' }],
});

export const article = style({
padding: '16px 20px 30px',
});
Expand Down
22 changes: 18 additions & 4 deletions src/app/list/create/_components/CreateItem.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import { useFormContext } from 'react-hook-form';

import Header from './item/Header';
import Header from '@/components/Header/Header';
import Items from './item/Items';
import * as styles from './CreateItem.css';

interface CreateItemProps {
onBackClick: () => void;
onSubmitClick: () => void;
isSubmitting: boolean;
type: 'create' | 'edit';
}

export default function CreateItem({ onBackClick, onSubmitClick, isSubmitting }: CreateItemProps) {
export default function CreateItem({ onBackClick, onSubmitClick, isSubmitting, type }: CreateItemProps) {
const {
formState: { isValid },
} = useFormContext();

return (
<div>
<Header onBackClick={onBackClick} isSubmitActive={isValid && !isSubmitting} onSubmitClick={onSubmitClick} />
<Header
title={type === 'create' ? '리스트 생성' : '리스트 수정'}
left="back"
leftClick={onBackClick}
right={
<button
className={isValid && !isSubmitting ? styles.headerNextButton.active : styles.headerNextButton.inactive}
disabled={!isValid || isSubmitting}
onClick={onSubmitClick}
>
완료
</button>
}
/>
<div className={styles.article}>
<h3 className={styles.label}>
아이템 추가 <span className={styles.required}>*</span>
Expand All @@ -27,7 +41,7 @@ export default function CreateItem({ onBackClick, onSubmitClick, isSubmitting }:
최소 3개, 최대 10개까지 아이템을 추가할 수 있어요. <br />
아이템의 순서대로 순위가 정해져요.
</p>
<Items />
<Items disabled={type === 'edit'} />
</div>
</div>
);
Expand Down
10 changes: 10 additions & 0 deletions src/app/list/create/_components/CreateList.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@ export const body = style({
justifyContent: 'space-between',
rowGap: '50px',
});

export const headerNextButton = style({
fontSize: '1.6rem',
color: '#AFB1B6',
cursor: 'default',
});

export const headerNextButtonActive = style({
fontSize: '1.6rem',
});
52 changes: 43 additions & 9 deletions src/app/list/create/_components/CreateList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import { useEffect, useState } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { useRouter } from 'next/navigation';
import { useSearchParams } from 'next/navigation';

import Header from './list/Header';
// import Header from './list/Header';
import Header from '@/components/Header/Header';
import Section from './list/Section';
import SimpleInput from './list/SimpleInput';
import ButtonSelector from './list/ButtonSelector';
Expand All @@ -19,13 +21,14 @@ import { listPlaceholder } from '@/lib/constants/placeholder';
import { BACKGROUND_COLOR } from '@/styles/Color';
import { CategoryType } from '@/lib/types/categoriesType';
import { UserProfileType } from '@/lib/types/userProfileType';
import { getCategories } from '@/app/_api/category/getCategories';
import { getUsers } from '@/app/_api/user/getUsers';
import getCategories from '@/app/_api/category/getCategories';
import getUsers from '@/app/_api/user/getUsers';
import { listDescriptionRules, listLabelRules, listTitleRules } from '@/lib/constants/formInputValidationRules';
// import { listDescription } from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.css';

interface CreateListProps {
onNextClick: () => void;
type: 'create' | 'edit';
}

/**
Expand All @@ -35,15 +38,16 @@ interface CreateListProps {
*
* @param props.onNextClick - 헤더의 '다음'버튼을 클릭했을때 동작시킬 함수
*/
function CreateList({ onNextClick }: CreateListProps) {
function CreateList({ onNextClick, type }: CreateListProps) {
const [categories, setCategories] = useState<CategoryType[]>([]);
const [users, setUsers] = useState<UserProfileType[]>([]);

const { setValue, control } = useFormContext();
const { setValue, getValues, control } = useFormContext();
const collaboIDs = useWatch({ control, name: 'collaboratorIds' });
const title = useWatch({ control, name: 'title' });
const category = useWatch({ control, name: 'category' });

const router = useRouter();
const searchParams = useSearchParams();
const isTemplateCreation = searchParams?.has('title') && searchParams?.has('category');

Expand Down Expand Up @@ -76,12 +80,33 @@ function CreateList({ onNextClick }: CreateListProps) {
return (
<div>
{/* 헤더 */}
<Header isNextActive={title && category} onClickNext={onNextClick} />
<Header
title={type === 'create' ? '리스트 생성' : '리스트 수정'}
left="close"
leftClick={() => {
router.back();
}}
right={
<button
className={title && category ? styles.headerNextButtonActive : styles.headerNextButton}
disabled={!title && !category}
onClick={onNextClick}
>
다음
</button>
}
/>

<div className={styles.body}>
{/* 리스트 제목 */}
<Section title="타이틀" isRequired={true}>
<SimpleInput type="short" name="title" placeholder={listPlaceholder.title} rules={listTitleRules} />
<SimpleInput
type="short"
name="title"
placeholder={listPlaceholder.title}
rules={listTitleRules}
defaultValue={getValues('title')}
/>
</Section>

{/* 리스트 소개 */}
Expand All @@ -91,6 +116,7 @@ function CreateList({ onNextClick }: CreateListProps) {
name="description"
placeholder={listPlaceholder.description}
rules={listDescriptionRules}
defaultValue={getValues('description')}
></SimpleInput>
</Section>

Expand All @@ -101,7 +127,7 @@ function CreateList({ onNextClick }: CreateListProps) {
onClick={(item: CategoryType) => {
setValue('category', item.nameValue);
}}
defaultValue={searchParams?.get('category')}
defaultValue={category}
/>
</Section>

Expand All @@ -125,13 +151,20 @@ function CreateList({ onNextClick }: CreateListProps) {
collaboIDs.filter((collaboId: number) => collaboId !== userId)
);
}}
rules={{
maxNum: {
value: 20,
errorMessage: `콜라보레이터는 최대 20명까지 지정할 수 있어요.`,
},
}}
defaultValue={getValues('collaboratorIds')}
/>
</Section>

{/* 배경 색상 */}
<Section title="배경 색상" isRequired={true}>
<ColorSelector
defaultColor={BACKGROUND_COLOR.WHITE}
defaultColor={getValues('backgroundColor')}
colors={Object.values(BACKGROUND_COLOR)}
onClick={(color: string) => {
setValue('backgroundColor', color);
Expand All @@ -149,6 +182,7 @@ function CreateList({ onNextClick }: CreateListProps) {
onClick={(b: boolean) => {
setValue('isPublic', b);
}}
defaultValue={getValues('isPublic')}
/>
</Section>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/app/list/create/_components/item/Items.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const title = style([
placeholder,
{
flexGrow: 1,
backgroundColor: 'transparent',
':disabled': { cursor: 'not-allowed' },
},
]);

Expand Down
7 changes: 6 additions & 1 deletion src/app/list/create/_components/item/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ const ensureHttp = (link: string) => {
// return domain;
// };

export default function Items() {
interface ItemsProps {
disabled?: boolean;
}

export default function Items({ disabled }: ItemsProps) {
const [currentLink, setCurrentLink] = useState<string>('');
const {
register,
Expand Down Expand Up @@ -107,6 +111,7 @@ export default function Items() {
autoComplete="off"
maxLength={100}
{...register(`items.${index}.title`, itemTitleRules)}
disabled={disabled}
/>
}
commentTextArea={
Expand Down
4 changes: 2 additions & 2 deletions src/app/list/create/_components/list/ButtonSelector.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { style } from '@vanilla-extract/css';

export const container = style({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
columnGap: '12px',

overflow: 'auto',
whiteSpace: 'nowrap',
Expand All @@ -16,11 +14,13 @@ export const container = style({

export const button = style({
height: '40px',
marginRight: '12px',
padding: '8px 12px',

fontSize: '1.6rem',
fontWeight: '600',

color: '#000',
backgroundColor: 'transparent',

whiteSpace: 'nowrap',
Expand Down
12 changes: 8 additions & 4 deletions src/app/list/create/_components/list/ButtonSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import * as styles from './ButtonSelector.css';
import { CategoryType } from '@/lib/types/categoriesType';

interface ButtonSelectorProps {
list: CategoryType[];
onClick: (item: CategoryType) => void;
defaultValue?: string | null;
defaultValue: string;
}

/**
Expand All @@ -18,14 +18,18 @@ interface ButtonSelectorProps {
* @param defaultValue - 기본으로 선택되어있는 요소
*/
function ButtonSelector({ list, onClick, defaultValue }: ButtonSelectorProps) {
const [selectedButton, setSelectedButton] = useState<string>(defaultValue || '');
const [selectedButton, setSelectedButton] = useState<string>(defaultValue);

useEffect(() => {
setSelectedButton(defaultValue);
}, [defaultValue]);

return (
<div className={styles.container}>
{list.map((item) => (
<button
key={item.codeValue}
className={`${styles.button} ${item.nameValue === selectedButton ? styles.buttonActive : ''}`}
className={`${styles.button} ${item.nameValue.toLocaleLowerCase() === selectedButton.toLocaleLowerCase() ? styles.buttonActive : ''}`}
onClick={() => {
onClick(item);
setSelectedButton(item.nameValue);
Expand Down
8 changes: 6 additions & 2 deletions src/app/list/create/_components/list/ColorSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import * as styles from './ColorSelector.css';

interface ColorSelectorProps {
Expand All @@ -19,12 +19,16 @@ interface ColorSelectorProps {
function ColorSelector({ defaultColor, colors, onClick }: ColorSelectorProps) {
const [selectedColor, setSelectedColor] = useState(defaultColor);

useEffect(() => {
setSelectedColor(defaultColor);
}, [defaultColor]);

return (
<div className={styles.backgroundContainer}>
{colors.map((color) => (
<button
key={color}
className={`${styles.colorCircle} ${selectedColor === color && styles.selectedColor}`}
className={`${styles.colorCircle} ${selectedColor.toLocaleLowerCase() === color.toLocaleLowerCase() && styles.selectedColor}`}
style={{ backgroundColor: color }}
onClick={() => {
onClick(color);
Expand Down
7 changes: 7 additions & 0 deletions src/app/list/create/_components/list/Header.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ export const header = style({
paddingLeft: '20px',
paddingRight: '20px',

position: 'sticky',
top: '0',
left: '0',
zIndex: '10',

display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',

backgroundColor: '#fff',

borderBottom: '1px solid rgba(0, 0, 0, 0.10)',
});

Expand Down
Loading

0 comments on commit 6fd4773

Please sign in to comment.