feat: 로그인 화면 및 공통 속성 생성

This commit is contained in:
2025-12-31 18:40:46 +09:00
parent 822cd55c93
commit 7400363466
17 changed files with 689 additions and 17 deletions

27
App.tsx
View File

@@ -1,20 +1,17 @@
import { StatusBar } from 'expo-status-bar'; import { NavigationContainer } from "@react-navigation/native";
import { StyleSheet, Text, View } from 'react-native'; import React from "react";
import { enableScreens } from "react-native-screens";
import AuthGate from "./src/navigation/AuthGate";
import { AuthProvider } from "./src/store/auth";
enableScreens();
export default function App() { export default function App() {
return ( return (
<View style={styles.container}> <AuthProvider>
<Text>Open up App.tsx to start working on your app!</Text> <NavigationContainer>
<StatusBar style="auto" /> <AuthGate />
</View> </NavigationContainer>
</AuthProvider>
); );
} }
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

312
package-lock.json generated
View File

@@ -8,10 +8,14 @@
"name": "audiobook-app", "name": "audiobook-app",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@react-navigation/native": "^7.1.26",
"@react-navigation/native-stack": "^7.9.0",
"expo": "~54.0.30", "expo": "~54.0.30",
"expo-status-bar": "~3.0.9", "expo-status-bar": "~3.0.9",
"react": "19.1.0", "react": "19.1.0",
"react-native": "0.81.5" "react-native": "0.81.5",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "~19.1.0", "@types/react": "~19.1.0",
@@ -2972,6 +2976,123 @@
"integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==", "integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@react-navigation/core": {
"version": "7.13.7",
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.13.7.tgz",
"integrity": "sha512-k2ABo3250vq1ovOh/iVwXS6Hwr5PVRGXoPh/ewVFOOuEKTvOx9i//OBzt8EF+HokBxS2HBRlR2b+aCOmscRqBw==",
"license": "MIT",
"dependencies": {
"@react-navigation/routers": "^7.5.3",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
"query-string": "^7.1.3",
"react-is": "^19.1.0",
"use-latest-callback": "^0.2.4",
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
"react": ">= 18.2.0"
}
},
"node_modules/@react-navigation/core/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@react-navigation/core/node_modules/react-is": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",
"integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
"license": "MIT"
},
"node_modules/@react-navigation/elements": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.3.tgz",
"integrity": "sha512-3+eyvWiVPIEf6tN9UdduhOEHcTuNe3R5WovgiVkfH9+jApHMTZDc2loePTpY/i2HDJhObhhChpJzO6BVjrpdYQ==",
"license": "MIT",
"dependencies": {
"color": "^4.2.3",
"use-latest-callback": "^0.2.4",
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
"@react-native-masked-view/masked-view": ">= 0.2.0",
"@react-navigation/native": "^7.1.26",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0"
},
"peerDependenciesMeta": {
"@react-native-masked-view/masked-view": {
"optional": true
}
}
},
"node_modules/@react-navigation/native": {
"version": "7.1.26",
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.26.tgz",
"integrity": "sha512-RhKmeD0E2ejzKS6z8elAfdfwShpcdkYY8zJzvHYLq+wv183BBcElTeyMLcIX6wIn7QutXeI92Yi21t7aUWfqNQ==",
"license": "MIT",
"dependencies": {
"@react-navigation/core": "^7.13.7",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
"use-latest-callback": "^0.2.4"
},
"peerDependencies": {
"react": ">= 18.2.0",
"react-native": "*"
}
},
"node_modules/@react-navigation/native-stack": {
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.9.0.tgz",
"integrity": "sha512-C/mNPhI0Pnerl7C2cB+6fAkdgSmfKECMERrbyfjx3P6JmEuTC54o+GV1c62FUmlRaRUassVHbtw4EeaY2uLh0g==",
"license": "MIT",
"dependencies": {
"@react-navigation/elements": "^2.9.3",
"color": "^4.2.3",
"sf-symbols-typescript": "^2.1.0",
"warn-once": "^0.1.1"
},
"peerDependencies": {
"@react-navigation/native": "^7.1.26",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
"react-native-screens": ">= 4.0.0"
}
},
"node_modules/@react-navigation/native/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@react-navigation/routers": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
"integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11"
}
},
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.27.8", "version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -3916,6 +4037,19 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -3931,6 +4065,34 @@
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/commander": { "node_modules/commander": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@@ -4096,6 +4258,15 @@
} }
} }
}, },
"node_modules/decode-uri-component": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"license": "MIT",
"engines": {
"node": ">=0.10"
}
},
"node_modules/deep-extend": { "node_modules/deep-extend": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -4301,6 +4472,7 @@
"resolved": "https://registry.npmjs.org/expo/-/expo-54.0.30.tgz", "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.30.tgz",
"integrity": "sha512-6q+aFfKL0SpT8prfdpR3V8HcN51ov0mCGuwQTzyuk6eeO9rg7a7LWbgPv9rEVXGZEuyULstL8LGNwHqusand7Q==", "integrity": "sha512-6q+aFfKL0SpT8prfdpR3V8HcN51ov0mCGuwQTzyuk6eeO9rg7a7LWbgPv9rEVXGZEuyULstL8LGNwHqusand7Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.0", "@babel/runtime": "^7.20.0",
"@expo/cli": "54.0.20", "@expo/cli": "54.0.20",
@@ -4885,6 +5057,12 @@
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": { "node_modules/fast-json-stable-stringify": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -4912,6 +5090,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -5284,6 +5471,12 @@
"loose-envify": "^1.0.0" "loose-envify": "^1.0.0"
} }
}, },
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
"node_modules/is-core-module": { "node_modules/is-core-module": {
"version": "2.16.1", "version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -7222,6 +7415,24 @@
"qrcode-terminal": "bin/qrcode-terminal.js" "qrcode-terminal": "bin/qrcode-terminal.js"
} }
}, },
"node_modules/query-string": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"license": "MIT",
"dependencies": {
"decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/queue": { "node_modules/queue": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
@@ -7274,6 +7485,18 @@
"ws": "^7" "ws": "^7"
} }
}, },
"node_modules/react-freeze": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
"integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=17.0.0"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@@ -7347,6 +7570,33 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-safe-area-context": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
"integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-screens": {
"version": "4.16.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz",
"integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"react-freeze": "^1.0.0",
"react-native-is-edge-to-edge": "^1.2.1",
"warn-once": "^0.1.0"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native/node_modules/@react-native/virtualized-lists": { "node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.81.5", "version": "0.81.5",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
@@ -7820,6 +8070,15 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/sf-symbols-typescript": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz",
"integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -7870,6 +8129,15 @@
"plist": "^3.0.5" "plist": "^3.0.5"
} }
}, },
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/sisteransi": { "node_modules/sisteransi": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -7931,6 +8199,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/sprintf-js": { "node_modules/sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -7994,6 +8271,15 @@
"node": ">= 0.10.0" "node": ">= 0.10.0"
} }
}, },
"node_modules/strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -8499,6 +8785,24 @@
"browserslist": ">= 4.21.0" "browserslist": ">= 4.21.0"
} }
}, },
"node_modules/use-latest-callback": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz",
"integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/utils-merge": { "node_modules/utils-merge": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -8550,6 +8854,12 @@
"makeerror": "1.0.12" "makeerror": "1.0.12"
} }
}, },
"node_modules/warn-once": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz",
"integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
"license": "MIT"
},
"node_modules/wcwidth": { "node_modules/wcwidth": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",

View File

@@ -9,10 +9,14 @@
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
"@react-navigation/native": "^7.1.26",
"@react-navigation/native-stack": "^7.9.0",
"expo": "~54.0.30", "expo": "~54.0.30",
"expo-status-bar": "~3.0.9", "expo-status-bar": "~3.0.9",
"react": "19.1.0", "react": "19.1.0",
"react-native": "0.81.5" "react-native": "0.81.5",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "~19.1.0", "@types/react": "~19.1.0",

View File

@@ -0,0 +1,27 @@
import React from "react";
import { Text, type TextProps, type TextStyle } from "react-native";
import { Theme } from "../../theme/theme";
type Variant = "logo" | "title" | "body" | "muted" | "error";
type Props = TextProps & {
variant?: Variant;
style?: TextStyle | TextStyle[];
};
export default function AppText({ variant = "body", style, ...props }: Props) {
const base: TextStyle = {
color: Theme.Colors.text,
fontSize: Theme.FontSize.md,
};
const variants: Record<Variant, TextStyle> = {
logo: { fontSize: Theme.FontSize.xxl, fontWeight: Theme.FontWeight.bold },
title: { fontSize: Theme.FontSize.xl, fontWeight: Theme.FontWeight.bold },
body: { fontSize: Theme.FontSize.md },
muted: { color: Theme.Colors.mutedText, fontSize: Theme.FontSize.sm },
error: { color: Theme.Colors.danger, fontSize: Theme.FontSize.sm },
};
return <Text {...props} style={[base, variants[variant], style]} />;
}

View File

@@ -0,0 +1,73 @@
import React from "react";
import { Pressable, type PressableProps, StyleSheet, View } from "react-native";
import { Theme } from "../../theme/theme";
import AppText from "./AppText";
type Props = PressableProps & {
title: string;
variant?: "primary" | "secondary";
};
export default function Button({
title,
variant = "primary",
style,
...props
}: Props) {
const isPrimary = variant === "primary";
return (
<Pressable
{...props}
style={({ pressed }) => [
styles.base,
isPrimary ? styles.primary : styles.secondary,
pressed &&
(isPrimary ? styles.primaryPressed : styles.secondaryPressed),
typeof style === "function" ? style({ pressed }) : style,
]}
>
<View>
<AppText
style={{
textAlign: "center",
fontWeight: Theme.FontWeight.bold,
color: isPrimary ? Theme.Colors.text : Theme.Colors.text,
}}
>
{title}
</AppText>
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
base: {
height: 48,
paddingVertical: 0,
paddingHorizontal: Theme.Spacing.lg,
borderRadius: Theme.Radius.md,
alignItems: "center", // 가로 가운데
justifyContent: "center", // 세로 가운데
},
text: {
textAlign: "center",
fontWeight: Theme.FontWeight.bold,
lineHeight: 20,
},
primary: {
backgroundColor: Theme.Colors.primary,
},
primaryPressed: {
backgroundColor: Theme.Colors.primaryPressed,
},
secondary: {
backgroundColor: Theme.Colors.surface,
borderWidth: 1,
borderColor: Theme.Colors.border,
},
secondaryPressed: {
opacity: 0.9,
},
});

View File

@@ -0,0 +1,32 @@
import React from "react";
import { StyleSheet, TextInput, type TextInputProps, View } from "react-native";
import { Theme } from "../../theme/theme";
type Props = TextInputProps;
export default function Input(props: Props) {
return (
<View style={styles.wrap}>
<TextInput
{...props}
placeholderTextColor={Theme.Colors.placeholder}
style={[styles.input, props.style]}
/>
</View>
);
}
const styles = StyleSheet.create({
wrap: {
borderWidth: 1,
borderColor: Theme.Colors.border,
borderRadius: Theme.Radius.md,
backgroundColor: Theme.Colors.inputBg,
},
input: {
paddingHorizontal: Theme.Spacing.md,
paddingVertical: Theme.Spacing.md,
color: Theme.Colors.text,
fontSize: Theme.FontSize.md,
},
});

View File

@@ -0,0 +1,17 @@
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import React from "react";
import HomeScreen from "../screens/HomeScreen";
export type AppStackParamList = {
Home: undefined;
};
const Stack = createNativeStackNavigator<AppStackParamList>();
export default function AppStack() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
);
}

View File

@@ -0,0 +1,10 @@
import React from "react";
import AppStack from "./AppStack";
import AuthStack from "./AuthStack";
export default function AuthGate() {
// const { isAuthed } = useAuth();
const isAuthed = true;
// return <AuthStack />;
return isAuthed ? <AppStack /> : <AuthStack />;
}

View File

@@ -0,0 +1,17 @@
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import React from "react";
import LoginScreen from "../screens/LoginScreen";
export type AuthStackParamList = {
Login: undefined;
};
const Stack = createNativeStackNavigator<AuthStackParamList>();
export default function AuthStack() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={LoginScreen} />
</Stack.Navigator>
);
}

View File

@@ -0,0 +1,18 @@
import { StyleSheet, Text, View } from "react-native";
export default function HomeScreen() {
return (
<View style={styles.container}>
<Text>feawfewa</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "red",
alignContent: "center",
justifyContent: "center",
},
});

View File

@@ -0,0 +1,74 @@
import React from "react";
import { Pressable, StyleSheet, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import AppText from "../components/ui/AppText";
import Button from "../components/ui/Button";
import Input from "../components/ui/Input";
import { Theme } from "../theme/theme";
export default function LoginScreen() {
return (
<SafeAreaView style={styles.safe}>
<View style={styles.container}>
<AppText variant="title">Your personal growth podcast</AppText>
<AppText variant="muted">Sign in to continue</AppText>
<View style={{ height: Theme.Spacing.lg }} />
<Input
placeholder="Username"
autoCapitalize="none"
autoCorrect={false}
textContentType="username"
/>
<View style={{ height: Theme.Spacing.sm }} />
<Input
placeholder="Password"
secureTextEntry
autoCapitalize="none"
autoCorrect={false}
textContentType="password"
/>
<View style={{ height: Theme.Spacing.lg }} />
<Button title="Continue" onPress={() => {}} />
<View style={{ height: Theme.Spacing.md }} />
<Pressable
onPress={() => {
console.log("feawf");
}}
>
<AppText variant="muted" style={styles.signupLine}>
Don&apos;t have an account?{" "}
<AppText style={styles.signupLink}>Sign up</AppText>
</AppText>
</Pressable>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safe: {
flex: 1,
backgroundColor: Theme.Colors.bg,
},
container: {
flex: 1,
padding: Theme.Spacing.xl,
justifyContent: "center",
gap: Theme.Spacing.sm,
},
signupLine: {
textAlign: "center",
},
signupLink: {
color: Theme.Colors.primary,
fontWeight: Theme.FontWeight.bold,
},
});

31
src/store/auth.tsx Normal file
View File

@@ -0,0 +1,31 @@
import React, { createContext, useContext, useMemo, useState } from "react";
export type AuthContextValue = {
isAuthed: boolean;
login: () => void;
logout: () => void;
};
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [isAuthed, setIsAuthed] = useState<boolean>(false);
console.log("isAuthed: " + isAuthed);
const value = useMemo<AuthContextValue>(
() => ({
isAuthed,
login: () => setIsAuthed(true),
logout: () => setIsAuthed(false),
}),
[isAuthed]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextValue {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}

22
src/theme/colors.ts Normal file
View File

@@ -0,0 +1,22 @@
export const Colors = {
// base
bg: "#0B1220",
surface: "#111827",
card: "#0F172A",
// text
text: "#FFFFFF",
mutedText: "#9CA3AF",
placeholder: "#6B7280",
// brand
primary: "#4F46E5",
primaryPressed: "#4338CA",
// status
danger: "#EF4444",
// lines
border: "#22304A",
inputBg: "#0B1220",
} as const;

6
src/theme/radius.ts Normal file
View File

@@ -0,0 +1,6 @@
export const Radius = {
sm: 10,
md: 12,
lg: 16,
xl: 20,
} as const;

8
src/theme/spacing.ts Normal file
View File

@@ -0,0 +1,8 @@
export const Spacing = {
xs: 4,
sm: 8,
md: 12,
lg: 16,
xl: 20,
"2xl": 24,
} as const;

12
src/theme/theme.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Colors } from "./colors";
import { Radius } from "./radius";
import { Spacing } from "./spacing";
import { FontSize, FontWeight } from "./typography";
export const Theme = {
Colors,
Spacing,
Radius,
FontSize,
FontWeight,
} as const;

14
src/theme/typography.ts Normal file
View File

@@ -0,0 +1,14 @@
export const FontSize = {
sm: 13,
md: 15,
lg: 18,
xl: 22,
xxl: 26,
} as const;
export const FontWeight = {
regular: "400",
medium: "500",
semibold: "600",
bold: "700",
} as const;