-
Notifications
You must be signed in to change notification settings - Fork 0
카카오 로그인,로그아웃 기능 구현 및 홈 스택페이지 구현 #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "20250603_#9_6\uC6D44\uC77C_\uAE30\uB2A5\uCD94\uAC00_\uC18C\uC15C\uB85C\uADF8\uC778_\uCE74\uCE74\uC624_\uB85C\uADF8\uC778"
Changes from 1 commit
d10d2bf
4c30c69
cfacfc6
7a92af1
77bb87f
1a1922b
985e956
c686f15
e0bb095
b9875db
edef31a
91f8111
f31cdc7
b1e112a
85253d6
7920d04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| EXPO_PUBLIC_KAKAO_REST_API_KEY=fcc79e9199b5dbcaedfc00bb30b3d4af | ||
| EXPO_PUBLIC_SERVER_BASE_URL=https://api.dailysnap.app |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,4 +2,5 @@ node_modules | |
| android | ||
| ios | ||
| build | ||
| dist | ||
| dist | ||
| tailwind.config.js | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,4 +2,4 @@ node_modules | |
| android | ||
| ios | ||
| build | ||
| dist | ||
| dist | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| export default { | ||
| expo: { | ||
| name: "DailySnap-FE", | ||
| slug: "DailySnap-FE", | ||
| version: "1.0.0", | ||
| orientation: "portrait", | ||
| scheme: "dailysnap", | ||
| userInterfaceStyle: "light", | ||
| newArchEnabled: true, | ||
| splash: { | ||
| resizeMode: "contain", | ||
| backgroundColor: "#ffffff" | ||
| }, | ||
| ios: { | ||
| supportsTablet: true, | ||
| bundleIdentifier: "com.jhsonny.DailySnapFE" | ||
| }, | ||
| android: { | ||
| adaptiveIcon: { | ||
| backgroundColor: "#ffffff" | ||
| }, | ||
| package: "com.jhsonny.DailySnapFE" | ||
| }, | ||
| web: { | ||
| bundler: "metro", | ||
| output: "static", | ||
| favicon: "./assets/images/favicon.png" | ||
| }, | ||
| plugins: [ | ||
| "expo-router", | ||
| "expo-font" | ||
| ], | ||
| experiments: { | ||
| typedRoutes: true | ||
| } | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { Tabs } from "expo-router"; | ||
|
|
||
| export default function TabsLayout() { | ||
| return ( | ||
| <Tabs | ||
| screenOptions={{ | ||
| headerShown: false, | ||
| }} | ||
| > | ||
| <Tabs.Screen | ||
| name="home" | ||
| options={{ | ||
| title: "홈", | ||
| }} | ||
| /> | ||
|
|
||
| <Tabs.Screen | ||
| name="archive" | ||
| options={{ | ||
| title: "아카이브", | ||
| }} | ||
| /> | ||
|
|
||
| <Tabs.Screen | ||
| name="upload" | ||
| options={{ | ||
| title: "업로드", | ||
| }} | ||
| /> | ||
|
|
||
| <Tabs.Screen | ||
| name="awards" | ||
| options={{ | ||
| title: "우수작", | ||
| }} | ||
| /> | ||
|
|
||
| <Tabs.Screen | ||
| name="profile" | ||
| options={{ | ||
| title: "마이", | ||
| }} | ||
| /> | ||
| </Tabs> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import React from "react"; | ||
| import ArchivePage from "../../pages/archive/ArchivePage"; | ||
|
|
||
| export default function ArchiveTab() { | ||
| return <ArchivePage />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import React from "react"; | ||
| import AwardsPage from "../../pages/awards/AwardsPage"; | ||
|
|
||
| export default function AwardsTab() { | ||
| return <AwardsPage />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import React from "react"; | ||
| import HomePage from "../../pages/home/HomePage"; | ||
|
|
||
| export default function HomeTab() { | ||
| return <HomePage />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import React from "react"; | ||
| import ProfilePage from "../../pages/profile/ProfilePage"; | ||
|
|
||
| export default function ProfileTab() { | ||
| return <ProfilePage />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import React from "react"; | ||
| import UploadPage from "../../pages/upload/UploadPage"; | ||
|
|
||
| export default function UploadTab() { | ||
| return <UploadPage />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,36 @@ | ||
| import NetInfo from "@react-native-community/netinfo"; | ||
| import { onlineManager, QueryClientProvider } from "@tanstack/react-query"; | ||
| import { Stack } from "expo-router"; | ||
| import { StatusBar } from "expo-status-bar"; | ||
| import { SafeAreaProvider } from "react-native-safe-area-context"; | ||
| import { queryClient } from "../shared/api/query-client"; | ||
| import { AuthProvider } from "../features/auth/model/AuthContext"; | ||
| import "../global.css"; | ||
|
|
||
| onlineManager.setEventListener(setOnline => { | ||
| return NetInfo.addEventListener(state => { | ||
| setOnline(!!state.isConnected); | ||
| }); | ||
| }); | ||
|
|
||
| export { default as styled } from "nativewind"; | ||
|
|
||
| export default function RootLayout() { | ||
| return ( | ||
| <QueryClientProvider client={queryClient}> | ||
| <Stack> | ||
| <Stack.Screen name="index" /> | ||
| </Stack> | ||
| </QueryClientProvider> | ||
| <SafeAreaProvider> | ||
| <QueryClientProvider client={queryClient}> | ||
| <AuthProvider> | ||
| <StatusBar style="auto" /> | ||
| <Stack screenOptions={{ headerShown: false }}> | ||
| {/* 인증 관련 스크린 */} | ||
| <Stack.Screen name="index" /> | ||
| <Stack.Screen name="login" /> | ||
|
|
||
| {/* 메인 앱 스크린 (탭 네비게이션) */} | ||
| <Stack.Screen name="(tabs)" /> | ||
| </Stack> | ||
| </AuthProvider> | ||
| </QueryClientProvider> | ||
| </SafeAreaProvider> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,31 @@ | ||
| import { SafeAreaView, ScrollView, Text } from "react-native"; | ||
| /*eslint-disable */ | ||
| import React, { useEffect } from "react"; | ||
| import { useRouter } from "expo-router"; | ||
| import { useAuth } from "../features/auth/model/AuthContext"; | ||
| import { View, ActivityIndicator } from "react-native"; | ||
|
|
||
| export default function App() { | ||
| const router = useRouter(); | ||
| const { userInfo } = useAuth(); | ||
|
|
||
| useEffect(() => { | ||
| const timer = setTimeout(() => { | ||
| if (userInfo) { | ||
| // 로그인된 사용자면, 홈 네비게이션으로 이동 | ||
| router.replace("/(tabs)/home"); | ||
| } else { | ||
| // 로그인되지 않았으면 로그인 페이지로 이동 | ||
| router.replace("/login"); | ||
| } | ||
| }, 100); | ||
|
|
||
| return () => clearTimeout(timer); | ||
| }, [userInfo, router]); | ||
|
|
||
| // 로딩 화면 표시. TODO: 추후에 디자인 된 로딩스피너로 수정 | ||
| return ( | ||
| <SafeAreaView style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> | ||
| <ScrollView style={{ width: "100%", paddingHorizontal: 16 }}> | ||
| <Text>App.tsx to start working on your app!</Text> | ||
| </ScrollView> | ||
| </SafeAreaView> | ||
| <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> | ||
| <ActivityIndicator size="large" color="#195B35" /> | ||
| </View> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import React from "react"; | ||
| import LoginPage from "../pages/auth/LoginPage"; | ||
|
|
||
| export default function Login() { | ||
| return <LoginPage />; | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| module.exports = function (api) { | ||
| api.cache(true); | ||
| return { | ||
| presets: ['babel-preset-expo'], | ||
| plugins: [ | ||
| 'nativewind/babel', | ||
| [ | ||
| 'module-resolver', | ||
| { | ||
| root: ['./'], | ||
| extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'], | ||
| alias: { | ||
| '@': './', | ||
| }, | ||
| }, | ||
| ], | ||
| ], | ||
| }; | ||
| }; |
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,58 @@ | ||||||||||||||||||
| import type { ReactNode } from "react"; | ||||||||||||||||||
| import React, { createContext, useContext, useState } from "react"; | ||||||||||||||||||
|
|
||||||||||||||||||
| // 사용자 정보 타입 정의 | ||||||||||||||||||
| interface UserInfo { | ||||||||||||||||||
| id: number; | ||||||||||||||||||
| email: string; | ||||||||||||||||||
| nickname: string; | ||||||||||||||||||
| profileImage?: string; | ||||||||||||||||||
| accessToken: string; | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+5
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove duplicate interface definition. The +import type { UserInfo } from "./types";
+
-// 사용자 정보 타입 정의
-interface UserInfo {
- id: number;
- email: string;
- nickname: string;
- profileImage?: string;
- accessToken: string;
-}
-📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| interface AuthContextType { | ||||||||||||||||||
| userInfo: UserInfo | null; // 현재 로그인된 사용자 정보 | ||||||||||||||||||
| login: (userInfo: UserInfo) => void; // 로그인 함수 | ||||||||||||||||||
| logout: () => void; // 로그아웃 함수 | ||||||||||||||||||
| isLoggedIn: boolean; // 로그인 상태 확인 | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // React Context 생성 (초기값은 undefined) | ||||||||||||||||||
| const AuthContext = createContext<AuthContextType | undefined>(undefined); | ||||||||||||||||||
|
|
||||||||||||||||||
| // 인증 컨텍스트를 사용하기 위한 커스텀 훅 | ||||||||||||||||||
| export const useAuth = () => { | ||||||||||||||||||
| const context = useContext(AuthContext); | ||||||||||||||||||
| // AuthProvider 외부에서 사용하려고 하면 에러 발생 | ||||||||||||||||||
| if (context === undefined) { | ||||||||||||||||||
| throw new Error("useAuth must be used within AuthProvider"); | ||||||||||||||||||
| } | ||||||||||||||||||
| return context; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| interface AuthProviderProps { | ||||||||||||||||||
| children: ReactNode; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // 인증 상태를 관리하는 Provider 컴포넌트 | ||||||||||||||||||
| export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => { | ||||||||||||||||||
| // 사용자 정보 상태 관리 (null이면 로그아웃 상태) | ||||||||||||||||||
| const [userInfo, setUserInfo] = useState<UserInfo | null>(null); | ||||||||||||||||||
|
|
||||||||||||||||||
| const login = (userInfo: UserInfo) => { | ||||||||||||||||||
| setUserInfo(userInfo); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const logout = () => { | ||||||||||||||||||
| setUserInfo(null); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const isLoggedIn = !!userInfo; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Context Provider로 하위 컴포넌트들에게 인증 상태와 함수들 제공 | ||||||||||||||||||
| return ( | ||||||||||||||||||
| <AuthContext.Provider value={{ userInfo, login, logout, isLoggedIn }}> | ||||||||||||||||||
| {children} | ||||||||||||||||||
| </AuthContext.Provider> | ||||||||||||||||||
| ); | ||||||||||||||||||
| }; | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| export interface UserInfo { | ||
| id: number; | ||
| email: string; | ||
| nickname: string; | ||
| profileImage?: string; | ||
| accessToken: string; | ||
| } | ||
|
|
||
| export interface SocialLoginProps { | ||
| onLoginSuccess: (userInfo: UserInfo) => void; | ||
| onLoginError: (error: string) => void; | ||
| } | ||
|
|
||
| export type SocialProvider = "kakao" | "naver" | "google"; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,11 @@ | ||||||||||||||||||||||||||||||||
| import React from "react"; | ||||||||||||||||||||||||||||||||
| import { TouchableOpacity, Text } from "react-native"; | ||||||||||||||||||||||||||||||||
| import type { SocialLoginProps } from "../model/types"; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| export const GoogleLoginButton: React.FC<SocialLoginProps> = ({ onLoginSuccess, onLoginError }) => { | ||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||
| <TouchableOpacity> | ||||||||||||||||||||||||||||||||
| <Text className="">구글로 시작하기</Text> | ||||||||||||||||||||||||||||||||
| </TouchableOpacity> | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Implement touch event handling and login logic. The button currently lacks:
<TouchableOpacity
+ onPress={() => {
+ // TODO: Implement Google login logic
+ onLoginError("Google login not implemented yet");
+ }}
>
- <Text className="">구글로 시작하기</Text>
+ <Text className="text-center py-3 px-6 bg-blue-500 text-white rounded-lg">
+ 구글로 시작하기
+ </Text>
</TouchableOpacity>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Move global CSS import to the top of the file.
Global CSS imports should be placed before other imports to ensure styles are loaded first and avoid potential timing issues.
🤖 Prompt for AI Agents