본문 바로가기

카테고리 없음

Fiesta Tour_6일차 ( 프로필 수정 및 내 게시글 리스트 확인 , 상세페이지 이동 )

동작 원리 및 코드 내용 검색해보기

  • return new Intl.NumberFormat('ko-KR').format(price);
  • {Object.entries(post.tag).map(([key, value]) => (
    <li key={key}>{value}</li> const code = searchParams.get('code');
  •   useEffect(() => {
        // 인증 상태 변경 감지
        const { data: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
          if (event === 'PASSWORD_RECOVERY') {
            setShowResetForm(true);
          }
        });
  •   // 컴포넌트 언마운트 시 리스너 정리
        return () => {
          if (authListener && typeof (authListener as any).unsubscribe === 'function') {
            (authListener as any).unsubscribe();
          }
        };
      }, [supabase]);


     

프로필 이미지 업로드 문제

테이블처럼 버켓에도 정책 설정하여 오류 해결

 

 

하지만 업로드 이미지가 이상하게 보이는 버그 발생

-> 이미지 이름이 유저 고유 id 값으로 고정되므로 이미지 삭제 후 재업로드하고 불러오는 방식으로 해결

'use client';

import axios from 'axios';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { createClient } from '@/utils/supabase/client';
import { API_MYPAGE_PROFILE } from '@/utils/apiConstants';
import Image from 'next/image';

type Profile = {
  id: string;
  name: string;
  email: string;
  avatar: string;
};

const ProfileForm = ({ userId }: { userId: string }) => {
  const [profile, setProfile] = useState<Profile | null>(null);
  const [nickname, setNickname] = useState('');
  const [email, setEmail] = useState('');
  const [imageUrl, setImageUrl] = useState('');
  const router = useRouter();
  const supabase = createClient();

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();
    if (profile) {
      await axios.put(API_MYPAGE_PROFILE(userId), { id: profile.id, name: nickname, email, avatar: imageUrl });
      router.back();
    }
  };

  const handleImageChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file) {
      const fileExt = file.name.split('.').pop();
      const fileName = `${userId}.${fileExt}`;
      const filePath = `profile_images/${fileName}`;

      // 기존 파일 삭제
      const existingImagePath = profile?.avatar?.split('/').pop();
      if (existingImagePath && existingImagePath !== fileName) {
        await supabase.storage.from('users').remove([`profile_images/${existingImagePath}`]);
      }

      // 새로운 파일 업로드
      const { error: uploadError } = await supabase.storage.from('users').upload(filePath, file, {
        upsert: true
      });

      if (uploadError) {
        console.error('Failed to upload image:', uploadError.message);
        return;
      }

      const { data: publicUrlData } = supabase.storage.from('users').getPublicUrl(filePath);
      if (publicUrlData) {
        setImageUrl(publicUrlData.publicUrl);

        await axios.put(API_MYPAGE_PROFILE(userId), { id: userId, avatar: publicUrlData.publicUrl });

        // 프로필 데이터를 다시 가져와서 상태를 업데이트
        const response = await axios.get(API_MYPAGE_PROFILE(userId));
        const profileData = response.data;
        setProfile(profileData);
        setNickname(profileData.name);
        setEmail(profileData.email);
        setImageUrl(publicUrlData.publicUrl);
      } else {
        console.error('Failed to get public URL');
      }
    }
  };

  useEffect(() => {
    const fetchProfile = async () => {
      const response = await axios.get(API_MYPAGE_PROFILE(userId));
      const profileData = response.data;
      setProfile(profileData);
      setNickname(profileData.name);
      setEmail(profileData.email);
      setImageUrl(profileData.avatar);
    };

    fetchProfile();
  }, [userId]);

  return (
    <div>
      <h1>Edit Profile</h1>
      {profile ? (
        <form onSubmit={handleSubmit}>
          <div>
            {imageUrl && (
              <Image
                src={`${imageUrl}?${new Date().getTime()}`}
                alt="Profile"
                width={70}
                height={70}
                className="rounded-full"
              />
            )}
            <input type="file" onChange={handleImageChange} />
          </div>
          <input
            type="text"
            value={nickname}
            onChange={(e) => setNickname(e.target.value)}
            placeholder="Nickname"
            className="text-black"
          />
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Email"
            className="text-black"
          />
          <button type="submit">Update Profile</button>
        </form>
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
};

export default ProfileForm;

 

이외에도 내가 작성한 게시글 리스트 확인 가능 및 상세보기 가능

내 투어 클릭 시 해당 상세페이지로 이동

'use client';

import axios from 'axios';
import { useQuery } from '@tanstack/react-query';
import React, { useState } from 'react';
import { useParams } from 'next/navigation';
import { API_MYPAGE_POST } from '@/utils/apiConstants';
import Link from 'next/link';

type Post = {
  id: string;
  created_at: string;
  user_id: string;
  title: string;
  image: string;
  updated_at?: string;
};

export default function PostList() {
  const params = useParams();
  const userId = Array.isArray(params.id) ? params.id[0] : params.id;

  const getPostsData = async () => {
    try {
      const response = await axios.get(API_MYPAGE_POST(userId));
      // 필터링 로직을 클라이언트에서 처리
      const data: Post[] = response.data;
      return data.filter((post) => post.user_id === userId);
    } catch (error) {
      if (axios.isAxiosError(error)) {
        throw new Error(`HTTP error! status: ${error.response?.status}`);
      } else {
        throw new Error('An unknown error occurred');
      }
    }
  };

  const { data, isLoading, error } = useQuery<Post[]>({
    queryKey: ['post', userId],
    queryFn: getPostsData,
    enabled: !!userId
  });

  if (isLoading) return <div className="flex h-screen items-center justify-center">Loading...</div>;

  if (error) {
    return <div className="flex h-screen items-center justify-center">Error: {error.message}</div>;
  }

  if (!data || data.length === 0) {
    return <div className="flex h-screen items-center justify-center">No posts found</div>;
  }

  return (
    <div className="max-w-[360px]">
      {data.map((post) => (
        <div key={post.id}>
          <p>{new Date(post.created_at).toLocaleString()}</p>
          <Link href={`/detail/${post.id}`}>
            <div className="flex">
              <img className="mb-[20px] mr-2 h-[76px] w-[76px]" src={post.image} alt={post.title} />
              <p className="overflow-hidden text-ellipsis whitespace-nowrap text-[14px] font-bold">{post.title}</p>
            </div>
          </Link>
        </div>
      ))}
    </div>
  );
}

회의 내용

마이페이지 내 투어에서 예약자 확인 구현해야함 / 예약자 정보 ( 일단 이메일 ,닉네임 ) / 작성 날짜 내 예약 -> 결제 날짜

 

오후 개발자 튜터님

apiConstants.ts 정리 방식 괜찮은 듯 번역18next 그거 쓰려면 영어/한국어 처음부터 같이 작업해야함 -> 나중에 하면 큰일남 -> 언어 파일을 만들어서 json형식으로 가져다 쓰는 것임

next에서는 axios도 괜찮지만 fetch가 조금 더 선호된다 하지만 axios가 편리한 기능도 더 있기때문에 나을 수 있다 공식문서가 fetch라서 정보가 많을 수 있다

 

저녁 개발자 튜터님

가결제면 mvp 괜찮을듯 기본부터 하고 어려운거해야지 -> 이게 실수다 중요한 기능이라면 도전적인 기능이라도 먼저 구현을 해내고 기본적인 로직을 완성하는게 낫다 그래야 나중에 로직이 크게 안바뀜 타임라인이 자세히 없는건 조금 아쉽다 HTML기본적인 태그들을 기준으로 공통적으로 쓰일 수 있는 컴포넌트는 ( p , title , h1 , button 등등 ) 디테일한 부분은 props로 전달하면 되니 아예 똑같은 부분은 미리 공통 컴포넌트로 만들어서 쓰면 이후 디자인 반영 시 훨씬 수월하게 적용 가능 할 것이다

pr방식은 큰 도움이 될 것이라 좋지만 작업시간을 고려해야해서 중요도를 나눠서 중요한것 위주로만 짚고 넘어가는게 좋을 듯 커밋 외에도 pr 제목 및 내용도 신경 써야함

주에1~2번정도는 방문 하실 예정

 

저녁 디자이너 멘토님

검색 시 지역명을 알고 검색 할 수 있는 외국인은 정말 드믈 것 같음 도 , 시 등 여러 지역들을 보여 줄 수 있는 리스트를 만드는게 필요 함 만약에 지역을 세분화 해서 선택지를 줄 수 있도록 긁어올 수 있는 정보를 찾아보고 없다면 타겟층을 바꿔야 할 듯 투어 리스트들도 촬영 명소나 전통음식 등 관심있는 외국인을 타겟으로 바꾸는 차선도 있을 듯 visitKOREA 같은 사이트를 알아보면 좋을듯 / where to eat Korea 등은 정보 구할 때 쓰일 검색어 오히려 영어로 검색해야 더 많이 나옴

검색 시 tag를 예시로 보여주는건 어떨지