Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
4 changes: 3 additions & 1 deletion frontend/src/apis/firebase/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const getUserData = async () => {
return null;
}

const userData = docSnap.data() as TUser;

const likeShoesRef = collection(db, "users", uid, "likeShoes");
const likeShoesSnap = await getDocs(likeShoesRef);
const likeShoes = likeShoesSnap.docs.map((doc) => ({
Expand All @@ -48,7 +50,7 @@ const getUserData = async () => {

return {
uid,
...docSnap.data(),
...userData,
likeShoes,
shoeCloset,
};
Expand Down
90 changes: 80 additions & 10 deletions frontend/src/apis/firebase/chatFirestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
setDoc,
FieldValue,
writeBatch,
startAfter,
QueryDocumentSnapshot,
DocumentData,
} from "firebase/firestore";
import { TChatResponse } from "@/types/chat";

Expand All @@ -26,6 +29,7 @@
content: string | TChatResponse;
id?: string;
}[];
lastVisibleDoc: QueryDocumentSnapshot<DocumentData> | null;
}> => {
const roomsCollection = collection(db, "chatSessions", userId, "rooms");
// 이 코드는 userId라는 도큐먼트의 하위 컬렉션 rooms에 접근하여,
Expand All @@ -48,7 +52,7 @@
});

latestRoomId = newRoomRef.id;
return { roomId: latestRoomId, messages: [] };
return { roomId: latestRoomId, messages: [], lastVisibleDoc: null };
} else {
const latestRoom = roomsSnapshot.docs[0];
latestRoomId = latestRoom.id;
Expand All @@ -62,12 +66,19 @@
latestRoomId,
"messages"
);
const messagesQuery = query(messagesCollection, orderBy("timestamp", "asc"));
const messagesQuery = query(
messagesCollection,
orderBy("timestamp", "desc"),
limit(5)
);
const messagesSnapshot = await getDocs(messagesQuery);

const messages = messagesSnapshot.docs.flatMap((doc) => {
const messages = messagesSnapshot.docs.reverse().flatMap((doc) => {
const data = doc.data();
const result = [];
// 질문과 답변을 한쌍으로 저장했기에, 하나의 doc에 대해서
// bot과 user 메세지 객체를 분리하여 저장한다.
// 이후에 type이 bot인지 user인지에 따라 화면에 그려지는 위치가 정해진다.

// user 메시지가 빈 문자열이 아닌 경우에만 추가
if (data.user && data.user.trim() !== "") {
Expand All @@ -89,7 +100,10 @@
return result;
});

return { roomId: latestRoomId, messages };
const lastVisibleDoc =
messagesSnapshot.docs[messagesSnapshot.docs.length - 1] || null;

return { roomId: latestRoomId, messages, lastVisibleDoc };
};

export const addMessageToFirestore = async (
Expand Down Expand Up @@ -132,9 +146,8 @@

// 성공적으로 메시지가 저장되었으므로 doc.id 반환
return docRef.id;
} catch (error) {

Check warning on line 149 in frontend/src/apis/firebase/chatFirestore.ts

View workflow job for this annotation

GitHub Actions / continuous-integration

'error' is defined but never used

Check warning on line 149 in frontend/src/apis/firebase/chatFirestore.ts

View workflow job for this annotation

GitHub Actions / continuous-integration

'error' is defined but never used

Check warning on line 149 in frontend/src/apis/firebase/chatFirestore.ts

View workflow job for this annotation

GitHub Actions / continuous-integration

'error' is defined but never used
console.error("메시지 추가 중 에러 발생: ", error);
return null; // 에러 발생 시 null 반환
throw new Error();
}
};

Expand Down Expand Up @@ -257,6 +270,7 @@
content: string | TChatResponse;
id?: string;
}[];
lastVisibleDoc: QueryDocumentSnapshot<DocumentData> | null;
}> => {
try {
const messagesCollection = collection(
Expand All @@ -269,11 +283,12 @@
);
const messagesQuery = query(
messagesCollection,
orderBy("timestamp", "asc")
orderBy("timestamp", "desc"),
limit(5)
);
const messagesSnapshot = await getDocs(messagesQuery);

const messages = messagesSnapshot.docs.flatMap((doc) => {
const messages = messagesSnapshot.docs.reverse().flatMap((doc) => {
const data = doc.data();
const result = [];

Expand All @@ -295,10 +310,13 @@
return result;
});

return { roomId, messages };
const lastVisibleDoc =
messagesSnapshot.docs[messagesSnapshot.docs.length - 1] || null;

return { roomId, messages, lastVisibleDoc };
} catch (error) {
console.error(error);
return { roomId, messages: [] };
return { roomId, messages: [], lastVisibleDoc: null };
}
};

Expand Down Expand Up @@ -330,3 +348,55 @@
console.error(error);
}
};

// 스크롤 위로 올렸을 때 호출할 함수
export const getOlderMessages = async (
userId: string,
roomId: string,
lastVisibleDoc: QueryDocumentSnapshot<DocumentData> | null
) => {
const messagesCollection = collection(
db,
"chatSessions",
userId,
"rooms",
roomId,
"messages"
);

const messagesQuery = query(
messagesCollection,
orderBy("timestamp", "desc"),
startAfter(lastVisibleDoc), // 마지막 스냅샷 이후부터 가져옴
limit(5)
);

const messagesSnapshot = await getDocs(messagesQuery);

const messages = messagesSnapshot.docs.reverse().flatMap((doc) => {
const data = doc.data();
const result = [];

if (data.user && data.user.trim() !== "") {
result.push({
type: "user" as const,
content: data.user as string,
});
}

if (data.bot) {
result.push({
type: "bot" as const,
content: data.bot as TChatResponse,
id: doc.id,
});
}

return result;
});

const newLastVisibleDoc =
messagesSnapshot.docs[messagesSnapshot.docs.length - 1];

return { messages, newLastVisibleDoc };
};
23 changes: 20 additions & 3 deletions frontend/src/components/Chat/ChatProductItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import userStore from "@store/auth.store";
import LikeButton from "@common/LikeButton";
import Img from "@common/html/Img";
import { useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import useChatStore from "@/store/chat.store";
import { useBottomSheet } from "@/store/bottomSheet.store";

interface ProductItemProps {
brand: string;
Expand All @@ -28,10 +30,18 @@
const { messageId } = useParams();
const isSharePage = Boolean(messageId);

const { addGuestMessage } = useChatStore();
const { open } = useBottomSheet();
const navigate = useNavigate();
const goToLogin = () => {
navigate("#login");
open("login");
};

const handleLikeClick = async (e: React.MouseEvent) => {
e.stopPropagation();
if (isLoggedIn) {
await addOrRemoveShoeFromLikes(user?.uid!, {

Check warning on line 44 in frontend/src/components/Chat/ChatProductItem.tsx

View workflow job for this annotation

GitHub Actions / continuous-integration

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong

Check warning on line 44 in frontend/src/components/Chat/ChatProductItem.tsx

View workflow job for this annotation

GitHub Actions / continuous-integration

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong

Check warning on line 44 in frontend/src/components/Chat/ChatProductItem.tsx

View workflow job for this annotation

GitHub Actions / continuous-integration

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong
brand,
productName,
imgUrl,
Expand All @@ -41,8 +51,15 @@
});
updateUserInfo();
} else {
console.log("로그인이 필요합니다.");
// 여기서 로그인하라는 채팅을 띄워주면 좋을 듯 하다. 일단 나중에 ..
addGuestMessage({
type: "bot",
content: {
message: "로그인이 필요한 기능입니다.",
},
});
setTimeout(() => {
goToLogin();
}, 1500);
}
};
return (
Expand All @@ -58,7 +75,7 @@
isLiked={isLiked}
/>
)}
<div className="bg-grey-300 absolute bottom-0.5 right-1.5 size-6 rounded-full">

Check warning on line 78 in frontend/src/components/Chat/ChatProductItem.tsx

View workflow job for this annotation

GitHub Actions / continuous-integration

Classname 'bg-grey-300' is not a Tailwind CSS class!

Check warning on line 78 in frontend/src/components/Chat/ChatProductItem.tsx

View workflow job for this annotation

GitHub Actions / continuous-integration

Classname 'bg-grey-300' is not a Tailwind CSS class!

Check warning on line 78 in frontend/src/components/Chat/ChatProductItem.tsx

View workflow job for this annotation

GitHub Actions / continuous-integration

Classname 'bg-grey-300' is not a Tailwind CSS class!
<Img src={customerImg} alt={brand} errorStyle="w-full opacity-40" />
</div>
</div>
Expand Down
23 changes: 21 additions & 2 deletions frontend/src/components/Chat/ChatShareDislikeBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { TChatResponse } from "@/types/chat";
import { Timestamp } from "firebase/firestore";
import ShareModal from "@common/ShareModal";
import useToggle from "@hooks/useToggle";
import { useNavigate } from "react-router-dom";
import { useBottomSheet } from "@/store/bottomSheet.store";

interface ChatShareDislikeBoxProps {
docId?: string | null;
Expand All @@ -29,9 +31,18 @@ const ChatShareDislikeBox = (props: ChatShareDislikeBoxProps) => {
timestamp: Timestamp;
}>();
const { user } = userStore();
const { roomId } = useChatStore();
const { roomId, addGuestMessage } = useChatStore();
const userId = user?.uid!;

const { open } = useBottomSheet();

const navigate = useNavigate();

const goToLogin = () => {
navigate("#login");
open("login");
};

const handleOpenModal = async () => {
if (user) {
try {
Expand All @@ -44,7 +55,15 @@ const ChatShareDislikeBox = (props: ChatShareDislikeBoxProps) => {
console.error(error);
}
} else {
console.log("로그인 해야 모달창이 열린단다");
addGuestMessage({
type: "bot",
content: {
message: "로그인이 필요한 기능입니다.",
},
});
setTimeout(() => {
goToLogin();
}, 1500);
}
};

Expand Down
41 changes: 27 additions & 14 deletions frontend/src/components/login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import useFocus from "@hooks/useFocus";

const Login = () => {
const navigate = useNavigate();
const { updateUserInfo, user, isLoggedIn } = userStore();
const { updateUserInfo, isLoggedIn } = userStore();
const { roomId, addUserMessage } = useChatStore();
const [loginData, setLoginData] = useState({
email: "",
Expand All @@ -23,7 +23,13 @@ const Login = () => {
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");

const { closeAll } = useBottomSheet();
const { closeAll, open } = useBottomSheet();

const goToSignUp = () => {
closeAll();
navigate("#signup");
open("login");
};

// 자동 포커스
const [emailRef, focusEmail, handleEmailKeyPress] =
Expand Down Expand Up @@ -81,24 +87,25 @@ const Login = () => {

//값 확인용
if (isLoginValid) {
await signInWithCredential(loginData).then(() => {
updateUserInfo();
closeAll();
navigate("/");
});

// 여기서 왜 isLoggedIn이 false 일까..
// 루트 경로로 보냈으니 Layout 컴포넌트에서 로그인 상태 변경 해줘야 하는거 아닌가??
// 일단 급한대로 아래 try문에서 로그인 상태 확인 없이 진행
console.log(isLoggedIn);
await signInWithCredential(loginData);
// zustand로 관리하는 user가 업데이트가 바로 안이루어져서,
// 임시 방편으로 updateUserInfo 가 userData를 반환하게끔 하고
// 반환값을 사용하도록 하자
// 필요한 데이터만 구조분해할당
const { uid, username } = (await updateUserInfo()) as {
uid: string;
username: string;
};
closeAll();
navigate("/");

// 여기서 맞춤상품 api 호출 처리
try {
const loginMent = `반갑습니다 ${user?.username}님! ${user?.username}님을 위한 맞춤 상품을 추천해 드릴께요`;
const loginMent = `반갑습니다 ${username!}님! ${username!}님을 위한 맞춤 상품을 추천해 드릴께요`;
const res = await chatApi.getCustomizedProduct();

if (res.status === 200) {
await addMessageToFirestore(user?.uid!, roomId!, "", {
await addMessageToFirestore(uid!, roomId!, "", {
message: loginMent,
reqProducts: res.data,
} as TChatResponse);
Expand Down Expand Up @@ -152,6 +159,12 @@ const Login = () => {
onKeyDown={(e) => handlePasswordPress(e, focusFormButton)}
/>
</InputField>
<span
className="ml-3 cursor-pointer text-xs font-bold text-[rgb(0,123,255)] hover:underline"
onClick={goToSignUp}
>
아이디가 없으신가요?
</span>
</div>
<div className="mt-4 px-5">
{/*로그인 폼 제출 버튼*/}
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/sidemenu/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ const SideMenu = ({
await deleteChatRoom(userId, deleteId);
const updatedChats = await getUserChatRooms(user?.uid!);
setChats(updatedChats);
// 지우려는 방이 현재 속한 방이라면 채팅방의 내용을 가장 최신 채팅으로 업데이트

// 지우려는 방이 현재 속한 방이라면 채팅방의 내용을
// 가장 최신 채팅이 이루어진 채팅방으로 업데이트
// (채팅방을 삭제했을 때 채팅창에 메세지가 전부 다 사라지면 사용자 경험 측면에서 안좋으니.)
if (deleteId === roomId) {
loadUserMessages(userId);
}
Expand Down
Loading
Loading