diff --git a/App.tsx b/App.tsx index 03d40a5..578a109 100644 --- a/App.tsx +++ b/App.tsx @@ -111,12 +111,12 @@ const CustomTab = ({ state, descriptors, navigation }: BottomTabBarProps) => { }); else navigation.navigate(route.name, { id: undefined }); } else if (route.name == '주문관리') { - if (isFocused) - navigation.reset({ - routes: [{ name: route.name, params: { id: undefined } }], - }); - else navigation.navigate(route.name, { id: undefined }); - } else if (route.name == '마이페이지') { + if (isFocused) + navigation.reset({ + routes: [{ name: route.name, params: { id: undefined } }], + }); + else navigation.navigate(route.name, { id: undefined }); + } else if (route.name == '마이페이지') { if (isFocused) navigation.reset({ routes: [{ name: route.name, params: { id: undefined } }], diff --git a/src/components/Auth/BasicForm.tsx b/src/components/Auth/BasicForm.tsx index d8106b5..bfeabdd 100644 --- a/src/components/Auth/BasicForm.tsx +++ b/src/components/Auth/BasicForm.tsx @@ -14,7 +14,7 @@ import DownArrow from '../../assets/common/DownArrow.svg'; import LeftArrow from '../../assets/common/Arrow.svg'; import Logo from '../../assets/common/Logo.svg'; import { FormProps } from './SignIn'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import InputBox from '../../common/InputBox'; import InputView from '../../common/InputView'; import BottomButton from '../../common/BottomButton'; @@ -104,18 +104,18 @@ export default function BasicForm({ navigation, route }: FormProps) { const is_reformer = route.params?.is_reformer ?? false; const { width, height } = Dimensions.get('window'); const [form, setForm] = useState({ - mail: '', - domain: undefined, - password: '', - nickname: '', - agreement: { + mail: route.params?.mail || '', + domain: route.params?.domain || undefined, + password: route.params?.password || '', + nickname: route.params?.nickname || '', + agreement: route.params?.agreement || { a: false, b: false, c: false, d: false, }, - introduce: '', - profile_image: undefined, + introduce: route.params?.introduce || '', + profile_image: route.params?.profile_image || undefined, }); const [checkPw, setCheckPw] = useState(''); const [isModalVisible, setModalVisible] = useState(false); // 리폼러 가입 모달 @@ -125,30 +125,41 @@ export default function BasicForm({ navigation, route }: FormProps) { '^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,15}$', ); + useEffect(() => { + if (route.params?.form) { + setForm(route.params.form) + } + }, [route.params?.form]); + const handleLogin = async () => { // 로그인 API 사용해서 토큰 발급 const params = { email: form.mail + '@' + form.domain, password: form.password } - const response = await request.post(`/api/user/login`, params); - await processLoginResponse2( // userRole 설정 함수 - response - ); + if (form.mail === '' || form.password === '') { + Alert.alert('이메일과 비밀번호를 모두 입력해주세요.') + } else { + const response = await request.post(`/api/user/login`, params); + await processLoginResponse2( // userRole 설정 함수 + response + ); + } }; const passwordValidation = async () => { - if (form.agreement.a === false || form.agreement.b === false || form.agreement.c === false || - form.domain === undefined || form.mail === '') { + const form_ = { ...form } + if (form_.agreement.a === false || form_.agreement.b === false || form_.agreement.c === false || + form_.domain === undefined || form_.mail === '') { Alert.alert('필수 사항들을 모두 입력해주세요.') - } else if (!form.domain.includes('.')) { + } else if (!form_.domain.includes('.')) { Alert.alert('올바른 이메일 형식이 아닙니다.') } else { // 누락된거 없을 때 - if (passwordRegExp.exec(form.password)) { + if (passwordRegExp.exec(form_.password)) { if (is_reformer === true) { // 리폼러의 경우 await handleSubmit(); // 일단 회원가입 하고, handleLogin(); // 토큰 발급 로직 } else { // 업씨러의 경우 - navigation.navigate('Upcyer', { form }) + navigation.navigate('Upcyer', { form_ }) } } else { Alert.alert('비밀번호가 올바르지 않습니다.'); @@ -367,6 +378,7 @@ export default function BasicForm({ navigation, route }: FormProps) { value="다음" pressed={false} onPress={() => { + console.log(form); passwordValidation(); }} // onPress={() => handleNext()} // 이건 임시. diff --git a/src/components/Auth/Login.tsx b/src/components/Auth/Login.tsx index 6251f66..a82388f 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Auth/Login.tsx @@ -114,7 +114,7 @@ export async function processLoginResponse( // 통상 로그인시 호출 함수 setUser: (user: UserType) => void // setUser 전달 ) { // const navigation = useNavigation>(); - if (response?.status == 200) { + if (response?.status === 200) { const accessToken = await response.data.access; const refreshToken = await response.data.refresh; setAccessToken(accessToken); @@ -124,14 +124,10 @@ export async function processLoginResponse( // 통상 로그인시 호출 함수 navigate(); // 인자로 전달받은 네비게이팅 수행 console.log('로그인 성공'); - } else if (response.status == 400) { - Alert.alert( - response.data.extra.fields !== undefined - ? response.data.extra.fields.detail - : response.data.message, - ); - } else { - Alert.alert('예상치 못한 오류가 발생하였습니다.'); + } else if (response.status === 400) { + Alert.alert('비밀번호가 틀렸습니다.'); + } else if (response.status === 404) { + Alert.alert('이메일을 찾을 수 없습니다.'); } }; @@ -143,17 +139,21 @@ export default function Login({ navigation, route }: LoginProps) { const request = Request(); const handleLogin = async () => { // 로그인 함수 - const response = await request.post(`/api/user/login`, form); - processLoginResponse( - response, - () => { - const parentNav = navigation.getParent(); - if (parentNav != undefined) parentNav.goBack(); - else navigation.navigate('Home'); - }, - setLogin, - setUser // setUser 전달, - ); + if (form.email === '' || form.password === '') { + Alert.alert('이메일과 비밀번호를 모두 입력해주세요.') + } else { + const response = await request.post(`/api/user/login`, form); + processLoginResponse( + response, + () => { + const parentNav = navigation.getParent(); + if (parentNav != undefined) parentNav.goBack(); + else navigation.navigate('Home'); + }, + setLogin, + setUser // setUser 전달, + ); + } }; return ( diff --git a/src/components/Auth/Reformer/Profile/Service.tsx b/src/components/Auth/Reformer/Profile/Service.tsx index dc15798..39add92 100644 --- a/src/components/Auth/Reformer/Profile/Service.tsx +++ b/src/components/Auth/Reformer/Profile/Service.tsx @@ -24,7 +24,9 @@ import { createStackNavigator, } from '@react-navigation/stack'; import styled from 'styled-components/native'; +import DetailScreenHeader from '../../../Home/components/DetailScreenHeader.tsx'; +// 리폼러 입장에서 보는 마이페이지! // Stack Navigator 생성 const Stack = createStackNavigator(); @@ -59,6 +61,13 @@ export const ProfileSection = ({ return ( + { }} + onPressRight={() => navigation.navigate('FixMyPage', { userInfo })} + leftButton='CustomBack' + rightButton='Fix' + /> {/* 배경 이미지와 프로필 사진 추가 */} >; } +type UpcyerPageProps = StackScreenProps; + function ProfilePic({ form, setForm }: UpcyerProps) { - const [photo, setPhoto] = useState(form.profile_image); + const [photo, setPhoto] = useState(form?.profile_image); const [handleAddButtonPress, handleImagePress] = useImagePicker(setPhoto); useEffect(() => { @@ -88,14 +95,32 @@ function ProfilePic({ form, setForm }: UpcyerProps) { ); } -export const UpcyFormProfile = (navigation: any, route: any) => { +export const UpcyFormProfile = ({ navigation, route }: UpcyerPageProps) => { const { width } = Dimensions.get('screen'); const [isModalVisible, setModalVisible] = useState(false); const [nickname, setNickname] = useState(''); const [introduce, setIntroduce] = useState(''); const request = Request(); - const { form } = route.params; - const [form_, setForm] = useState(form); + const form = route.params.form; + const [form_, setForm] = useState({ + mail: form?.mail || '', + domain: form?.domain || '', + password: form?.password || '', + nickname: form?.nickname || '', + agreement: form?.agreement, + introduce: form?.introduce || '', + profile_image: form?.profile_image || undefined, + }); + + useEffect(() => { + form.agreement = form_.agreement; + form.domain = form_.domain; + form.introduce = form_.introduce; + form.mail = form_.mail; + form.nickname = form_.nickname; + form.password = form_.password; + form.profile_image = form_.profile_image; + }, [form_]) const handleSubmit = async () => { const params = { @@ -153,7 +178,7 @@ export const UpcyFormProfile = (navigation: any, route: any) => { setForm(prev => { return { ...prev, nickname: value }; @@ -163,7 +188,7 @@ export const UpcyFormProfile = (navigation: any, route: any) => { /> setForm(prev => { return { ...prev, introduce: value }; @@ -172,7 +197,7 @@ export const UpcyFormProfile = (navigation: any, route: any) => { caption={{ default: '본인을 소개하는 글을 작성해주세요' }} long={true} /> - console.log(form_)}> + console.log(form)}> ddddddd { const data = [ { diff --git a/src/components/Home/Market/Service.tsx b/src/components/Home/Market/Service.tsx index 018c9c4..c5ec05a 100644 --- a/src/components/Home/Market/Service.tsx +++ b/src/components/Home/Market/Service.tsx @@ -6,6 +6,8 @@ import { TouchableOpacity, StyleSheet, ImageBackground, + ActivityIndicator, + Alert, } from 'react-native'; import { Title20B, @@ -18,14 +20,19 @@ import HeartButton from '../../../common/HeartButton'; import DetailModal from '../Market/GoodsDetailOptionsModal'; import { Styles } from '../../../types/UserTypes.ts'; import { SelectedOptionProps } from '../HomeMain.tsx'; +import { getAccessToken } from '../../../common/storage.js'; +import Request from '../../../common/requests.js'; +// 홈화면에 있는, 서비스 전체 리스트! interface ServiceCardProps { - name: string; - price: number; - tags: string[]; + name: string; // 리폼러 이름 + basic_price: number; + service_styles: string[]; imageUri: string; - title: string; - description: string; + service_title: string; + service_content: string; + market_uuid: string; + service_uuid: string; } interface ServiceCardComponentProps extends ServiceCardProps { @@ -38,33 +45,39 @@ const MAX_PRICE: number = 24000; const serviceCardDummyData: ServiceCardProps[] = [ { name: '하느리퐁퐁', - price: 100000, - tags: ['빈티지', '미니멀', '캐주얼'] as Styles[], + basic_price: 100000, + service_styles: ['빈티지', '미니멀', '캐주얼'] as Styles[], imageUri: 'https://image.made-in-china.com/2f0j00efRbSJMtHgqG/Denim-Bag-Youth-Fashion-Casual-Small-Mini-Square-Ladies-Shoulder-Bag-Women-Wash-Bags.webp', - title: '청바지 에코백 만들어 드립니다', - description: + service_title: '청바지 에코백 만들어 드립니다', + service_content: '안입는 청바지를 활용한 나만의 에코백! 아주 좋은 에코백 환경에도 좋고 나에게도 좋고 어찌구저찌구한 에코백입니다 최고임 짱짱', + market_uuid: 'ss', + service_uuid: 'k1', }, { name: '똥구르리리', - price: 20000, - tags: ['미니멀'] as Styles[], + basic_price: 20000, + service_styles: ['미니멀'] as Styles[], imageUri: 'https://image.made-in-china.com/2f0j00efRbSJMtHgqG/Denim-Bag-Youth-Fashion-Casual-Small-Mini-Square-Ladies-Shoulder-Bag-Women-Wash-Bags.webp', - title: '커스텀 짐색', - description: + service_title: '커스텀 짐색', + service_content: '안입는 청바지를 활용한 나만의 에코백! 아주 좋은 에코백 환경에도 좋고 나에게도 좋고 어찌구저찌구한 에코백입니다 최고임 짱짱', + market_uuid: 'ss', + service_uuid: 'k2', }, { name: '훌라훌라맨', - price: 50000, - tags: ['빈티지'] as Styles[], + basic_price: 50000, + service_styles: ['빈티지'] as Styles[], imageUri: 'https://image.made-in-china.com/2f0j00efRbSJMtHgqG/Denim-Bag-Youth-Fashion-Casual-Small-Mini-Square-Ladies-Shoulder-Bag-Women-Wash-Bags.webp', - title: '청바지 에코백 만들어 드립니다', - description: + service_title: '청바지 에코백 만들어 드립니다', + service_content: '안입는 청바지를 활용한 나만의 에코백! 아주 좋은 에코백 환경에도 좋고 나에게도 좋고 어찌구저찌구한 에코백입니다 최고임 짱짱', + market_uuid: 'ss', + service_uuid: 'k3', }, ]; @@ -73,7 +86,7 @@ type ServiceMarketProps = { navigation: any; }; -const ServiceMarket = ({ +const EntireServiceMarket = ({ selectedFilterOption, navigation, }: ServiceMarketProps) => { @@ -85,6 +98,8 @@ const ServiceMarket = ({ }); const [modalOpen, setModalOpen] = useState(false); + const [loading, setLoading] = useState(true); // 로딩용 + const request = Request(); const [selectedStyles, setSelectedStyles] = useState([]); const [serviceCardData, setServiceCardData] = useState(serviceCardDummyData); @@ -92,17 +107,59 @@ const ServiceMarket = ({ const serviceTitle: string = '지금 주목해야 할 업사이클링 서비스'; const serviceDescription: string = '안 입는 옷을 장마 기간에 필요한 물품으로'; + const fetchData = async () => { + const accessToken = await getAccessToken(); + const headers = { + Authorization: `Bearer ${accessToken}` + } + if (accessToken === undefined) { + console.log('이 경우에는 어떻게 할까요..?'); + + } else { + try { + // API 호출 + const response = await request.get(`/api/market/service`, headers); + if (response && response.status === 200) { + // servicecard 코드 작성해야함!! + + } else { + Alert.alert('오류가 발생했습니다.'); + console.log(response); + } + } catch (error) { + console.error(error); + } finally { + // 로딩 상태 false로 변경 + setLoading(false); + } + } + }; + + // 컴포넌트가 처음 렌더링될 때 API 호출 + useEffect(() => { + fetchData(); + }, []); + useEffect(() => { if (selectedFilterOption == '가격순') { - // filter by price + // filter by basic_price const sortedByPriceData = [...serviceCardDummyData].sort( - (a, b) => a.price - b.price, + (a, b) => a.basic_price - b.basic_price, ); setServiceCardData(sortedByPriceData); } // TODO: add more filtering logic here }, [selectedFilterOption]); + // 로딩 중일 때 로딩 스피너 표시 + if (loading) { + return ( + + + + ); + } + return ( { return ( ); @@ -143,12 +202,14 @@ const ServiceMarket = ({ export const ServiceCard = ({ name, - price, - tags, + basic_price, + service_styles, imageUri, - title, - description, + service_title, + service_content, navigation, + market_uuid, + service_uuid, }: ServiceCardComponentProps) => { const [like, setLike] = useState(false); @@ -157,16 +218,16 @@ export const ServiceCard = ({ return ( { navigation.navigate('ServiceDetailPage', { reformerName: name, - serviceName: title, - basicPrice: price, + serviceName: service_title, + basicPrice: basic_price, maxPrice: MAX_PRICE, reviewNum: REVIEW_NUM, - tags: tags, + service_styles: service_styles, backgroundImageUri: imageUri, profileImageUri: imageUri, }); @@ -179,22 +240,22 @@ export const ServiceCard = ({ // FIXME: fix here with imageUri variable }}> {name} - {price} 원 ~ - - {tags.map((tag, index) => { + {basic_price} 원 ~ + + {service_styles.map((service_style, index) => { return ( - {tag} + {service_style} ); })} - {title} + {service_title} setLike(!like)} /> - {description} + {service_content} ); }; @@ -209,7 +270,7 @@ const styles = StyleSheet.create({ flex: 1, marginHorizontal: 0, }, - tag: { + service_style: { display: 'flex', flexDirection: 'row', position: 'absolute', @@ -266,4 +327,4 @@ const TextStyles = StyleSheet.create({ }, }); -export default ServiceMarket; +export default EntireServiceMarket; diff --git a/src/components/Home/components/DetailScreenHeader.tsx b/src/components/Home/components/DetailScreenHeader.tsx index 15f7059..7117f28 100644 --- a/src/components/Home/components/DetailScreenHeader.tsx +++ b/src/components/Home/components/DetailScreenHeader.tsx @@ -1,5 +1,5 @@ import { SafeAreaView, TouchableOpacity, View } from 'react-native'; -import { Body14M, Subtitle18B } from '../../../styles/GlobalText'; +import { Body14M, Subtitle18B, Body16B } from '../../../styles/GlobalText'; import React from 'react'; import { BLACK, LIGHTGRAY } from '../../../styles/GlobalColor'; import LeftArrowIcon from '../../../assets/common/Arrow.svg'; @@ -11,7 +11,7 @@ interface HeaderProps { title: string; leftButton: 'CustomBack' | 'LeftArrow' | 'Exit' | 'None'; onPressLeft: () => void; - rightButton: 'Save' | 'Search' | 'Edit' | 'Exit' | 'None'; + rightButton: 'Save' | 'Search' | 'Edit' | 'Exit' | 'None' | 'Fix'; onPressRight: () => void; saved?: number; border?: boolean; @@ -34,6 +34,7 @@ const DetailScreenHeader = ({ LeftArrow: , CustomBack: , None: <>, + Fix: ..., }; return ( 서비스 탭에 들어가는 컴포넌트임 +// 마켓 페이지(업씨러가 보는 마켓 페이지) -> 서비스 탭에 들어가는 컴포넌트임 interface ServiceItemProps { navigation: any; @@ -11,12 +11,14 @@ const ServiceItem = ({ navigation }: ServiceItemProps) => { <> diff --git a/src/pages/FixMyPage.tsx b/src/pages/FixMyPage.tsx index 40127b1..f428ddc 100644 --- a/src/pages/FixMyPage.tsx +++ b/src/pages/FixMyPage.tsx @@ -90,7 +90,9 @@ function ProfilePic({ form, setForm }: ProfileProps) { bottom: 0, right: 0, }}> - + + + ); diff --git a/src/pages/MyPage.tsx b/src/pages/MyPage.tsx index 5726b90..d8d25d6 100644 --- a/src/pages/MyPage.tsx +++ b/src/pages/MyPage.tsx @@ -1,6 +1,7 @@ import { Alert, Button, + Dimensions, FlatList, Image, ImageBackground, @@ -69,6 +70,7 @@ const MyPageScreen = ({ }; const MyPageMainScreen = ({ navigation, route }: MypageStackProps) => { + const { width, height } = Dimensions.get('screen'); const ProfileSection = ({ nickname, backgroundphoto, @@ -88,30 +90,20 @@ const MyPageMainScreen = ({ navigation, route }: MypageStackProps) => { title="" leftButton="CustomBack" onPressLeft={() => { }} - rightButton="Edit" + rightButton="Fix" onPressRight={editProfile} /> - - + + {profile_image === undefined || profile_image.uri === undefined ? ( // 전자는 편집페이지에서 사진 삭제했을 경우, 후자는 가장 처음에 로딩될 경우 { { } /> )} - - {nickname} + + {nickname} @@ -218,8 +210,8 @@ const MyPageMainScreen = ({ navigation, route }: MypageStackProps) => { // }; const [routes] = useState([ - { key: 'order', title: '주문' }, - { key: 'like', title: '좋아요' }, + { key: 'order', title: '주문내역' }, + // { key: 'like', title: '좋아요' }, ]); const flatListRef = useRef(null); const scrollRef = useRef(null); @@ -259,7 +251,7 @@ const MyPageMainScreen = ({ navigation, route }: MypageStackProps) => { fontWeight: '700', fontSize: 16, }} - onTabPress={() => Alert.alert('준비중입니다!ㅠㅠ')} + // onTabPress={() => Alert.alert('준비중입니다!ㅠㅠ')} // 룩북, 좋아요 모아보기 기능 구현되면 위의 onTapPress는 삭제할 것 /> )} @@ -269,11 +261,11 @@ const MyPageMainScreen = ({ navigation, route }: MypageStackProps) => { ( {route.key === 'order' && } - {route.key === 'like' && + {/* {route.key === 'like' && - } + } */} ) )}