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);
+}