From 8829a44b8882ec3ab412e968e950b8efb24f3f18 Mon Sep 17 00:00:00 2001 From: corpi Date: Thu, 1 Jan 2026 18:43:52 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20=EB=B0=8F=20=ED=99=88=20=EC=8A=A4=ED=81=AC=EB=A6=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + app.json | 14 +++--- package-lock.json | 95 +++++++++++++++++++++++++++++++++++++ package.json | 13 ++++- src/api/auth.ts | 28 +++++++++++ src/api/client.ts | 25 ++++++++++ src/navigation/AuthGate.tsx | 6 +-- src/screens/HomeScreen.tsx | 12 ++++- src/screens/LoginScreen.tsx | 24 +++++++++- src/store/auth.tsx | 52 ++++++++++++++++---- 10 files changed, 248 insertions(+), 23 deletions(-) create mode 100644 src/api/auth.ts create mode 100644 src/api/client.ts diff --git a/.gitignore b/.gitignore index d914c32..5f769de 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.* # generated native folders /ios /android +.env.* +.idea diff --git a/app.json b/app.json index ce67c44..0543463 100644 --- a/app.json +++ b/app.json @@ -1,27 +1,29 @@ { "expo": { "name": "audiobook-app", - "slug": "audiobook-app", + "slug": "podcast", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", - "newArchEnabled": true, "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.corpi.audiobookapp", + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false + } }, "android": { + "package": "com.corpi.audiobookapp", "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" - }, - "edgeToEdgeEnabled": true, - "predictiveBackGestureEnabled": false + } }, "web": { "favicon": "./assets/favicon.png" diff --git a/package-lock.json b/package-lock.json index d09a78d..de8e884 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "audiobook-app", "version": "1.0.0", "dependencies": { + "@react-native-async-storage/async-storage": "2.2.0", "@react-navigation/native": "^7.1.26", "@react-navigation/native-stack": "^7.9.0", "expo": "~54.0.30", @@ -19,6 +20,7 @@ }, "devDependencies": { "@types/react": "~19.1.0", + "dotenv-cli": "^11.0.0", "typescript": "~5.9.2" } }, @@ -2722,6 +2724,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz", @@ -4346,6 +4360,64 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-cli": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", + "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^17.1.0", + "dotenv-expand": "^12.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-cli/node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-cli/node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-cli/node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dotenv-expand": { "version": "11.0.7", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", @@ -5525,6 +5597,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -6423,6 +6504,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7471,6 +7564,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7508,6 +7602,7 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", diff --git a/package.json b/package.json index e81e041..8a59471 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,19 @@ "start": "expo start", "android": "expo start --android", "ios": "expo start --ios", - "web": "expo start --web" + "web": "expo start --web", + "start:local": "EXPO_NO_DOTENV=1 dotenv -e .env.local -- expo start -c", + "start:dev": "EXPO_NO_DOTENV=1 dotenv -e .env.dev -- expo start -c", + "start:prod": "EXPO_NO_DOTENV=1 dotenv -e .env.prod -- expo start -c", + "ios:local": "EXPO_NO_DOTENV=1 dotenv -e .env.local -- expo start -c --ios", + "ios:dev": "EXPO_NO_DOTENV=1 dotenv -e .env.dev -- expo start -c --ios", + "ios:prod": "EXPO_NO_DOTENV=1 dotenv -e .env.prod -- expo start -c --ios", + "android:local": "EXPO_NO_DOTENV=1 dotenv -e .env.local -- expo start -c --android", + "android:dev": "EXPO_NO_DOTENV=1 dotenv -e .env.dev -- expo start -c --android", + "android:prod": "EXPO_NO_DOTENV=1 dotenv -e .env.prod -- expo start -c --android" }, "dependencies": { + "@react-native-async-storage/async-storage": "2.2.0", "@react-navigation/native": "^7.1.26", "@react-navigation/native-stack": "^7.9.0", "expo": "~54.0.30", @@ -20,6 +30,7 @@ }, "devDependencies": { "@types/react": "~19.1.0", + "dotenv-cli": "^11.0.0", "typescript": "~5.9.2" }, "private": true diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..915fe27 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,28 @@ +import { apiFetch } from "./client"; + +export type LoginRequest = { + loginId: string; + password: string; +}; + +export async function loginApi(body: LoginRequest): Promise { + try { + const res = await apiFetch("/api/v1/auth/login", { + method: "POST", + body: JSON.stringify(body), + }); + + console.log("res status:", res.status); + const json = (await res.json()) as any; + + const token: string | undefined = + json?.data?.accessToken ?? json?.accessToken ?? json?.data?.token; + + if (!token) throw new Error("No accessToken in response"); + + return token; + } catch (e) { + console.error("로그인 api 실패:", e); + throw e; + } +} diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..2ac53e3 --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,25 @@ +function getApiBaseUrl() { + const url = process.env.EXPO_PUBLIC_API_BASE_URL?.trim(); + if (!url) throw new Error("Missing EXPO_PUBLIC_API_BASE_URL"); + return url.replace(/\/$/, ""); +} + +export async function apiFetch(path: string, init?: RequestInit) { + const API_BASE_URL = getApiBaseUrl(); + const safePath = path.startsWith("/") ? path : `/${path}`; + + const res = await fetch(`${API_BASE_URL}${safePath}`, { + ...init, + headers: { + "Content-Type": "application/json", + ...(init?.headers ?? {}), + }, + }); + + if (!res.ok) { + const text = await res.text().catch(() => ""); + throw new Error(`HTTP ${res.status} ${res.statusText} ${text}`); + } + + return res; +} diff --git a/src/navigation/AuthGate.tsx b/src/navigation/AuthGate.tsx index bdc50f1..edc446b 100644 --- a/src/navigation/AuthGate.tsx +++ b/src/navigation/AuthGate.tsx @@ -1,10 +1,10 @@ import React from "react"; +import { useAuth } from "../store/auth"; import AppStack from "./AppStack"; import AuthStack from "./AuthStack"; export default function AuthGate() { - // const { isAuthed } = useAuth(); - const isAuthed = true; - // return ; + const { isAuthed } = useAuth(); + return isAuthed ? : ; } diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index fb615a5..e3ef1b1 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -1,9 +1,17 @@ -import { StyleSheet, Text, View } from "react-native"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { Button, StyleSheet, View } from "react-native"; export default function HomeScreen() { return ( - feawfewa +