Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2e0f1e6
Initial commit
andada-creator Feb 9, 2026
209952d
feat: 로그인 화면 및 회원가입 화면 레이아웃 및 스타일 구현
andada-creator Feb 16, 2026
ba708d6
feat: 전화번호, 아이디 중복 확인 기능 추가
andada-creator Feb 16, 2026
58db8c1
Merge pull request #3 from GDGoC2026-TripleS-Project/Feat/#1-login-ui…
andada-creator Feb 17, 2026
ee55af9
[Feat] 인증번호 인증 로직 설정 및 테스트용(id, pw, authCode) 구현, 에러 사항 수정
andada-creator Feb 17, 2026
4baf37c
메인페이지 이번주 인기글 top10, 전체글 조회 부분 구현
andada-creator Feb 18, 2026
fbc8979
메인페이지 구현(완)(디자인은 수정필요)
andada-creator Feb 18, 2026
088e8c7
메인페이지 하단 탭 바 구현
andada-creator Feb 18, 2026
a06efc9
Feat: 메인 페이지 UI 및 PostCard 디자인 수정 #2
andada-creator Feb 19, 2026
f25936b
Merge pull request #5 from GDGoC2026-TripleS-Project/Feat/#2-main-page
andada-creator Feb 19, 2026
011b9f2
Feat: 계정 설정 페이지 UI 및 프로필 사진 변경 로직 생성
andada-creator Feb 19, 2026
a774cc5
Feat: 모달 창 구현 및 소셜 연동 로그인 버튼 구현
andada-creator Feb 19, 2026
b3fbb39
Merge pull request #6 from GDGoC2026-TripleS-Project/Feat/#3-account-…
andada-creator Feb 19, 2026
7d98e61
아카이브 UI 구현
andada-creator Feb 19, 2026
7d0ee29
[fix] 하단 탭 바 내베게이션 로직 오류 수정
andada-creator Feb 20, 2026
6542765
Merge pull request #8 from GDGoC2026-TripleS-Project/Feat/#4-archive
andada-creator Feb 20, 2026
1db602e
[Feat] menu페이지 UI구현
andada-creator Feb 20, 2026
a4d6253
[Fix]메뉴 네비게이션 경로 수정
andada-creator Feb 20, 2026
a892278
----마지막 커밋----
andada-creator Feb 21, 2026
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
44 changes: 44 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
HEAD
# Logs
logs
*.log
Expand Down Expand Up @@ -137,3 +138,46 @@ dist
# Vite logs files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/
expo-env.d.ts

# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo

# generated native folders
/ios
/android
7403bcd (Initial commit)
20 changes: 20 additions & 0 deletions App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
36 changes: 36 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"expo": {
"name": "PastUs_FE",
"slug": "PastUs_FE",
"version": "1.0.0",
"scheme": "pastus",
"orientation": "portrait",
"icon": "./assets/PastUs_SplashText.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/PastUs_SplashText.png",
"resizeMode": "contain",
"backgroundColor": "#2B57D0"
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/PastUs_SplashText.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true
},
"web": {
"favicon": "./assets/PastUs_SplashText.png"
},
"plugins": [
"expo-router",
"expo-secure-store",
"expo-font"
]
}
}

18 changes: 18 additions & 0 deletions app/(tabs)/_layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { Tabs } from 'expo-router';
import BottomBar from '../../src/components/navigation/BottomBar';

export default function TabLayout() {
return (
<Tabs
tabBar={(props) => (
// 🚀 현재 인덱스에 따라 activeTab을 home 또는 profile로 전달합니다.
<BottomBar activeTab={props.state.index === 0 ? 'home' : 'profile'} />
)}
screenOptions={{ headerShown: false }}
>
<Tabs.Screen name="main" />
<Tabs.Screen name="profile" />
</Tabs>
);
}
210 changes: 210 additions & 0 deletions app/(tabs)/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import React, { useEffect, useState } from 'react';
import { View, Text, ScrollView, StyleSheet, Pressable, ActivityIndicator } from 'react-native';
import { useRouter, Stack } from 'expo-router'; // 🚀 Stack 추가!
import * as SecureStore from 'expo-secure-store';
import { Ionicons } from '@expo/vector-icons';
import { SafeAreaView } from 'react-native-safe-area-context';

import { getMyProfile } from '../../src/api/userService';
import { getAllPosts, getTrendingPosts, getTrendingTags } from '../../src/api/postService';
import PostCard from '../../src/components/main/PostCard';

export default function MainScreen() {
const router = useRouter();
const [userInfo, setUserInfo] = useState(null);
const [popularTags, setPopularTags] = useState([]);
const [popularPosts, setPopularPosts] = useState([]);
const [recentPosts, setRecentPosts] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => { fetchAllData(); }, []);

const fetchAllData = async () => {
try {
const token = await SecureStore.getItemAsync('userToken');
if (!token) { router.replace('/auth/login'); return; }

const [userRes, tagsRes, popRes, recentRes] = await Promise.all([
getMyProfile(),
getTrendingTags(),
getTrendingPosts(),
getAllPosts(0, 3),
]);

if (userRes.status === 200) setUserInfo(userRes.data);
if (tagsRes.status === 200) setPopularTags(tagsRes.data);
if (popRes.status === 200) setPopularPosts(popRes.data.slice(0, 3));
if (recentRes.status === 200) setRecentPosts(recentRes.data.content);

} catch (error) {
console.log("데이터 로딩 중 에러:", error);
} finally {
setLoading(false);
}
};

if (loading) return <ActivityIndicator size="large" color="#2B57D0" style={{ flex: 1 }} />;

return (
<SafeAreaView style={styles.container} edges={['top']}>
{/* 🚀 시스템 헤더 숨기기 */}
<Stack.Screen options={{ headerShown: false }}/>

{/* 🚀 헤더 섹션: View로 감싸서 가로 정렬 */}
<View style={styles.header}>
<Text style={styles.logo}>PastUs</Text>
<View style={styles.headerIcons}>
<Pressable onPress={() => router.push('/search')}>
<Ionicons name="search-outline" size={28} color="black" />
</Pressable>
<Pressable onPress={() => router.push('/menu')} hitSlop={15}>
<Ionicons name="menu-outline" size={28} color="black" style={{ marginLeft: 15 }} />
</Pressable>
</View>
</View>

<ScrollView showsVerticalScrollIndicator={false}>
{/* 상단 배너 */}
<View style={styles.proBanner}>
<Text style={styles.proTitle}>정확한 문장 검색 + 제약없는 글쓰기</Text>
<Text style={styles.proSub}>
{userInfo?.userName}님의 현재 신뢰도는 {userInfo?.trustScore}% 입니다.
</Text>
</View>

{/* 인기 태그 */}
<View style={styles.tagSection}>
<Text style={styles.sectionTitle}>이번 주 인기 태그</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.tagList}>
{popularTags.map((tag, index) => (
<View key={index} style={styles.tagBadge}>
<Text style={styles.tagText}>#{tag}</Text>
</View>
))}
</ScrollView>
</View>

{/* 인기글 3개 */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>이번 주 인기글 Top 10</Text>
<Pressable
onPress={() => router.push('/posts/popular-list')}
hitSlop={{ top:20, bottom: 20, left: 20, right: 20}}
>
<Ionicons name="chevron-forward" size={20} color="#000" />
</Pressable>
</View>
{popularPosts.map(post => <PostCard key={post.postId} item={post} />)}
</View>

{/* 전체글 3개 */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>전체글 보기</Text>
<Pressable
onPress={() => router.push('/posts/all-list')}
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
>
<Ionicons name="chevron-forward" size={20} color="#000" />
</Pressable>
</View>
{recentPosts.map(post => <PostCard key={post.postId} item={post} />)}
</View>
</ScrollView>



</SafeAreaView> // 🚀 여기서 닫아야 모든 콘텐츠가 안전 영역 안에 들어옵니다!
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
},
/* 🚀 1. 헤더: 로고와 아이콘 정밀 배치 */
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 12
},
logo: {
fontSize: 26,
fontWeight: '700',
fontFamily: 'NoticiaText-Bold',
color: '#000'
},
headerIcons: {
flexDirection: 'row',
alignItems: 'center'
},

/* 🚀 2. 프로 배너: 피그마 블루 컬러 적용 */
proBanner: {
backgroundColor: '#2B57D0',
marginHorizontal: 20,
marginVertical: 15,
paddingVertical: 24,
borderRadius: 12, // 시안의 둥근 모서리
alignItems: 'center'
},
proTitle: {
color: '#fff',
fontSize: 16,
fontWeight: '700'
},
proSub: {
color: 'rgba(255, 255, 255, 0.8)',
fontSize: 12,
marginTop: 6
},

/* 🚀 3. 섹션 타이틀: 피그마 규격(18px, 600) 강제 적용 */
section: {
paddingHorizontal: 20,
marginBottom: 30
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12
},
sectionTitle: {
fontSize: 18, // 피그마 명세
fontWeight: '600', // 피그마 명세 (SemiBold)
lineHeight: 22, // 피그마 명세
color:'#000'
},
more: {
fontSize: 20,
color: '#000',
fontWeight: '300'
},

/* 🚀 4. 인기 태그: 가로 스크롤 및 배지 스타일 */
tagSection: {
paddingLeft: 20, // 왼쪽 정렬 유지를 위해 패딩 분리
marginBottom: 30
},
tagList: {
marginTop: 10
},
tagBadge: {
backgroundColor: '#2B57D0',
paddingHorizontal: 14,
paddingVertical: 7,
borderRadius: 20,
marginRight: 8
},
tagText: {
color: '#fff',
fontSize: 12,
fontWeight: '700'
},

});
Loading