From 4d94888bb8fa87cff9a47028e6fe729b1d5e5fe2 Mon Sep 17 00:00:00 2001 From: lusixing <32328454+lusixing@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:52:36 -0800 Subject: [PATCH] added login and register --- App.tsx | 35 +++- package-lock.json | 60 +++++-- package.json | 1 + src/context/AuthContext.tsx | 66 +++++++ src/navigation/AuthNavigator.tsx | 20 +++ src/screens/LoginScreen.tsx | 256 +++++++++++++++++++++++++++ src/screens/MeScreen.tsx | 272 +++++++++++++++-------------- src/screens/RegisterScreen.tsx | 290 +++++++++++++++++++++++++++++++ src/services/auth.service.ts | 80 +++++++++ src/types/index.ts | 31 +++- 10 files changed, 960 insertions(+), 151 deletions(-) create mode 100644 src/context/AuthContext.tsx create mode 100644 src/navigation/AuthNavigator.tsx create mode 100644 src/screens/LoginScreen.tsx create mode 100644 src/screens/RegisterScreen.tsx create mode 100644 src/services/auth.service.ts diff --git a/App.tsx b/App.tsx index 14742bb..86623ba 100644 --- a/App.tsx +++ b/App.tsx @@ -2,16 +2,33 @@ import React from 'react'; import { StatusBar } from 'expo-status-bar'; import { NavigationContainer } from '@react-navigation/native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, View, ActivityIndicator } from 'react-native'; import TabNavigator from './src/navigation/TabNavigator'; +import AuthNavigator from './src/navigation/AuthNavigator'; +import { AuthProvider, useAuth } from './src/context/AuthContext'; +import { colors } from './src/theme/colors'; + +function AppContent() { + const { user } = useAuth(); + + return ( + + + {user ? ( + + ) : ( + + )} + + ); +} export default function App() { return ( - - - - + + + ); } @@ -19,5 +36,13 @@ export default function App() { const styles = StyleSheet.create({ container: { flex: 1, + backgroundColor: '#000', + }, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: colors.sentinel.background, }, }); + diff --git a/package-lock.json b/package-lock.json index 8dd5bcb..ab285f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@expo/vector-icons": "~14.0.4", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", + "@react-navigation/native-stack": "^6.11.0", "expo": "~52.0.0", "expo-asset": "~11.0.5", "expo-constants": "~17.0.8", @@ -76,7 +77,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -480,6 +480,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" @@ -496,6 +497,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -511,6 +513,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -526,6 +529,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", @@ -543,6 +547,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/traverse": "^7.28.6" @@ -643,6 +648,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" }, @@ -763,6 +769,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -949,6 +956,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1014,6 +1022,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1060,6 +1069,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" @@ -1128,6 +1138,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1144,6 +1155,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1159,6 +1171,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1175,6 +1188,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1190,6 +1204,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5" @@ -1206,6 +1221,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1285,6 +1301,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1330,6 +1347,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1345,6 +1363,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1377,6 +1396,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", @@ -1395,6 +1415,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1427,6 +1448,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1491,6 +1513,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" @@ -1586,6 +1609,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1711,6 +1735,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1727,6 +1752,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1823,6 +1849,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1857,6 +1884,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1872,6 +1900,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1904,6 +1933,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -2022,6 +2052,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -2639,7 +2670,6 @@ "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-4.0.1.tgz", "integrity": "sha512-CRpbLvdJ1T42S+lrYa1iZp1KfDeBp4oeZOK3hdpiS5n0vR0nhD6sC1gGF0sTboCTp64tLteikz5Y3j53dvgOIw==", "license": "MIT", - "peer": true, "peerDependencies": { "react-native": "*" } @@ -3672,7 +3702,6 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.18.tgz", "integrity": "sha512-mIT9MiL/vMm4eirLcmw2h6h/Nm5FICtnYSdohq4vTLA2FF/6PNhByM7s8ffqoVfE5L0uAa6Xda1B7oddolUiGg==", "license": "MIT", - "peer": true, "dependencies": { "@react-navigation/core": "^6.4.17", "escape-string-regexp": "^4.0.0", @@ -3684,6 +3713,23 @@ "react-native": "*" } }, + "node_modules/@react-navigation/native-stack": { + "version": "6.11.0", + "resolved": "https://registry.npmmirror.com/@react-navigation/native-stack/-/native-stack-6.11.0.tgz", + "integrity": "sha512-U5EcUB9Q2NQspCFwYGGNJm0h6wBCOv7T30QjndmvlawLkNt7S7KWbpWyxS9XBHSIKF57RgWjfxuJNTgTstpXxw==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^1.3.31", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-safe-area-context": ">= 3.0.0", + "react-native-screens": ">= 3.0.0" + } + }, "node_modules/@react-navigation/routers": { "version": "6.1.9", "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", @@ -3837,7 +3883,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4440,7 +4485,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5456,6 +5500,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5580,7 +5625,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.48.tgz", "integrity": "sha512-/HR/vuo57KGEWlvF3GWaquwEAjXuA5hrOCsaLcZ3pMSA8mQ27qKd1jva4GWzpxXYedlzs/7LLP1XpZo6hXTsog==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.22.27", @@ -9053,7 +9097,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -9137,7 +9180,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.76.9.tgz", "integrity": "sha512-+LRwecWmTDco7OweGsrECIqJu0iyrREd6CTCgC/uLLYipiHvk+MH9nd6drFtCw/6Blz6eoKTcH9YTTJusNtrWg==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native/assets-registry": "0.76.9", @@ -9239,7 +9281,6 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz", "integrity": "sha512-ukk5PxcF4p3yu6qMZcmeiZgowhb5AsKRnil54YFUUAXVIS7PJcMHGGC+q44fCiBg44/1AJk5njGMez1m9H0BVQ==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -9250,7 +9291,6 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.4.0.tgz", "integrity": "sha512-c7zc7Zwjty6/pGyuuvh9gK3YBYqHPOxrhXfG1lF4gHlojQSmIx2piNbNaV+Uykj+RDTmFXK0e/hA+fucw/Qozg==", "license": "MIT", - "peer": true, "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" diff --git a/package.json b/package.json index c55d677..a3691be 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@expo/vector-icons": "~14.0.4", "@react-navigation/bottom-tabs": "^6.6.1", "@react-navigation/native": "^6.1.18", + "@react-navigation/native-stack": "^6.11.0", "expo": "~52.0.0", "expo-asset": "~11.0.5", "expo-constants": "~17.0.8", diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx new file mode 100644 index 0000000..891de9c --- /dev/null +++ b/src/context/AuthContext.tsx @@ -0,0 +1,66 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; +import { User, LoginRequest, RegisterRequest } from '../types'; +import { authService } from '../services/auth.service'; +import { Alert } from 'react-native'; + +interface AuthContextType { + user: User | null; + token: string | null; + isLoading: boolean; + signIn: (credentials: LoginRequest) => Promise; + signUp: (data: RegisterRequest) => Promise; + signOut: () => void; +} + +const AuthContext = createContext(undefined); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(null); + const [token, setToken] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const signIn = async (credentials: LoginRequest) => { + setIsLoading(true); + try { + const response = await authService.login(credentials); + setToken(response.access_token); + setUser(response.user); + } catch (error) { + throw error; + } finally { + setIsLoading(false); + } + }; + + const signUp = async (data: RegisterRequest) => { + setIsLoading(true); + try { + await authService.register(data); + // After successful registration, sign in automatically + await signIn({ username: data.username, password: data.password }); + } catch (error) { + throw error; + } finally { + setIsLoading(false); + } + }; + + const signOut = () => { + setUser(null); + setToken(null); + }; + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +} diff --git a/src/navigation/AuthNavigator.tsx b/src/navigation/AuthNavigator.tsx new file mode 100644 index 0000000..278c85e --- /dev/null +++ b/src/navigation/AuthNavigator.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import LoginScreen from '../screens/LoginScreen'; +import RegisterScreen from '../screens/RegisterScreen'; + +const Stack = createNativeStackNavigator(); + +export default function AuthNavigator() { + return ( + + + + + ); +} diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx new file mode 100644 index 0000000..7c5e05f --- /dev/null +++ b/src/screens/LoginScreen.tsx @@ -0,0 +1,256 @@ +import React, { useState } from 'react'; +import { + View, + Text, + StyleSheet, + TextInput, + TouchableOpacity, + KeyboardAvoidingView, + Platform, + SafeAreaView, + Alert, + ActivityIndicator, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { Feather, MaterialCommunityIcons } from '@expo/vector-icons'; +import { colors, spacing, borderRadius, typography, shadows } from '../theme/colors'; +import { useAuth } from '../context/AuthContext'; + +export default function LoginScreen({ navigation }: any) { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(null); + const { signIn, isLoading } = useAuth(); + + const handleLogin = async () => { + setError(null); + if (!email || !password) { + setError('Please enter both username and password.'); + return; + } + + try { + await signIn({ username: email, password }); + } catch (err: any) { + setError('Login failed. Please check your credentials.'); + } + }; + + return ( + + + + + + + + + Welcome Aboard! + Sign in to access your sanctuaryy + {error && ( + + + {error} + + )} + + + + + + + + + + + + + + + Forgot Password? + + + + {isLoading ? ( + + ) : ( + Login + )} + + + + + OR + + + + navigation.navigate('Register')} + > + + Don't have an account? Join the Fleet + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + gradient: { + flex: 1, + }, + safeArea: { + flex: 1, + }, + content: { + flex: 1, + paddingHorizontal: spacing.lg, + justifyContent: 'center', + }, + header: { + alignItems: 'center', + marginBottom: spacing.xxl, + }, + logoContainer: { + width: 80, + height: 80, + borderRadius: borderRadius.full, + backgroundColor: 'rgba(255, 255, 255, 0.1)', + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.lg, + borderWidth: 1, + borderColor: 'rgba(184, 224, 229, 0.2)', + }, + title: { + fontSize: typography.fontSize.xxl, + fontWeight: 'bold', + color: colors.sentinel.text, + marginBottom: spacing.xs, + letterSpacing: 0.5, + }, + subtitle: { + fontSize: typography.fontSize.base, + color: colors.sentinel.textSecondary, + marginBottom: spacing.md, + }, + errorContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(255, 68, 68, 0.1)', + padding: spacing.sm, + borderRadius: borderRadius.md, + marginTop: spacing.sm, + borderWidth: 1, + borderColor: 'rgba(255, 68, 68, 0.2)', + }, + errorText: { + color: colors.sentinel.statusCritical, + fontSize: typography.fontSize.sm, + marginLeft: spacing.xs, + fontWeight: '500', + }, + form: { + width: '100%', + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.05)', + borderRadius: borderRadius.lg, + borderWidth: 1, + borderColor: 'rgba(184, 224, 229, 0.1)', + marginBottom: spacing.base, + height: 56, + paddingHorizontal: spacing.base, + }, + inputIcon: { + marginRight: spacing.md, + }, + input: { + flex: 1, + color: colors.sentinel.text, + fontSize: typography.fontSize.base, + }, + forgotButton: { + alignSelf: 'flex-end', + marginBottom: spacing.lg, + }, + forgotText: { + color: colors.sentinel.primary, + fontSize: typography.fontSize.sm, + }, + loginButton: { + backgroundColor: colors.nautical.teal, + height: 56, + borderRadius: borderRadius.lg, + alignItems: 'center', + justifyContent: 'center', + ...shadows.glow, + }, + loginButtonText: { + color: colors.white, + fontSize: typography.fontSize.md, + fontWeight: 'bold', + letterSpacing: 0.5, + }, + divider: { + flexDirection: 'row', + alignItems: 'center', + marginVertical: spacing.xl, + }, + dividerLine: { + flex: 1, + height: 1, + backgroundColor: 'rgba(184, 224, 229, 0.1)', + }, + dividerText: { + color: colors.sentinel.textSecondary, + paddingHorizontal: spacing.md, + fontSize: typography.fontSize.sm, + }, + registerLink: { + alignItems: 'center', + }, + registerText: { + color: colors.sentinel.textSecondary, + fontSize: typography.fontSize.base, + }, + registerHighlight: { + color: colors.sentinel.primary, + fontWeight: 'bold', + }, +}); diff --git a/src/screens/MeScreen.tsx b/src/screens/MeScreen.tsx index 7389d1c..e9446f5 100644 --- a/src/screens/MeScreen.tsx +++ b/src/screens/MeScreen.tsx @@ -13,6 +13,7 @@ import { import { LinearGradient } from 'expo-linear-gradient'; import { Ionicons, Feather, MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons'; import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors'; +import { useAuth } from '../context/AuthContext'; // Sentinel Protocol Status const protocolStatus = { @@ -179,21 +180,22 @@ export default function MeScreen() { const [triggerGraceDays, setTriggerGraceDays] = useState(15); const [triggerSource, setTriggerSource] = useState<'dual' | 'subscription' | 'activity'>('dual'); const [triggerKillSwitch, setTriggerKillSwitch] = useState(true); + const { user, signOut } = useAuth(); const handleOpenLink = (url: string) => { - Linking.openURL(url).catch(() => {}); + Linking.openURL(url).catch(() => { }); }; const handleAbandonIsland = () => { Alert.alert( - 'Abandon Island', - 'Are you sure you want to delete your account? This action is irreversible. All your data, including vault contents, will be permanently destroyed.', + 'Sign Out', + 'Are you sure you want to sign out?', [ { text: 'Cancel', style: 'cancel' }, - { - text: 'Delete Account', + { + text: 'Sign Out', style: 'destructive', - onPress: () => Alert.alert('Account Deletion', 'Please contact support to proceed with account deletion.') + onPress: signOut }, ] ); @@ -206,7 +208,7 @@ export default function MeScreen() { style={styles.gradient} > - - Captain + {user?.username || 'Captain'} MASTER OF THE SANCTUM - Pro Member + {user?.tier || 'Pro Member'} @@ -280,8 +282,8 @@ export default function MeScreen() { THE CAPTAIN'S PROTOCOLS {captainProtocols.map((item, index) => ( - - {/* Abandon Island Button */} - - ABANDON ISLAND + SIGN OUT {/* Settings Menu */} SETTINGS {settingsMenu.map((item, index) => ( - ABOUT {protocolExplainers.slice(0, 2).map((item) => ( - setSelectedExplainer(item)} @@ -407,7 +409,7 @@ export default function MeScreen() { {selectedExplainer.title} - @@ -443,11 +445,11 @@ export default function MeScreen() { - - - 12 words split into two sealed shards - - + + + 12 words split into two sealed shards + + @@ -661,126 +663,126 @@ export default function MeScreen() { activeOpacity={1} onPress={() => setShowTideModal(false)} > - {}}> + { }}> - - - - - Tide Notifications - - - - ALERT LEVEL - - {[ - { key: 'low', label: 'Low Tide' }, - { key: 'high', label: 'High Tide' }, - { key: 'red', label: 'Red Tide' }, - ].map((item) => { - const isActive = tideLevel === item.key; - return ( - setTideLevel(item.key as typeof tideLevel)} - activeOpacity={0.85} - > - - {item.label} - - - ); - })} + + + + Tide Notifications - - - CHANNELS - - {[ - { key: 'push', label: 'App Push', icon: 'notifications' }, - { key: 'email', label: 'Email', icon: 'mail' }, - { key: 'sms', label: 'SMS', icon: 'chatbubble' }, - { key: 'emergency', label: 'Emergency', icon: 'alert' }, - ].map((item) => { - const isActive = tideChannels[item.key as keyof typeof tideChannels]; - return ( - - setTideChannels((prev) => ({ - ...prev, - [item.key]: !prev[item.key as keyof typeof prev], - })) - } - activeOpacity={0.85} - > - - - {item.label} - - - ); - })} + + + ALERT LEVEL + + {[ + { key: 'low', label: 'Low Tide' }, + { key: 'high', label: 'High Tide' }, + { key: 'red', label: 'Red Tide' }, + ].map((item) => { + const isActive = tideLevel === item.key; + return ( + setTideLevel(item.key as typeof tideLevel)} + activeOpacity={0.85} + > + + {item.label} + + + ); + })} + - - - CADENCE - - {[ - { key: 'daily', label: 'Daily' }, - { key: 'weekly', label: 'Weekly' }, - { key: 'monthly', label: 'Monthly' }, - ].map((item) => { - const isActive = tideCadence === item.key; - return ( - setTideCadence(item.key as typeof tideCadence)} - activeOpacity={0.85} - > - - {item.label} - - - ); - })} + + CHANNELS + + {[ + { key: 'push', label: 'App Push', icon: 'notifications' }, + { key: 'email', label: 'Email', icon: 'mail' }, + { key: 'sms', label: 'SMS', icon: 'chatbubble' }, + { key: 'emergency', label: 'Emergency', icon: 'alert' }, + ].map((item) => { + const isActive = tideChannels[item.key as keyof typeof tideChannels]; + return ( + + setTideChannels((prev) => ({ + ...prev, + [item.key]: !prev[item.key as keyof typeof prev], + })) + } + activeOpacity={0.85} + > + + + {item.label} + + + ); + })} + - - - - - Last confirmation: 3 days ago · Next reminder in 4 days. - + + CADENCE + + {[ + { key: 'daily', label: 'Daily' }, + { key: 'weekly', label: 'Weekly' }, + { key: 'monthly', label: 'Monthly' }, + ].map((item) => { + const isActive = tideCadence === item.key; + return ( + setTideCadence(item.key as typeof tideCadence)} + activeOpacity={0.85} + > + + {item.label} + + + ); + })} + + + + + + + Last confirmation: 3 days ago · Next reminder in 4 days. + + + + + setShowTideModal(false)} + > + + Save + + setShowTideModal(false)} + > + + Close + - - - setShowTideModal(false)} - > - - Save - - setShowTideModal(false)} - > - - Close - - diff --git a/src/screens/RegisterScreen.tsx b/src/screens/RegisterScreen.tsx new file mode 100644 index 0000000..15a3370 --- /dev/null +++ b/src/screens/RegisterScreen.tsx @@ -0,0 +1,290 @@ +import React, { useState } from 'react'; +import { + View, + Text, + StyleSheet, + TextInput, + TouchableOpacity, + KeyboardAvoidingView, + Platform, + SafeAreaView, + ScrollView, + Alert, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { Feather, MaterialCommunityIcons } from '@expo/vector-icons'; +import { colors, spacing, borderRadius, typography, shadows } from '../theme/colors'; +import { useAuth } from '../context/AuthContext'; +import { ActivityIndicator } from 'react-native'; + +export default function RegisterScreen({ navigation }: any) { + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [error, setError] = useState(null); + const { signUp, isLoading } = useAuth(); + + const handleRegister = async () => { + setError(null); + if (!name || !email || !password || !confirmPassword) { + setError('Please fill in all fields.'); + return; + } + + if (password !== confirmPassword) { + setError('Passwords do not match.'); + return; + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + setError('Please enter a valid email address.'); + return; + } + + try { + await signUp({ username: name, email, password }); + } catch (err: any) { + setError(err.message || 'Registration failed. Please try again.'); + } + }; + + return ( + + + + + + navigation.goBack()} + > + + + + + + + + Join the Fleet + Begin your journey with Sentinel + {error && ( + + + {error} + + )} + + + + + + + + + + + + + + + + + + + + + + + + + {isLoading ? ( + + ) : ( + Create Account + )} + + + navigation.navigate('Login')} + > + + Already have an account? Login + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + gradient: { + flex: 1, + }, + safeArea: { + flex: 1, + }, + content: { + flex: 1, + }, + scrollContent: { + flexGrow: 1, + paddingHorizontal: spacing.lg, + paddingBottom: spacing.xl, + }, + backButton: { + marginTop: spacing.md, + marginBottom: spacing.lg, + width: 40, + height: 40, + borderRadius: borderRadius.full, + backgroundColor: 'rgba(255, 255, 255, 0.05)', + alignItems: 'center', + justifyContent: 'center', + }, + header: { + alignItems: 'center', + marginBottom: spacing.xl, + }, + logoContainer: { + width: 80, + height: 80, + borderRadius: borderRadius.full, + backgroundColor: 'rgba(255, 255, 255, 0.1)', + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.lg, + borderWidth: 1, + borderColor: 'rgba(184, 224, 229, 0.2)', + }, + title: { + fontSize: typography.fontSize.xxl, + fontWeight: 'bold', + color: colors.sentinel.text, + marginBottom: spacing.xs, + letterSpacing: 0.5, + }, + subtitle: { + fontSize: typography.fontSize.base, + color: colors.sentinel.textSecondary, + }, + form: { + width: '100%', + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.05)', + borderRadius: borderRadius.lg, + borderWidth: 1, + borderColor: 'rgba(184, 224, 229, 0.1)', + marginBottom: spacing.base, + height: 56, + paddingHorizontal: spacing.base, + }, + inputIcon: { + marginRight: spacing.md, + }, + input: { + flex: 1, + color: colors.sentinel.text, + fontSize: typography.fontSize.base, + }, + registerButton: { + backgroundColor: colors.nautical.seafoam, + height: 56, + borderRadius: borderRadius.lg, + alignItems: 'center', + justifyContent: 'center', + marginTop: spacing.md, + ...shadows.glow, + }, + registerButtonText: { + color: colors.white, + fontSize: typography.fontSize.md, + fontWeight: 'bold', + letterSpacing: 0.5, + }, + loginLink: { + alignItems: 'center', + marginTop: spacing.xl, + }, + loginLinkText: { + color: colors.sentinel.textSecondary, + fontSize: typography.fontSize.base, + }, + highlight: { + color: colors.nautical.seafoam, + fontWeight: 'bold', + }, + disabledButton: { + opacity: 0.7, + }, + errorContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(255, 68, 68, 0.1)', + padding: spacing.sm, + borderRadius: borderRadius.md, + marginTop: spacing.md, + borderWidth: 1, + borderColor: 'rgba(255, 68, 68, 0.2)', + }, + errorText: { + color: colors.sentinel.statusCritical, + fontSize: typography.fontSize.sm, + marginLeft: spacing.xs, + fontWeight: '500', + }, +}); diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts new file mode 100644 index 0000000..a1f221d --- /dev/null +++ b/src/services/auth.service.ts @@ -0,0 +1,80 @@ +import { LoginRequest, LoginResponse, RegisterRequest, User } from '../types'; + +const PLATFORM_URL = 'http://192.168.56.103:8000'; +const no_backend_mode = true; + +const MOCK_USER: User = { + id: 999, + username: 'MockCaptain', + public_key: 'mock_public_key', + is_admin: true, + guale: false, + tier: 'premium', + tier_expires_at: '2026-12-31T23:59:59Z', + last_active_at: new Date().toISOString(), +}; + +export const authService = { + async login(credentials: LoginRequest): Promise { + if (no_backend_mode) { + console.log('No-Backend Mode: Simulating login...'); + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + access_token: 'mock_access_token', + token_type: 'bearer', + user: { ...MOCK_USER, username: credentials.username }, + }); + }, 200); + }); + } + try { + const response = await fetch(`${PLATFORM_URL}/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(credentials), + }); + + if (!response.ok) { + throw new Error('Login failed'); + } + + return await response.json(); + } catch (error) { + console.error('Login error:', error); + throw error; + } + }, + + async register(data: RegisterRequest): Promise { + if (no_backend_mode) { + console.log('No-Backend Mode: Simulating registration...'); + return new Promise((resolve) => { + setTimeout(() => { + resolve({ ...MOCK_USER, username: data.username }); + }, 200); + }); + } + try { + const response = await fetch(`${PLATFORM_URL}/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.detail?.[0]?.msg || 'Registration failed'); + } + + return await response.json(); + } catch (error) { + console.error('Registration error:', error); + throw error; + } + }, +}; diff --git a/src/types/index.ts b/src/types/index.ts index 4de19da..5d46372 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -13,7 +13,7 @@ export interface FlowRecord { } // Vault Types -export type VaultAssetType = +export type VaultAssetType = | 'game_account' | 'private_key' | 'document' @@ -72,3 +72,32 @@ export interface ProtocolInfo { version: string; lastUpdated: Date; } + +// Auth Types +export interface User { + id: number; + username: string; + public_key: string; + is_admin: boolean; + guale: boolean; + tier: string; + tier_expires_at: string; + last_active_at: string; +} + +export interface LoginRequest { + username: string; + password: string; +} + +export interface RegisterRequest { + username: string; + password: string; + email: string; +} + +export interface LoginResponse { + access_token: string; + token_type: string; + user: User; +}