본문 바로가기

카테고리 없음

Fiesta Tour_10일차

투어 상세 페이지에서 가이드에게 메시지 보내기

과정에서 팀원의 코드를 기반으로 추가 및 수정을 진행해야 하다보니 어려움이 많았음

사용중인 api부터 코드 스타일 등등 로직을 이해하여 구현에 성공

1:1 실시간 채팅은 확인해보고 개선해야함

기존에 가이드 일부 정보만 받아오던 코드에서

가이드 고유id , 위치 정보 등 더 필요한 정보를 받아오고 투어 내용도 받아와서 params를 통해 채팅 페이지로 전달

'use client';

import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import Image from 'next/image';
import { useParams, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
import { createClient } from '@/utils/supabase/client';
import { API_MYPAGE_PROFILE, API_POST_DETAILS } from '@/utils/apiConstants';

interface User {
  name: string;
  avatar: string;
  id: string;
  region: string;
}

type Post = {
  id: string;
  title: string;
  image: string;
};

export const Guide = () => {
  const { id: postId } = useParams() as { id: string };
  const supabase = createClient();
  const router = useRouter();
  const [customerUser, setCustomerUser] = useState<User | null>(null);

  // 작성자 데이터를 가져오는 함수
  const fetchUser = async (): Promise<User> => {
    const response = await axios.get(`/api/detail/guide/${postId}`);
    return response.data.user; // response.data에서 user 객체를 반환
  };

  const {
    data: user,
    isPending,
    error
  } = useQuery<User>({
    queryKey: ['user', postId],
    queryFn: fetchUser,
    enabled: !!postId
  });

  const fetchPostDetails = async (): Promise<Post> => {
    const response = await axios.get(API_POST_DETAILS(postId));
    return response.data;
  };

  const {
    data: post,
    isPending: isPostPending,
    error: postError
  } = useQuery<Post>({
    queryKey: ['post', postId],
    queryFn: fetchPostDetails,
    enabled: !!postId
  });

  const fetchCustomerUser = async () => {
    const { data, error } = await supabase.auth.getUser();

    if (error) throw new Error(error.message);

    if (data.user) {
      const userId = data.user.id;
      const response = await axios.get(API_MYPAGE_PROFILE(userId));
      const profileData = response.data;

      const customerUserData: User = {
        id: profileData.id,
        name: profileData.name,
        avatar: profileData.avatar,
        region: profileData.region
      };

      setCustomerUser(customerUserData);
    }
  };

  const handleChat = () => {
    if (!customerUser || !user || !post) {
      throw new Error('Customer user, guide user, or post details are missing.');
    }

    const senderId = customerUser.id;
    const receiverId = user.id;

    const query = new URLSearchParams({
      postId,
      postTitle: post.title,
      postImage: post.image
    }).toString();

    router.push(`/${senderId}/${receiverId}/chatpage?${query}`);
  };

  useEffect(() => {
    fetchCustomerUser();
  }, [supabase]);

  if (isPending) return <div>Loading...</div>;

  if (error) return <div>Error fetching user data</div>;

  return (
    <div>
      <h3>투어 가이드</h3>
      <div>
        {user && (
          <>
            <Image
              src={user.avatar}
              alt={`${user.name}의 아바타`}
              width={96}
              height={96}
              className="mr-2 h-24 w-24 rounded-full object-cover"
            />
            <p>{user.region}</p>
            <h4>{user.name}</h4>
            <h5>투어 지역은 보류</h5>
            <button className="mb-6 border" onClick={handleChat}>
              메시지 보내기
            </button>
          </>
        )}
      </div>
    </div>
  );
};

params를 통해 내id , 가이드id , 게시글 정보를 받아와서 채팅 페이지 구현

'use client';

import React, { useEffect } from 'react';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
import Image from 'next/image';
import { createClient } from '@/utils/supabase/client';
import Chat from '@/components/Chat';

const ChatPage = () => {
  const { id: senderId, receiverid } = useParams() as { id: string; receiverid: string };
  const searchParams = useSearchParams();
  const router = useRouter();
  const supabase = createClient();

  const handleBack = () => {
    router.back();
  };

  const checkAccess = async () => {
    const { data, error } = await supabase.auth.getUser();

    if (error) throw new Error(error.message);

    if (data.user.id !== senderId && data.user.id !== receiverid) {
      alert('접근 권한이 없습니다.');
      router.push('/');
    }
  };

  useEffect(() => {
    checkAccess();
  }, [senderId, receiverid, router, supabase]);

  const postTitle = searchParams.get('postTitle') || '제목 없음';
  const postImage = searchParams.get('postImage') || '';

  return (
    <div>
      <div className="mb-4 flex justify-around">
        <button onClick={handleBack}>Go Back</button>
        <p className="font-bold">message</p>
      </div>
      <div className="flex">
        <Image
          className="mb-[20px] mr-2"
          src={postImage ?? '/icons/upload.png'}
          alt={postTitle ?? 'Default title'}
          width={40}
          height={40}
        />
        <p className="text-[14px] font-bold">Title: {postTitle}</p>
      </div>
      <Chat senderId={senderId} receiverId={receiverid} />
    </div>
  );
};

export default ChatPage;

 

관련하여 새로운 api 파일 생성

import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/utils/supabase/server';

export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
  const supabase = createClient();
  const { id: postId } = params;

  try {
    const { data: post, error } = await supabase.from('posts').select('*').eq('id', postId).single();
    if (error) {
      return NextResponse.json({ error: error.message }, { status: 500 });
    }

    return NextResponse.json(post, { status: 200 });
  } catch (error) {
    return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

 

구현 과정에서 사용된 URL 라우팅 정리

URL 라우팅의 개념

URL 라우팅은 웹 애플리케이션에서 특정 URL에 대한 요청을 적절한 컴포넌트로 매핑하는 과정입니다. Next.js에서는 클라이언트 측 라우팅과 서버 측 라우팅을 모두 지원하며, 다양한 훅과 API를 제공하여 이를 쉽게 관리할 수 있습니다.

주요 개념 및 기능

1. URLSearchParams

URLSearchParams는 URL의 쿼리 문자열을 다루기 위한 브라우저 내장 API입니다. 이를 통해 쿼리 문자열을 쉽게 생성, 수정, 삭제 및 검색할 수 있습니다.

  • 생성: 쿼리 매개변수를 설정하여 새로운 쿼리 문자열을 생성합니다.
  • 수정: 기존 쿼리 문자열의 매개변수를 수정할 수 있습니다.
  • 삭제: 특정 매개변수를 삭제할 수 있습니다.
  • 검색: 특정 매개변수의 값을 검색할 수 있습니다.
const params = new URLSearchParams({
  key1: 'value1',
  key2: 'value2'
});
console.log(params.toString()); // "key1=value1&key2=value2"

 

2. useSearchParams (Next.js)

useSearchParams는 Next.js에서 제공하는 훅으로, 현재 URL의 쿼리 매개변수를 읽는 데 사용됩니다. 이를 통해 클라이언트 컴포넌트에서 URL 쿼리 매개변수를 쉽게 액세스할 수 있습니다.

  • 읽기: 현재 URL에서 쿼리 매개변수를 읽습니다.
  • 검색: 특정 쿼리 매개변수의 값을 검색할 수 있습니다.

3. useParams (Next.js)

useParams는 Next.js에서 동적 경로 매개변수를 읽는 데 사용되는 훅입니다. 동적 라우팅을 처리할 때 유용하며, URL 경로에서 특정 매개변수를 추출합니다.

  • 동적 경로 매개변수: URL 경로의 특정 부분을 변수로 처리하여 동적 매개변수를 추출합니다.
// URL이 /posts/[id]인 경우
const { id } = useParams(); // id는 동적 경로 매개변수

4. useRouter (Next.js)

useRouter는 Next.js에서 라우팅과 관련된 다양한 기능을 제공하는 훅입니다. 이를 통해 클라이언트 측에서 URL 변경, 뒤로 가기, 페이지 이동 등을 제어할 수 있습니다.

  • router.push: 사용자를 새로운 URL로 이동시킵니다. 브라우저 히스토리에 새 항목이 추가되어 뒤로 가기 버튼을 사용할 수 있습니다.
  • router.replace: 사용자를 새로운 URL로 이동시키지만, 현재 페이지를 대체하여 브라우저 히스토리에 새 항목을 추가하지 않습니다.
  • router.back: 사용자를 브라우저 히스토리에서 이전 페이지로 이동시킵니다. 이는 브라우저의 뒤로 가기 버튼과 동일한 효과를 가집니다.

실제 사용 코드

  const handleChat = () => {
    if (!customerUser || !user || !post) {
      throw new Error('Customer user, guide user, or post details are missing.');
    }

    const senderId = customerUser.id;
    const receiverId = user.id;

    const query = new URLSearchParams({
      postTitle: post.title,
      postImage: post.image
    }).toString();

    router.push(`/${senderId}/${receiverId}/chatpage?${query}`);
  };
  const postTitle = searchParams.get('postTitle') || '제목 없음';
  const postImage = searchParams.get('postImage') || '';

라우팅의 활용

Next.js에서 제공하는 라우팅 기능을 통해 URL 경로와 쿼리 매개변수를 손쉽게 다루고, 클라이언트 측에서 동적으로 페이지를 이동할 수 있습니다. 이러한 기능들을 활용하면 사용자 경험을 향상시키고, 애플리케이션의 내비게이션을 더 효과적으로 관리할 수 있습니다.