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

Feat: ProductItem 시간, style, Type 변경, 프로필 이미지 추가 #123

Merged
merged 13 commits into from
Oct 11, 2024

Conversation

aquaman122
Copy link
Contributor

@aquaman122 aquaman122 commented Oct 7, 2024

💡 작업 내용

  • ProductItem 시간, style, Type 변경
  • 프로필 사진 추가
  • 로그아웃 시 리프레시 만료될때 에러 해결

💡 자세한 설명

🛠️ ProductItem 시간, style, Type 변경

// ProductItem.tsx
import jordanBlackImage from '@/assets/images/jordan_black.jpeg';
import TimeLabel from './atomic/TimeLabel';

export interface ProductProps {
  id: number;
  name: string;
  minPrice: number;
  timeRemaining?: number;
  participantCount?: number;
  isParticipating?: boolean;
  likeCount?: number;
  isLiked?: boolean;
  status?: string;
  createdAt?: string;
  imageUrl?: string;
}

const ProductItem = ({
  product,
  children,
}: {
  product: ProductProps;
  children: React.ReactNode;
}) => {

  return (
    <div key={product.id} className="mb-4">
      <div className="flex flex-col">
        <div className="w-full h-auto mb-4">
          <div className="relative">
            <img
              className="object-cover w-full h-[15rem] rounded-t"
              src={`${product.imageUrl ? product.imageUrl : jordanBlackImage}`}
              alt="Jordan Black Shoes"
            />
            {product.timeRemaining && <TimeLabel time={product.timeRemaining} />}
          </div>
        </div>

        <div className="flex flex-col gap-[8px]">
          <div>
            <p className="text-sm font-semibold">{product.name}</p>
          </div>
          <div className="flex flex-col">{children}</div>
        </div>
      </div>
    </div>
  );
};

export default ProductItem;

🛠️ 프로필 사진 추가

import Layout from '@/components/layout/Layout';
import Button from '@/components/common/Button';
import FormField from '@/components/common/form/FormField';
import { useNavigate } from 'react-router-dom';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { useEffect, useRef, useState } from 'react';
import { useEditProfile } from '@/hooks/useProfile';
import { UserProfile } from '@/@types/user';
import { useQuery } from '@tanstack/react-query';
import { queryKeys } from '@/constants/queryKeys';
import { nicknameCheck } from '@/components/login/queries';
import ProfileImageUploader from '@/components/profile/ProfileImageUploader';

const ProfileEdit = () => {
  const formRef = useRef<HTMLFormElement>(null);
  const navigate = useNavigate();
  const [isNicknameChecked, setIsNicknameChecked] = useState(false);
  const [profileImage, setProfileImage] = useState<string | null>(null);
  const [profileFile, setProfileFile] = useState<File | null>(null);
  const [useDefaultImage, setUseDefaultImage] = useState(false);
  const { control, watch, handleSubmit, handleEditProfile, originalNickname, userProfileImageUrl } = useEditProfile();
  const nickname = watch('nickname');

  const { refetch: checkNickname } = useQuery({
    queryKey: [queryKeys.NICKNAME, nickname],
    queryFn: () => nicknameCheck(nickname),
    enabled: false,
  });

  const handleSubmitClick = () => {
    if (formRef.current) {
      formRef.current.dispatchEvent(
        new Event('submit', { cancelable: true, bubbles: true }),
      );
    }
  };

  const onSubmit = (data: UserProfile) => {
    const { nickname, bio, link } = data;
    if (isNicknameChecked || nickname === originalNickname) {
      const formData = new FormData();
      const submitData = {
        nickname,
        bio,
        link,
        useDefaultImage
      };

      if (profileFile) {
        formData.append('file', profileFile);
        setUseDefaultImage(false);
      } else {
        setUseDefaultImage(true);
      }
      formData.append('request',
        new Blob([JSON.stringify(submitData)], {
          type: 'application/json',
        })
      );
      handleEditProfile(formData);
    } else {
      // 에러 띄우기 닉네임 중복 확인을 해주세요.
      alert('닉네임바꿔');
    }
  };

  const onNicknameCheck = async () => {
    if (nickname === originalNickname) {
      setIsNicknameChecked(true);
      // 띄우기
      alert('닉네임 변경 안됨');
      return;
    }

    const { data } = await checkNickname();
    const { isAvailable } = data;
    setIsNicknameChecked(isAvailable);
    
    if (isAvailable) {
      // 사용 가능한 닉네임입니다. 띄워주기
      alert('사용 가능')
    } else {
      // 이미 사용중인 닉네임입니다. 띄워주기
      alert('이미 사용 중')
    }
  };

  useEffect(() => {
    if (userProfileImageUrl) {
      setProfileImage(userProfileImageUrl);
    }
  }, [userProfileImageUrl]);

  return (
    <Layout>
      <Layout.Header title="프로필 수정" handleBack={() => navigate('/user')} />
      <Layout.Main>
        <form
          ref={formRef}
          className="flex flex-col px-4 py-6 space-y-4"
          onSubmit={handleSubmit(onSubmit)}
        >
          <h2 className="pb-4 text-lg font-bold">프로필 정보</h2>
          <ProfileImageUploader 
            file={profileFile}
            setFile={setProfileFile}
            image={profileImage}
            setImage={setProfileImage}
          />
          <div className='flex items-end gap-4'>
            <div className='flex-1 w-4/5'>
              <FormField
                label="닉네임 *"
                name="nickname"
                control={control}
                render={(field) => (
                  <Input
                    id="닉네임 *"
                    type="text"
                    placeholder="닉네임을 입력해주세요 (공백 제외 15글자 이내)"
                    className="focus-visible:ring-cheeseYellow"
                    {...field}
                  />
                )}
              />
            </div>
            <div>
              <Button type='button' className='h-10' onClick={onNicknameCheck}>중복확인</Button>
            </div>
          </div>
          <FormField
            label="자기소개"
            name="bio"
            control={control}
            render={(field) => (
              <Textarea
                id="자기소개"
                placeholder="자기소개를 입력해주세요"
                className="focus-visible:ring-cheeseYellow"
                {...field}
              />
            )}
          />
          <FormField
            label="링크"
            name="link"
            control={control}
            render={(field) => (
              <Input
                id="링크"
                type="text"
                placeholder="링크를 입력해주세요"
                className="focus-visible:ring-cheeseYellow"
                {...field}
              />
            )}
          />
        </form>
      </Layout.Main>
      <Layout.Footer type="single">
        <Button
          type="submit"
          className="w-full h-[47px] rounded-lg"
          color="cheeseYellow"
          onClick={handleSubmitClick}
        >
          프로필 수정 완료
        </Button>
      </Layout.Footer>
    </Layout>
  );
};

export default ProfileEdit;

🛠️ 로그아웃 시 리프레시 만료될때 에러 해결

export const logout = async () => {
  try {
    await refreshToken();
    await httpClient.post(API_END_POINT.LOGOUT, { withCredentials: true });
    removeToken();
  } catch (error) {
    throw error;
  }
};
스크린샷 2024-10-08 오후 11 02 35 스크린샷 2024-10-08 오후 11 02 30

📗 참고 자료 (선택)

📢 리뷰 요구 사항 (선택)

🚩 후속 작업 (선택)

✅ 셀프 체크리스트

  • PR 제목을 형식에 맞게 작성했나요?
  • 브랜치 전략에 맞는 브랜치에 PR을 올리고 있나요? (master/main이 아닙니다.)
  • 이슈는 close 했나요?
  • Reviewers, Labels, Projects를 등록했나요?
  • 작업 도중 문서 수정이 필요한 경우 잘 수정했나요?
  • 테스트는 잘 통과했나요?
  • 불필요한 코드는 제거했나요?

closes #이슈번호

@aquaman122 aquaman122 self-assigned this Oct 7, 2024
@aquaman122 aquaman122 added the ✨feature 구현, 개선 사항 관련 부분 label Oct 7, 2024
Copy link
Contributor

@CLOUDoort CLOUDoort left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다.
수정사항만 수정해주세요!

className="object-cover w-full h-[10rem] rounded-t"
src={`${product.cdnPath ? product.cdnPath : jordanBlackImage}`}
className="object-cover w-full h-[15rem] rounded-t"
src={`${product.imageUrl ? product.imageUrl : jordanBlackImage}`}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기본이미지 설정은 이제 필요없을 듯 합니다!
이미지 에러 해결했고, 이미지 한 장은 무조건 있으니까요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵!

className="object-cover w-full h-[10rem] rounded-t"
src={`${product.cdnPath ? product.cdnPath : jordanBlackImage}`}
className="object-cover w-full h-[15rem] rounded-t"
src={`${product.imageUrl ? product.imageUrl : jordanBlackImage}`}
alt="Jordan Black Shoes"
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alt 값도 수정이 필요해보입니다.

@@ -8,7 +8,7 @@ const TimeLabel = ({ time }: { time: number }) => {
aria-label="시간"
className={`absolute bottom-0 w-full pt-1 text-center bg-white opacity-80 ${color} border-b-2`}
>
{`${formattedTime}시간 남음`}
{formattedTime === 0 ? null :`${formattedTime}시간 남음`}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 time이 0이되면 경매 종료 문구로 바꿔놓겠습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 넵넵

@@ -22,7 +22,7 @@ const ProductItem = ({ product, children }: { product: ProductProps; children: R
<div className='flex flex-col'>
<div className='w-full h-auto mb-4'>
<div className='relative'>
<img className='object-cover w-full h-[15rem] rounded-t' src={`${product.cdnPath ? product.cdnPath : jordanBlackImage}`} alt='Jordan Black Shoes' />
<img className='object-cover w-full h-[15rem] rounded-t' src={`${product.imageUrl ? product.imageUrl : jordanBlackImage}`} alt='Jordan Black Shoes' />
{product.timeRemaining && <TimeLabel time={product.timeRemaining} />}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 위와 마찬가지로 Default image필요없을 것 같습니다.

@@ -32,7 +32,7 @@ const OrderLostProduct = ({ product }: { product: IUserAuctionLostItem }) => {
<LuUsers className='text-gray-500' />
<p className='text-sm text-gray-500'>가장 높은 금액</p>
</div>
<p className='ml-4 font-semibold'>{`${(product.highestBid).toLocaleString()}원`}</p>
<p className='ml-4 font-semibold'>{`${(product.highestAmount).toLocaleString()}원`}</p>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숫자를 원으로 바꾸는 함수를 util 함수에 작성해놨으니 그거 사용하시면 됩니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵!

@@ -18,7 +18,7 @@ const OrderWonProduct = ({ product }: { product: IUserAuctionWonItem }) => {
<IoPricetagsOutline className='text-gray-500' />
<p className='text-sm text-gray-500'>시작가</p>
</div>
<p className='ml-4 font-semibold'>{`${product.minPrice.toLocaleString()}원`}</p>
<p className='ml-4 font-semibold'>{`${product.minPrice ? product.minPrice.toLocaleString() : 'N/A'}원`}</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숫자를 원으로 바꾸는 함수를 util 함수에 작성해놨으니 그거 사용하시면 됩니다.

@@ -32,7 +32,7 @@ const OrderWonProduct = ({ product }: { product: IUserAuctionWonItem }) => {
<LuUsers className='text-gray-500' />
<p className='text-sm text-gray-500'>최종 낙찰금액</p>
</div>
<p className='ml-4 font-semibold'>{`${(product.winningBid).toLocaleString()}원`}</p>
<p className='ml-4 font-semibold'>{`${product.winningAmount.toLocaleString()}원`}</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숫자를 원으로 바꾸는 함수를 util 함수에 작성해놨으니 그거 사용하시면 됩니다.

Copy link
Contributor

@CLOUDoort CLOUDoort left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다.

@@ -18,7 +18,7 @@ const ROUTERS = Object.freeze({
ITEM: '/auctions/pre-auction',
EDIT: '/auctions/pre-auction/edit',
},
ADDRESSBOOK: '/addressbook',
ADDRESSBOOK: '/shipping',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

라우터는 이거아니었나요?? /auctions/{auctionId}/shipping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 그렇네요!

@aquaman122 aquaman122 changed the title Feat: ProductItem 시간, style, Type 변경 Feat: ProductItem 시간, style, Type 변경, 프로필 이미지 추가 Oct 8, 2024
@aquaman122 aquaman122 merged commit 8676f07 into dev Oct 11, 2024
@aquaman122 aquaman122 deleted the feat/nickname-check branch October 11, 2024 10:00
@aquaman122 aquaman122 restored the feat/nickname-check branch October 14, 2024 12:24
@aquaman122 aquaman122 deleted the feat/nickname-check branch October 14, 2024 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨feature 구현, 개선 사항 관련 부분
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants