From 1de7a0d2ae117d88730c0c21f835875f81592209 Mon Sep 17 00:00:00 2001 From: Yhyun31 Date: Thu, 3 Apr 2025 14:11:34 +0900 Subject: [PATCH 01/79] =?UTF-8?q?=E2=9C=A8feat:=20=EA=B3=B5=EA=B0=9C?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/navigations/Main.js | 1 + src/screens/PublicProfile.js | 206 +++++++++++++++++------------------ 2 files changed, 98 insertions(+), 109 deletions(-) diff --git a/src/navigations/Main.js b/src/navigations/Main.js index c63eca9..6cb63af 100644 --- a/src/navigations/Main.js +++ b/src/navigations/Main.js @@ -37,6 +37,7 @@ const Main = () => { + diff --git a/src/screens/PublicProfile.js b/src/screens/PublicProfile.js index 9742234..62913b7 100644 --- a/src/screens/PublicProfile.js +++ b/src/screens/PublicProfile.js @@ -1,29 +1,27 @@ import React, { useContext } from "react"; -import { View, Text, Image, TouchableOpacity, ScrollView } from "react-native"; -import styled from "styled-components/native"; -import { MaterialIcons ,Feather} from "@expo/vector-icons"; - +import { Platform } from "react-native"; +import styled, { ThemeContext } from "styled-components/native"; +import { MaterialIcons, Feather } from "@expo/vector-icons"; import Review from "../components/Review"; -// 스타일 const Container = styled.View` flex: 1; background-color: #fff; - padding-left: 20px; - padding-right: 20px; + padding: 0 20px; + padding-top: 15px; `; const ProfileContainer = styled.View` flex-direction: row; align-items: center; - margin-top: 20px; + margin-bottom:15px; `; const ProfileImageContainer = styled.View` width: 60px; height: 60px; - margin-left:10px; - margin-right:15px; + margin-left: 10px; + margin-right: 15px; border-radius: 30px; background-color: #ddd; align-items: center; @@ -44,7 +42,7 @@ const UserInfo = styled.View` const UserName = styled.Text` font-size: 20px; - font-family: ${({theme}) => theme.fonts.bold}; + font-family: ${({ theme }) => theme.fonts.bold}; margin-right: 15px; color: ${({ theme }) => theme.colors.mainBlue}; `; @@ -57,136 +55,126 @@ const StarContainer = styled.View` const StarText = styled.Text` font-size: 14px; color: ${({ theme }) => theme.colors.mainBlue}; - font-family: ${({theme}) => theme.fonts.extraBold}; + font-family: ${({ theme }) => theme.fonts.extraBold}; margin-left: 5px; `; - const SectionTitle = styled.Text` font-size: 18px; - font-family: ${({theme}) => theme.fonts.bold}; + font-family: ${({ theme }) => theme.fonts.bold}; color: #656565; - margin-bottom: 5px; + margin-bottom: 15px; `; -const SectionContent = styled.View` - margin-top: 10px; - +const ScrollSection = styled.View` + flex: ${Platform.OS === "ios" ? 2 : 2}; + margin-top: 20px; + max-height:180px; `; -const CareerContainer = styled.View` - margin-top: 20px; - height: 160px; - margin-left: 5px; +const ScrollArea = styled.ScrollView` + flex-grow: 0; `; const CareerText = styled.Text` - margin-top:5px; font-size: 15px; - font-family: ${({theme}) => theme.fonts.regular}; - color: ${({theme})=>theme.colors.black}; + font-family: ${({ theme }) => theme.fonts.regular}; + color: ${({ theme }) => theme.colors.black}; `; - const Placeholder = styled.Text` font-size: 16px; - color: ${({theme})=>theme.colors.grey}; - font-family: ${({theme}) => theme.fonts.bold}; + color: ${({ theme }) => theme.colors.grey}; + font-family: ${({ theme }) => theme.fonts.bold}; text-align: center; `; -const ReviewContainer = styled.ScrollView` - margin-top: 20px; - height: 500px; - margin-left: 5px; -`; - -const ReviewScrollContainer = styled.ScrollView` - max-height: 400px; -`; - - const Divider = styled.View` height: 1px; - background-color: ${({theme})=>theme.colors.grey}; + background-color: ${({ theme }) => theme.colors.grey}; + margin-top: 10px; + margin-bottom: 10px; +`; + +const ReviewSection = styled.View` + flex: ${Platform.OS === "ios" ? 3 : 3}; + margin-top: 20px; + margin-bottom:50px; `; const dummyUser = { name: "홍길동", - career: `안녕하세요~ 홍길동입니다\n저는 2024년도에 독서 모임장으로 활동하며 어쩌구저쩌구\n외라라리라랄라라란 살라살라\n이상입니다! 감사합니다! 차하하`, + career: `안녕하세요~ 홍길동입니다\n저는 2024년도에 독서 모임장으로 활동하며 \n어쩌구저쩌구\n외라라리라랄라라란 살라살라\n이상입니다! 감사합니다!`, reviews: [ - { star: 4.5, text: "책임감 있게 모임을 이끌어줬어요!", created_at: "2025.02.28" }, - { star: 5.0, text: "모두가 참여할 수 있는 재밌는 모임을 만들어 주셨어요 👍", created_at: "2025.02.28" }, - { star: 4.0, text: "정말 유익한 시간이었습니다.", created_at: "2025.02.27" }, - { star: 4.8, text: "참여자들과 원활한 소통이 인상적이었어요.", created_at: "2025.02.26" }, - { star: 5.0, text: "친절하고 배려심 넘치는 진행이었어요.", created_at: "2025.02.25" }, - { star: 3.5, text: "조금 아쉬운 점도 있었지만, 전반적으로 만족합니다.", created_at: "2025.02.24" }, - { star: 4.2, text: "재밌는 활동들 덕분에 시간 가는 줄 몰랐어요!", created_at: "2025.02.23" }, - { star: 4.7, text: "다음에도 참여하고 싶어요!", created_at: "2025.02.22" } + { star: 4.5, sentence: "책임감 있게 모임을 이끌어줬어요!", createdAt: "2025.02.28" }, + { star: 5.0, sentence: "모두가 참여할 수 있는 재밌는 모임을 만들어 주셨어요 👍", createdAt: "2025.02.28" }, + { star: 4.0, sentence: "정말 유익한 시간이었습니다.", createdAt: "2025.02.27" }, + { star: 4.8, sentence: "참여자들과 원활한 소통이 인상적이었어요.", createdAt: "2025.02.26" }, + { star: 5.0, sentence: "친절하고 배려심 넘치는 진행이었어요.", createdAt: "2025.02.25" }, + { star: 3.5, sentence: "조금 아쉬운 점도 있었지만, 전반적으로 만족합니다.", createdAt: "2025.02.24" }, + { star: 4.2, sentence: "재밌는 활동들 덕분에 시간 가는 줄 몰랐어요!", createdAt: "2025.02.23" }, + { star: 4.7, sentence: "다음에도 참여하고 싶어요!", createdAt: "2025.02.22" } ] }; -dummyUser.totalStar = dummyUser.reviews.length > 0 - ? (dummyUser.reviews.reduce((acc, review) => acc + review.star, 0) / dummyUser.reviews.length).toFixed(1) - : "0.0"; + +dummyUser.totalStar = + dummyUser.reviews.length > 0 + ? (dummyUser.reviews.reduce((acc, review) => acc + review.star, 0) / dummyUser.reviews.length).toFixed(1) + : "0.0"; const PublicProfile = ({ route }) => { - - const user = route?.params?.user || dummyUser; //더미데이터 사용용 - - - return ( - - - - - {/* 프로필 정보 */} - - - {user.image ? ( - - ) : ( - - )} - - - {user.name} - - - {user.totalStar || "0.0"} - - - - - - {/* 경력 */} - - 경력 - - {user.career ? {user.career} : 등록되지 않았습니다} - - - - - - {/* 리뷰 */} - - 리뷰 - - {(user?.reviews?.length ?? 0) > 0 ? ( - user.reviews.map((review, index) => ) // ✅ Review 컴포넌트 사용 - ) : ( - 등록되지 않았습니다 - )} - - - - - - + const theme = useContext(ThemeContext); + const user = route?.params?.user || dummyUser; + + return ( + + {/* 프로필 영역 */} + + + {user.image ? ( + + ) : ( + + )} + + + {user.name} + + + {user.totalStar} + + + + + {/* 경력 */} + + 경력 + + {user.career ? ( + {user.career} + ) : ( + 등록되지 않았습니다 + )} + + + + + + {/* 리뷰 */} + + 리뷰 + + {user.reviews.length > 0 ? ( + user.reviews.map((review, index) => ( + + )) + ) : ( + 등록되지 않았습니다 + )} + + - ); - }; - + ); +}; export default PublicProfile; - From 97a5e1121691a46d7bf43b4b05b02eea190e5367 Mon Sep 17 00:00:00 2001 From: Yhyun31 Date: Thu, 3 Apr 2025 14:11:53 +0900 Subject: [PATCH 02/79] =?UTF-8?q?=F0=9F=8E=A8style:=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/screens/PostDetail.js | 43 +++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/screens/PostDetail.js b/src/screens/PostDetail.js index 04c3dda..276f2dd 100644 --- a/src/screens/PostDetail.js +++ b/src/screens/PostDetail.js @@ -1,8 +1,10 @@ import React, { useState, useContext } from "react"; -import { useRoute } from "@react-navigation/native"; +import { useRoute, useNavigation } from "@react-navigation/native"; import { Feather, AntDesign, Ionicons, FontAwesome6 } from "@expo/vector-icons"; import { styled, ThemeContext } from "styled-components/native"; import Button from "../components/Button"; +import { TouchableOpacity } from "react-native"; + const Container = styled.View` flex: 1; @@ -131,6 +133,7 @@ const LikeText = styled.Text` const PostDetail = () => { const theme = useContext(ThemeContext); const route = useRoute(); + const navigation = useNavigation(); const { postId, title = "제목 없음", @@ -164,6 +167,7 @@ const PostDetail = () => { // 작성자 더미 데이터 const user = { + userId:1, name: "홍길동", career: "안녕하세요~ 홍길동입니다.\n2024년부터 독서 모임장으로 활동하고 있어요!", @@ -236,23 +240,26 @@ const PostDetail = () => { {/* 작성자 정보 섹션 */} - - - - {user.image ? ( - - ) : ( - - )} - - - - - {user.name} - - - {user.career} - + navigation.navigate("공개프로필",{userId:user.userId})}> + + + + {user.image ? ( + + ) : ( + + )} + + + + + {user.name} + + + {user.career} + + + {/* 하단 좋아요 & 신청 버튼 고정 */} + { + setAlertVisible(false); + if (onConfirmAction) onConfirmAction(); + }} + /> + + setConfirmVisible(false)} // ✅ 취소 시 모달 닫기만 + /> ); diff --git a/src/screens/PaymentScreen.js b/src/screens/PaymentScreen.js index 709c423..0b68cd5 100644 --- a/src/screens/PaymentScreen.js +++ b/src/screens/PaymentScreen.js @@ -1,6 +1,7 @@ import React, { useState, useRef } from "react"; import { WebView } from "react-native-webview"; import { Alert, Linking, Platform } from "react-native"; +import { AlertModal } from "../components"; import axios from "axios"; import EncryptedStorage from "react-native-encrypted-storage"; @@ -8,6 +9,10 @@ const PaymentScreen = ({ route, navigation }) => { const { amount, title } = route.params; const [paymentData, setPaymentData] = useState(null); + const [alertVisible, setAlertVisible] = useState(false); + const [alertMessage, setAlertMessage] = useState(""); + const [onConfirmAction, setOnConfirmAction] = useState(null); // goBack 등을 지정할 수 있는 콜백 + const hasProcessedPayment = useRef(false); const handleUrlScheme = (event) => { @@ -39,7 +44,8 @@ const PaymentScreen = ({ route, navigation }) => { Linking.openURL(url); } catch (e) { console.error("❌ 앱 실행 에러:", e); - Alert.alert("앱 실행 실패", "필요한 앱이 설치되지 않았습니다."); + setAlertMessage("필요한 앱이 설치되지 않았습니다."); + setAlertVisible(true); } return false; } @@ -88,10 +94,14 @@ const PaymentScreen = ({ route, navigation }) => { } ); - Alert.alert("결제 성공", "서버에 결제 정보가 전달되었습니다.", [{ text: "확인", onPress: () => navigation.goBack() }]); + setAlertMessage("결제가 성공적으로 완료되었습니다."); + setOnConfirmAction(() => () => navigation.goBack()); // 확인 시 goBack 실행 + setAlertVisible(true); } catch (err) { console.error("❌ 서버 전송 실패:", err.response?.data || err.message); - Alert.alert("전송 실패", "서버로 결제 정보를 전달하는 데 실패했습니다."); + + setAlertMessage("서버로 결제 정보를 전달하는 데 실패했습니다."); + setAlertVisible(true); } }; @@ -129,7 +139,19 @@ const PaymentScreen = ({ route, navigation }) => { `; - return ; + return ( + <> + + { + setAlertVisible(false); + if (onConfirmAction) onConfirmAction(); // goBack 등 실행 + }} + /> + + ); }; export default PaymentScreen; diff --git a/src/screens/PostDetail.js b/src/screens/PostDetail.js index 123d347..509655b 100644 --- a/src/screens/PostDetail.js +++ b/src/screens/PostDetail.js @@ -2,7 +2,7 @@ import React, { useState, useContext, useEffect } from "react"; import { useRoute, useNavigation } from "@react-navigation/native"; import { Feather, AntDesign, Ionicons } from "@expo/vector-icons"; import { styled, ThemeContext } from "styled-components/native"; -import Button from "../components/Button"; +import { Button, AlertModal } from "../components"; import { TouchableOpacity, Text } from "react-native"; import useRequireLogin from "../hooks/useRequireLogin"; import axios from "axios"; @@ -144,8 +144,9 @@ const PostDetail = () => { const [liked, setLiked] = useState(false); const [likeId, setLikeId] = useState(null); const [likes, setLikes] = useState(0); - const [user, setUser] = useState(null); + const [alertVisible, setAlertVisible] = useState(false); + const [alertMessage, setAlertMessage] = useState(""); const fetchDetail = async () => { try { @@ -197,7 +198,8 @@ const PostDetail = () => { const accessToken = await EncryptedStorage.getItem("accessToken"); if (!accessToken) { - Alert.alert("로그인이 필요합니다"); + setAlertMessage("로그인이 필요합니다"); + setAlertVisible(true); return; } @@ -240,7 +242,8 @@ const PostDetail = () => { } else { console.log("📡 설정 중 오류:", error.message); } - Alert.alert("오류", "좋아요 처리 중 문제가 발생했습니다."); + setAlertMessage("좋아요 처리 중 문제가 발생했습니다."); + setAlertVisible(true); } }; @@ -343,6 +346,13 @@ const PostDetail = () => { /> + { + setAlertVisible(false); + }} + /> ); }; diff --git a/src/screens/ReviewForm.js b/src/screens/ReviewForm.js index ac93379..88bc4b0 100644 --- a/src/screens/ReviewForm.js +++ b/src/screens/ReviewForm.js @@ -1,5 +1,5 @@ import React, { useContext, useState, useEffect } from "react"; -import { Button, Input } from "../components"; +import { Button, Input, AlertModal } from "../components"; import styled, { ThemeContext } from "styled-components/native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import FontAwesome from "react-native-vector-icons/FontAwesome"; @@ -59,6 +59,10 @@ const ReviewForm = ({ route, navigation }) => { const [disabled, setDisabled] = useState(true); const [rating, setRating] = useState(0); // 별점 상태 + const [alertVisible, setAlertVisible] = useState(false); + const [alertMessage, setAlertMessage] = useState(""); + const [onConfirmAction, setOnConfirmAction] = useState(null); + useEffect(() => { setDisabled(form.trim().length === 0 || rating < 1); }, [form, rating]); @@ -83,7 +87,7 @@ const ReviewForm = ({ route, navigation }) => { const handleReviewSubmit = async () => { try { - const accessToken = await EncryptedStorage.getItem("accessToken"); // 여기서 await 필요 + const accessToken = await EncryptedStorage.getItem("accessToken"); await axios.post( "http://10.0.2.2:8080/api/review", @@ -99,11 +103,15 @@ const ReviewForm = ({ route, navigation }) => { }, } ); - alert("리뷰 등록 성공"); - navigation.goBack(); + + setAlertMessage("리뷰 등록이 완료되었습니다."); + setOnConfirmAction(() => () => navigation.goBack()); + setAlertVisible(true); } catch (e) { - alert("리뷰 등록 실패"); - console.error(e); + console.error("리뷰 등록 실패:", e); + setAlertMessage("리뷰 등록에 실패했습니다."); + setOnConfirmAction(null); + setAlertVisible(true); } }; @@ -142,6 +150,14 @@ const ReviewForm = ({ route, navigation }) => { }} textStyle={{ marginLeft: 0, fontSize: 16 }} /> + { + setAlertVisible(false); + if (onConfirmAction) onConfirmAction(); + }} + /> ); }; diff --git a/src/screens/Search.js b/src/screens/Search.js index d7d9222..ec4f092 100644 --- a/src/screens/Search.js +++ b/src/screens/Search.js @@ -1,6 +1,7 @@ import React, { useState, useContext, useCallback, useEffect } from "react"; import { Keyboard, ScrollView, TouchableOpacity } from "react-native"; import { styled, ThemeContext } from "styled-components/native"; +import { AlertModal } from "../components"; import { Feather } from "@expo/vector-icons"; import { useNavigation, useFocusEffect } from "@react-navigation/native"; import axios from "axios"; @@ -75,9 +76,12 @@ const Search = () => { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [currentUserId, setCurrentUserId] = useState(null); + const [alertVisible, setAlertVisible] = useState(false); + const [alertMessage, setAlertMessage] = useState(""); const navigation = useNavigation(); const theme = useContext(ThemeContext); + const fetchUserInfo = async () => { try { const token = await EncryptedStorage.getItem("accessToken"); @@ -114,7 +118,8 @@ const Search = () => { setResults(dtoList); } catch (error) { console.error("❌ 검색 실패:", error); - Alert.alert("검색 실패", "다시 시도해주세요."); + setAlertMessage("검색 실패. 다시 시도해주세요."); + setAlertVisible(true); setResults([]); } }; @@ -172,6 +177,7 @@ const Search = () => { )} + setAlertVisible(false)} /> ); };