diff --git a/app.config.ts b/app.config.ts index 7f69c87..a303a14 100644 --- a/app.config.ts +++ b/app.config.ts @@ -31,6 +31,12 @@ const config: ExpoConfig = { googleServicesFile: googleServicesFile, }, plugins: [ + [ + '@react-native-google-signin/google-signin', + { + iosUrlScheme: process.env.EXPO_PUBLIC_IOS_URL_SCHEME, + }, + ], [ 'expo-build-properties', { diff --git a/app/index.tsx b/app/index.tsx index 5a0ef25..afdcccb 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,5 +1,146 @@ +import { StyleSheet, Text, View, StatusBar, Pressable } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { googleSignIn, configureGoogleSignIn } from '../services/auth/google'; +import GoogleLogo from '../assets/svg/google.svg'; +import KakaoLogo from '../assets/svg/kakao.svg'; +import NaverLogo from '../assets/svg/naver.svg'; +import AppleLogo from '../assets/svg/apple.svg'; import { Redirect } from 'expo-router'; export default function Index() { - return ; + configureGoogleSignIn(); + + return ( + + /* + + + + + 모든 동아리를 + {'\n'} + 하나로 연결하다 + + KONECT + + + 소셜 계정으로 로그인 + + + + + + + + + + + + + + + + + */ + ); } + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#FFFFFF', + }, + textContainer: { + paddingTop: 32, + paddingHorizontal: 32, + gap: 10, + }, + title: { + fontSize: 36, + textAlign: 'left', + fontWeight: '900', + }, + description: { + fontSize: 24, + textAlign: 'left', + fontWeight: '800', + }, + buttonContainer: { + flex: 1, + justifyContent: 'flex-end', + alignItems: 'center', + paddingBottom: 72, + gap: 12, + }, + socialText: { + fontSize: 14, + color: '#6B7280', + }, + socialButtonContainer: { + flexDirection: 'row', + gap: 8, + }, + googleLoginButton: { + width: 44, + height: 44, + borderRadius: 50, + borderWidth: 1, + backgroundColor: '#FFFFFF', + alignItems: 'center', + justifyContent: 'center', + borderColor: '#E5E7EB', + }, + kakaoLoginButton: { + width: 44, + height: 44, + borderRadius: 50, + backgroundColor: '#FEE500', + alignItems: 'center', + justifyContent: 'center', + }, + naverLoginButton: { + width: 44, + height: 44, + borderRadius: 50, + backgroundColor: '#03c75a', + alignItems: 'center', + justifyContent: 'center', + }, + appleLoginButton: { + width: 44, + height: 44, + borderRadius: 50, + backgroundColor: '#000000', + alignItems: 'center', + justifyContent: 'center', + }, + updateButton: { + height: 56, + backgroundColor: '#323532', + borderRadius: 8, + borderCurve: 'continuous', + borderWidth: 0.5, + borderColor: '#D6DAE0', + alignItems: 'center', + justifyContent: 'center', + }, + updateButtonText: { + textAlign: 'center', + color: '#FFFFFF', + }, + cancelButton: { + height: 56, + backgroundColor: '#FFFFFF', + borderRadius: 8, + borderCurve: 'continuous', + borderWidth: 0.5, + borderColor: '#D6DAE0', + alignItems: 'center', + justifyContent: 'center', + }, + cancelButtonText: { + color: '#021730', + textAlign: 'center', + fontWeight: '600', + }, +}); diff --git a/assets/svg/apple.svg b/assets/svg/apple.svg new file mode 100644 index 0000000..e470988 --- /dev/null +++ b/assets/svg/apple.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/google.svg b/assets/svg/google.svg new file mode 100644 index 0000000..fa1c250 --- /dev/null +++ b/assets/svg/google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/svg/kakao.svg b/assets/svg/kakao.svg new file mode 100644 index 0000000..40b1fe0 --- /dev/null +++ b/assets/svg/kakao.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/svg/naver.svg b/assets/svg/naver.svg new file mode 100644 index 0000000..3a00218 --- /dev/null +++ b/assets/svg/naver.svg @@ -0,0 +1,3 @@ + + + diff --git a/metro.config.js b/metro.config.js new file mode 100644 index 0000000..36af4e3 --- /dev/null +++ b/metro.config.js @@ -0,0 +1,19 @@ +const { getDefaultConfig } = require('expo/metro-config'); + +module.exports = (() => { + const config = getDefaultConfig(__dirname); + + const { transformer, resolver } = config; + + config.transformer = { + ...transformer, + babelTransformerPath: require.resolve('react-native-svg-transformer/expo'), + }; + config.resolver = { + ...resolver, + assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'), + sourceExts: [...resolver.sourceExts, 'svg'], + }; + + return config; +})(); diff --git a/package-lock.json b/package-lock.json index 584115c..bff1015 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,7 @@ "@logicwind/react-native-exit-app": "^0.2.0", "@react-native-async-storage/async-storage": "^2.2.0", "@react-native-cookies/cookies": "^6.2.1", - "@react-navigation/bottom-tabs": "^7.2.0", - "@react-navigation/native": "^7.0.14", - "@react-navigation/stack": "^7.1.2", + "@react-native-google-signin/google-signin": "^16.1.1", "expo": "^54.0.0", "expo-apple-authentication": "~8.0.8", "expo-application": "~7.0.8", @@ -4340,6 +4338,22 @@ "react-native": ">= 0.60.2" } }, + "node_modules/@react-native-google-signin/google-signin": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@react-native-google-signin/google-signin/-/google-signin-16.1.1.tgz", + "integrity": "sha512-lcHBnZ7uvCJiWtGooKOklo/4okqszWvJ0BatW1UaIe+ynmpVpp1lyJkvv1Mj08d39k4soaWuhZVNKjD/RFL34Q==", + "license": "MIT", + "peerDependencies": { + "expo": ">=52.0.40", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, "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", @@ -4691,25 +4705,6 @@ "nanoid": "^3.3.11" } }, - "node_modules/@react-navigation/stack": { - "version": "7.6.16", - "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-7.6.16.tgz", - "integrity": "sha512-kYmogwU2jpxmjIrGO2P9PJCwgBHiju7OdItRkhFEHHppwU4jJQx/ViJtJ9ib3G4pqpurFdmqn1YsVB4bf7Zmxw==", - "license": "MIT", - "dependencies": { - "@react-navigation/elements": "^2.9.5", - "color": "^4.2.3", - "use-latest-callback": "^0.2.4" - }, - "peerDependencies": { - "@react-navigation/native": "^7.1.28", - "react": ">= 18.2.0", - "react-native": "*", - "react-native-gesture-handler": ">= 2.0.0", - "react-native-safe-area-context": ">= 4.0.0", - "react-native-screens": ">= 4.0.0" - } - }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", diff --git a/package.json b/package.json index 49789d7..7c8f9ef 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@logicwind/react-native-exit-app": "^0.2.0", "@react-native-async-storage/async-storage": "^2.2.0", "@react-native-cookies/cookies": "^6.2.1", + "@react-native-google-signin/google-signin": "^16.1.1", "expo": "^54.0.0", "expo-apple-authentication": "~8.0.8", "expo-application": "~7.0.8", @@ -62,6 +63,7 @@ "eslint-config-expo": "~10.0.0", "jest": "^29.2.1", "jest-expo": "~54.0.17", + "react-native-svg-transformer": "^1.5.3", "react-test-renderer": "^19.1.0", "typescript": "^5.3.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9253914..e4ef30d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@react-native-cookies/cookies': specifier: ^6.2.1 version: 6.2.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + '@react-native-google-signin/google-signin': + specifier: ^16.1.1 + version: 16.1.1(expo@54.0.33)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo: specifier: ^54.0.0 version: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@5.0.4(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)))(expo-router@6.0.23)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -150,6 +153,9 @@ importers: jest-expo: specifier: ~54.0.17 version: 54.0.17(@babel/core@7.29.0)(expo@54.0.33)(jest@29.7.0(@types/node@25.2.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-svg-transformer: + specifier: ^1.5.3 + version: 1.5.3(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3) react-test-renderer: specifier: ^19.1.0 version: 19.2.4(react@19.1.0) @@ -1228,6 +1234,16 @@ packages: peerDependencies: react-native: '>= 0.60.2' + '@react-native-google-signin/google-signin@16.1.1': + resolution: {integrity: sha512-lcHBnZ7uvCJiWtGooKOklo/4okqszWvJ0BatW1UaIe+ynmpVpp1lyJkvv1Mj08d39k4soaWuhZVNKjD/RFL34Q==} + peerDependencies: + expo: '>=52.0.40' + react: '*' + react-native: '*' + peerDependenciesMeta: + expo: + optional: true + '@react-native/assets-registry@0.81.5': resolution: {integrity: sha512-705B6x/5Kxm1RKRvSv0ADYWm5JOnoiQ1ufW7h8uu2E6G9Of/eE6hP/Ivw3U5jI16ERqZxiKQwk34VJbB0niX9w==} engines: {node: '>= 20.19.4'} @@ -1349,10 +1365,88 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@svgr/plugin-svgo@8.1.0': + resolution: {integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -1520,41 +1614,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -2020,6 +2122,15 @@ packages: core-js-compat@3.48.0: resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + create-jest@29.7.0: resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2046,10 +2157,22 @@ packages: resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} engines: {node: '>=8.0.0'} + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-what@6.2.2: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + cssom@0.3.8: resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} @@ -2198,6 +2321,9 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} @@ -3416,24 +3542,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -3482,6 +3612,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3509,6 +3642,12 @@ packages: mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -3669,6 +3808,9 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -3831,6 +3973,9 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-dirname@1.0.2: + resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3850,6 +3995,10 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -4018,6 +4167,12 @@ packages: react: '*' react-native: '*' + react-native-svg-transformer@1.5.3: + resolution: {integrity: sha512-M4uFg5pUt35OMgjD4rWWbwd6PmxV96W7r/gQTTa+iZA5B+jO6aURhzAZGLHSrg1Kb91cKG0Rildy9q1WJvYstg==} + peerDependencies: + react-native: '>=0.59.0' + react-native-svg: '>=12.0.0' + react-native-svg@15.12.1: resolution: {integrity: sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==} peerDependencies: @@ -4349,6 +4504,9 @@ packages: resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} engines: {node: '>=8.0.0'} + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4515,6 +4673,14 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + + svgo@3.3.2: + resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + engines: {node: '>=14.0.0'} + hasBin: true + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -6369,6 +6535,13 @@ snapshots: invariant: 2.2.4 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + '@react-native-google-signin/google-signin@16.1.1(expo@54.0.33)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + dependencies: + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + optionalDependencies: + expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@5.0.4(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)))(expo-router@6.0.23)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@react-native/assets-registry@0.81.5': {} '@react-native/babel-plugin-codegen@0.81.5(@babel/core@7.29.0)': @@ -6565,8 +6738,89 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-preset@8.1.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.0) + + '@svgr/core@8.1.0(typescript@5.9.3)': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.9.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.29.0 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@svgr/core': 8.1.0(typescript@5.9.3) + cosmiconfig: 8.3.6(typescript@5.9.3) + deepmerge: 4.3.1 + svgo: 3.3.2 + transitivePeerDependencies: + - typescript + '@tootallnate/once@2.0.0': {} + '@trysound/sax@0.2.0': {} + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -7344,6 +7598,15 @@ snapshots: dependencies: browserslist: 4.28.1 + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + create-jest@29.7.0(@types/node@25.2.0): dependencies: '@jest/types': 29.6.3 @@ -7390,8 +7653,22 @@ snapshots: mdn-data: 2.0.14 source-map: 0.6.1 + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + css-what@6.2.2: {} + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + cssom@0.3.8: {} cssom@0.5.0: {} @@ -7512,6 +7789,11 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + dotenv-expand@11.0.7: dependencies: dotenv: 16.4.7 @@ -9183,6 +9465,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + lru-cache@10.4.3: {} lru-cache@11.2.5: {} @@ -9205,6 +9491,10 @@ snapshots: mdn-data@2.0.14: {} + mdn-data@2.0.28: {} + + mdn-data@2.0.30: {} + memoize-one@5.2.1: {} memoize-one@6.0.0: {} @@ -9453,6 +9743,11 @@ snapshots: nested-error-stacks@2.0.1: {} + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -9631,6 +9926,8 @@ snapshots: parseurl@1.3.3: {} + path-dirname@1.0.2: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -9644,6 +9941,8 @@ snapshots: lru-cache: 11.2.5 minipass: 7.1.2 + path-type@4.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -9803,6 +10102,18 @@ snapshots: react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) warn-once: 0.1.1 + react-native-svg-transformer@1.5.3(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3): + dependencies: + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3) + path-dirname: 1.0.2 + react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + react-native-svg: 15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + transitivePeerDependencies: + - supports-color + - typescript + react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: css-select: 5.2.2 @@ -10209,6 +10520,11 @@ snapshots: slugify@1.6.6: {} + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + source-map-js@1.2.1: {} source-map-support@0.5.13: @@ -10386,6 +10702,18 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-parser@2.0.4: {} + + svgo@3.3.2: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.2.2 + css-tree: 2.3.1 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + symbol-tree@3.2.4: {} tar@7.5.7: diff --git a/services/auth/google.ts b/services/auth/google.ts new file mode 100644 index 0000000..3e677a8 --- /dev/null +++ b/services/auth/google.ts @@ -0,0 +1,54 @@ +import { + GoogleSignin, + isSuccessResponse, + isErrorWithCode, + statusCodes, +} from '@react-native-google-signin/google-signin'; +import { getTokens, TokenResponse } from './konect'; + +export const configureGoogleSignIn = () => { + GoogleSignin.configure({ + webClientId: process.env.EXPO_PUBLIC_WEB_CLIENT_ID, + offlineAccess: true, + iosClientId: process.env.EXPO_PUBLIC_IOS_CLIENT_ID, + }); +}; + +export const googleSignIn = async (): Promise => { + try { + const hasPlayServices = await GoogleSignin.hasPlayServices(); + if (!hasPlayServices) { + console.log('Google Play Services Not Available'); + return null; + } + + const response = await GoogleSignin.signIn(); + + if (isSuccessResponse(response)) { + if (!response.data.idToken) { + console.log('Google Sign-In Failed: No ID Token'); + return null; + } + return await getTokens('google', response.data.idToken ?? ''); + } else { + console.log('Google Sign-In Cancelled'); + return null; + } + } catch (error) { + if (isErrorWithCode(error)) { + switch (error.code) { + case statusCodes.IN_PROGRESS: + console.log('Google Sign-In In Progress'); + return null; + case statusCodes.PLAY_SERVICES_NOT_AVAILABLE: + console.log('Google Play Services Not Available'); + return null; + default: + console.error('Google Sign-In Error:', error.code); + return null; + } + } else { + return null; + } + } +}; diff --git a/services/auth/konect.ts b/services/auth/konect.ts new file mode 100644 index 0000000..d524ca5 --- /dev/null +++ b/services/auth/konect.ts @@ -0,0 +1,44 @@ +import { apiUrl } from '../../constants/constants'; +import { saveTokens } from '../tokenstore'; + +export interface TokenResponse { + redirectUrl: string; + accessToken: string | null; + refreshToken: string | null; + signupToken: string | null; +} + +export const getTokens = async ( + provider: 'google' | 'apple' | 'kakao' | 'naver', + idToken: string = '', + accessToken: string = '', + redirectUri: string = 'https://agit.gg' +): Promise => { + try { + const response = await fetch(`${apiUrl}/auth/oauth/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + provider: provider.toUpperCase(), + idToken: idToken, + accessToken: accessToken, + redirectUri: redirectUri, + }), + }); + + if (!response.ok) return null; + + const data: TokenResponse = await response.json(); + + if (data.accessToken && data.refreshToken) { + await saveTokens(data.accessToken, data.refreshToken); + } + + return data; + } catch (error) { + console.error('Error fetching tokens:', error); + return null; + } +}; diff --git a/services/tokenstore.ts b/services/tokenstore.ts new file mode 100644 index 0000000..68a6c2f --- /dev/null +++ b/services/tokenstore.ts @@ -0,0 +1,22 @@ +import * as SecureStore from 'expo-secure-store'; + +const ACCESS_TOKEN_KEY = 'access_token'; +const REFRESH_TOKEN_KEY = 'refresh_token'; + +export async function saveTokens(accessToken: string, refreshToken: string) { + await SecureStore.setItemAsync(ACCESS_TOKEN_KEY, accessToken); + await SecureStore.setItemAsync(REFRESH_TOKEN_KEY, refreshToken); +} + +export async function getAccessToken() { + return await SecureStore.getItemAsync(ACCESS_TOKEN_KEY); +} + +export async function getRefreshToken() { + return await SecureStore.getItemAsync(REFRESH_TOKEN_KEY); +} + +export async function removeTokens() { + await SecureStore.deleteItemAsync(ACCESS_TOKEN_KEY); + await SecureStore.deleteItemAsync(REFRESH_TOKEN_KEY); +}