Skip to content

Commit

Permalink
feat: 친구 페이지 친구 삭제, 내 초대코드 조회, 친구 추가 API 연동 (#28)
Browse files Browse the repository at this point in the history
* chore: eslint @typescript-eslint/no-floating-promises off

* feat: CheckIcon 추가, CheckBox component 생성

* fix: Button component ButtonHTMLAttributes props추가

* chore: 친구 페이지 헤더 우측 icon button component 분리

* feat: 친구 제거 API 연동

* style: 친구 페이지 스타일 수정

* feat: 내 초대코드 조회 API 연동

* feat: WarningIcon 추가, WarningLine component 생성

* feat: 친구 추가 API 연동

* fix: build error 수정

* refactor: CheckBox refactoring

* refactor: 친구추가 성공, 초대코드 복사 성공시 토스트 메세지 상수화

* refactor: 내 초대코드 조회 query useGetMyInviteCode function으로 분리
  • Loading branch information
hyeseon-han authored Feb 23, 2024
1 parent 23ce6af commit f588dd2
Show file tree
Hide file tree
Showing 20 changed files with 308 additions and 47 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ module.exports = {
'@typescript-eslint/strict-boolean-expressions': 'off',
'prettier/prettier': ['error', { endOfLine: 'auto' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-floating-promises': 'off'
},
};
9 changes: 9 additions & 0 deletions src/assets/icons/CheckIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

const CheckIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M1.3938 3.16216L5.02294 6.7913L10.6062 1.20801" stroke={props.stroke ? 'current' : '#F1F2F4'} strokeWidth="1.67" strokeLinecap="round"/>
</svg>
);
export default CheckIcon;

9 changes: 9 additions & 0 deletions src/assets/icons/WarningIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

const WarningIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path fill={props.fill ? "current" : '#FB2414'} fillRule="evenodd" clipRule="evenodd" d="M12 6C12 9.31348 9.3137 12 6 12C2.6863 12 0 9.31348 0 6C0 2.68652 2.6863 0 6 0C9.3137 0 12 2.68652 12 6ZM6 2.6168C6.27671 2.6168 6.50101 2.84121 6.50101 3.11777V6.30527C6.50101 6.58184 6.27671 6.80625 6 6.80625C5.72329 6.80625 5.49899 6.58184 5.49899 6.30527V3.11777C5.49899 2.84121 5.72329 2.6168 6 2.6168ZM6 8.88223C6.27667 8.88223 6.50098 8.65781 6.50098 8.38125C6.50098 8.10469 6.27667 7.88027 6 7.88027C5.72329 7.88027 5.49899 8.10469 5.49899 8.38125C5.49899 8.65781 5.72329 8.88223 6 8.88223Z" />
</svg>
);

export default WarningIcon;
2 changes: 2 additions & 0 deletions src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ export { default as DateIcon } from './DateIcon';
export { default as ClockIcon } from './ClockIcon';
export { default as CameraIcon } from './CameraIcon';
export { default as CircleCloseIcon } from './CircleCloseIcon';
export { default as CheckIcon } from './CheckIcon';
export { default as WarningIcon } from './WarningIcon';
5 changes: 4 additions & 1 deletion src/components/atoms/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ interface ButtonProps {
className?: string;
}

const Button: React.FC<ButtonProps> = ({ text, className, onClick }) => {
const Button: React.FC<
ButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>
> = ({ text, className, onClick, ...props }) => {
return (
<button
className={`p-18 gap-12 rounded-12 heading4-semibold ${className}`}
onClick={onClick}
{...props}
>
{text}
</button>
Expand Down
18 changes: 18 additions & 0 deletions src/components/atoms/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CheckIcon } from '@/assets/icons';
import React from 'react';

const CheckBox: React.FC<{ active: boolean; onClick: () => void }> = ({
active = false,
onClick,
}) => {
const commonStyle =
'flex justify-center items-center w-[20px] h-[20px] rounded-full';
const buttonClassName = `${commonStyle} ${active ? 'bg-primary2' : 'border border-gray5'}`;
return (
<button onClick={onClick} className={buttonClassName}>
<CheckIcon stroke={active ? '#F1F2F4' : '#9299AA'} />
</button>
);
};

export default CheckBox;
1 change: 1 addition & 0 deletions src/components/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export { default as Input } from './Input';
export { default as MiniButton } from './MiniButton';
export { default as ExclamationAlertSpan } from './ExclamationAlertSpan';
export { default as Lottie } from './Lottie';
export { default as CheckBox } from './CheckBox';
14 changes: 14 additions & 0 deletions src/components/molecules/FriendshipHeaderSettingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { SettingIcon } from '@/assets/icons';

const FriendshipHeaderSettingButton: React.FC<{ onClick: () => void }> = ({
onClick,
}) => {
return (
<button onClick={onClick}>
<SettingIcon />
</button>
);
};

export default FriendshipHeaderSettingButton;
2 changes: 1 addition & 1 deletion src/components/molecules/LabelRoundBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const LabelRoundBox: React.FC<{
return (
<div className="mb-[20px] px-[20px] py-[16px] bg-white rounded-[12px]">
<p className="mb-[12px] body1-semibold text-gray8">{label}</p>
<div className="flex">{content}</div>
{content}
</div>
);
};
Expand Down
13 changes: 13 additions & 0 deletions src/components/molecules/WarningLine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { WarningIcon } from '@/assets/icons';

const WarningLine: React.FC<{ text: string }> = ({ text }) => {
return (
<div className="flex items-center">
<WarningIcon />
<p className="ml-[4px] body2-regular text-point4">{text}</p>
</div>
);
};

export default WarningLine;
1 change: 1 addition & 0 deletions src/components/molecules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { default as NavWhiteBoxItem } from './NavWhiteBoxItem';
export { default as VerticalLabelValue } from './VerticalLabelValue';
export { default as ShareInfoRowItem } from './ShareInfoRowItem';
export { default as LabelRoundBox } from './LabelRoundBox';
export { default as WarningLine } from './WarningLine';
39 changes: 24 additions & 15 deletions src/components/organisms/FriendListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
import { AngleIcon } from '@/assets/icons';
import { type ProfileEnum } from '@/types/common';
import { returnProfileImg } from '@/utils/returnProfileImg';
import Image from 'next/image';
import React from 'react';
import { CheckBox } from '@/components/atoms';
import { type FriendshipData } from '@/types/friendship';

const FriendListItem: React.FC<{
name: string;
count: number;
profileEnum: ProfileEnum;
}> = ({ name, count, profileEnum }) => {
data: FriendshipData;
possibleDelete: boolean;
onClick: () => void;
active: boolean;
}> = ({ data, possibleDelete, onClick, active }) => {
return (
<div className="flex p-[16px] mb-[12px] justify-between items-center bg-white rounded-[12px]">
<div className="flex items-center">
{/* TODO profile img ENUM res 데이터로 교체 */}
<Image
src={returnProfileImg(profileEnum)}
src={returnProfileImg('GREEN')}
width={40}
height={40}
className="w-[40px] h-[40px] aspect-square"
alt="친구 프로필"
/>
<div className="ml-[16px]">
<p className="mb-[4px] heading4-semibold text-gray7">{name}</p>
<p className="mb-[4px] heading4-semibold text-gray7">
{data.nickname}
</p>
<p className="body2-medium text-gray5">
냉장고 식자재 목록 {count}
냉장고 식자재 목록 {data.ingredientCount}
</p>
</div>
</div>
<AngleIcon
width={16}
height={16}
fill="#CCCFD7"
transform="rotate(180)"
className="z-0"
/>
{possibleDelete ? (
<CheckBox onClick={onClick} active={active} />
) : (
<AngleIcon
width={16}
height={16}
fill="#CCCFD7"
transform="rotate(180)"
className="z-0"
/>
)}
</div>
);
};
Expand Down
71 changes: 53 additions & 18 deletions src/components/templates/AddFriendTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,82 @@
import React, { useEffect, useState } from 'react';
import { LabelRoundBox, WarningLine } from '@/components/molecules';
import React, { useState } from 'react';
import {
useAddFriendship,
useGetMyInviteCode,
} from '@/hooks/queries/friendship';

import { BulletNoticeBox } from '../organisms';
import { LabelRoundBox } from '../molecules';
import { BulletNoticeBox } from '@/components/organisms';
import { MiniButton } from '@/components/atoms';
import useToast from '@/hooks/useToast';

const MY_INVITATION_CODE = 'AB12CD3EF';
const FRIEND_ADD_SUCCESS_MESSAGE = '친구 추가가 완료되었습니다.';
const CODE_COPY_SUCCESS_MESSAGE = '초대 코드가 복사되었습니다.';

const AddFriendTemplate: React.FC = () => {
const [myCode, setMyCode] = useState<string>('');
const [friendInviteCode, setFriendInviteCode] = useState<string>('');
const [warningVisible, setWarningVisible] = useState<boolean>(false);
const { showToast } = useToast();
const addFriendship = useAddFriendship({
onSuccess: () => {
showToast(FRIEND_ADD_SUCCESS_MESSAGE, 'success');
},
});

const onCopy: () => void = () => {
navigator.clipboard
.writeText(myCode)
.then(() => null)
.writeText(myInviteCode ?? '')
.then(() => {
showToast(CODE_COPY_SUCCESS_MESSAGE, 'success');
})
.catch(() => null);
};

useEffect(() => {
setMyCode(MY_INVITATION_CODE);
}, []);
const onAddFriend = () => {
if (friendInviteCode.length < 9) {
setWarningVisible(true);
} else {
addFriendship.mutate({ inviteCode: friendInviteCode });
}
};

const { inviteCode: myInviteCode } = useGetMyInviteCode();

return (
<div className="pt-[72px] px-[20px]">
<LabelRoundBox
label="내 초대 코드"
content={
<>
<div className="flex">
<span className="flex-1 outline-none mr-[10px] border-none p-[10px] bg-gray1 rounded-[6px] text-gray8 body1-medium">
{myCode}
{myInviteCode ?? ''}
</span>
<MiniButton label="복사" onClick={onCopy} variant="active" />
</>
</div>
}
/>
<LabelRoundBox
label="상대 초대 코드 입력"
content={
<>
<input
placeholder="상대 초대 코드를 입력해주세요."
className="flex-1 outline-none mr-[10px] border-none p-[10px] bg-gray1 rounded-[6px] text-gray8 body1-medium"
/>
<MiniButton label="추가" onClick={onCopy} variant="clickable" />
<div className="flex mb-[8px]">
<input
placeholder="상대 초대 코드를 입력해주세요."
className={`flex-1 mr-[10px] p-[10px] bg-gray1 rounded-[6px] text-gray8 body1-medium ${warningVisible ? 'border border-point4' : 'border-none'}`}
value={friendInviteCode}
onChange={(e) => {
setFriendInviteCode(e.target.value);
}}
maxLength={10}
/>
<MiniButton
label="추가"
onClick={onAddFriend}
variant="clickable"
/>
</div>
{warningVisible ? (
<WarningLine text="9-10자리 초대 코드를 입력해주세요." />
) : null}
</>
}
/>
Expand Down
Loading

0 comments on commit f588dd2

Please sign in to comment.