diff --git a/README.md b/README.md
index 8e24ad4..0ce1149 100644
--- a/README.md
+++ b/README.md
@@ -147,12 +147,34 @@ Before starting, ensure you have:
```
react-native-crud/
+├── .expo/ # Expo cache and config
+├── .github/ # GitHub Actions & workflows
+├── assets/ # Images and fonts
+├── drizzle/ # Generated SQL migration files
+├── i18n/ # Internationalization config
+├── node_modules/ # Dependencies
├── src/
│ ├── app/ # Expo Router routes
-│ │ ├── (auth)/ # Public auth routes
-│ │ │ ├── +html.tsx
-│ │ │ ├── +not-found.tsx
-│ │ │ └── _layout.tsx
+│ │ ├── (auth)/ # Public auth routes (unauthenticated)
+│ │ │ ├── _layout.tsx # Auth stack layout
+│ │ │ ├── forgot-password.tsx # Forgot password screen
+│ │ │ ├── reset-password.tsx # Reset password screen
+│ │ │ ├── signin.tsx # Sign in screen
+│ │ │ └── signup.tsx # Sign up screen
+│ │ ├── (main)/ # Protected main routes (authenticated)
+│ │ │ └── (tabs)/ # Bottom tab navigator
+│ │ │ ├── about/ # About tab
+│ │ │ │ ├── _layout.tsx
+│ │ │ │ └── index.tsx
+│ │ │ ├── home/ # Home tab
+│ │ │ │ ├── _layout.tsx
+│ │ │ │ └── index.tsx
+│ │ │ └── settings/ # Settings tab
+│ │ │ ├── _layout.tsx
+│ │ │ └── index.tsx
+│ │ ├── +html.tsx # Custom HTML shell (web)
+│ │ ├── +not-found.tsx # 404 screen
+│ │ └── _layout.tsx # Root layout
│ └── shared/
│ ├── auth/ # Auth context & logic
│ │ ├── context.ts
@@ -164,6 +186,7 @@ react-native-crud/
│ │ │ └── index.tsx
│ │ └── ui/ # Reusable UI components
│ │ ├── forgot-password-form.tsx
+│ │ ├── header-avatar.tsx
│ │ ├── reset-password-form.tsx
│ │ ├── sign-in-form.tsx
│ │ ├── sign-up-form.tsx
@@ -192,7 +215,6 @@ react-native-crud/
│ ├── icon.ts
│ └── locale.ts
├── tests/ # Test files
-├── assets/ # Images and fonts
├── global.css # Global styles
├── app.json # Expo configuration
├── drizzle.config.ts # Drizzle Kit configuration
@@ -226,6 +248,14 @@ With the development server running (`bun run dev`), press `Shift + M` in the te
> **Note:** This plugin is available during native development only (iOS/Android). It does not work on Web.
+
+
+
+
+
+
+
+
---
diff --git a/bun.lock b/bun.lock
index 9e5ac8f..cca7f0c 100644
--- a/bun.lock
+++ b/bun.lock
@@ -10,6 +10,7 @@
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/native": "^7.1.28",
"@rn-primitives/avatar": "^1.2.0",
+ "@rn-primitives/dropdown-menu": "^1.2.0",
"@rn-primitives/label": "^1.2.0",
"@rn-primitives/popover": "^1.2.0",
"@rn-primitives/portal": "~1.3.0",
@@ -449,6 +450,8 @@
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
+ "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="],
+
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
@@ -457,6 +460,8 @@
"@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="],
+ "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="],
+
"@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="],
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
@@ -465,7 +470,7 @@
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
- "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
+ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
@@ -531,6 +536,8 @@
"@rn-primitives/avatar": ["@rn-primitives/avatar@1.2.0", "", { "dependencies": { "@rn-primitives/hooks": "1.3.0", "@rn-primitives/slot": "1.2.0", "@rn-primitives/types": "1.2.0" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-ic029KaJRADdjmjPzpaSaZ9QrtgGF8DnAA7TcQ/gYqUfLXjkbfzsjARKv7NtEoJLjWAcjIAK6R8JkcbMfPtYig=="],
+ "@rn-primitives/dropdown-menu": ["@rn-primitives/dropdown-menu@1.2.0", "", { "dependencies": { "@radix-ui/react-dropdown-menu": "^2.1.15", "@rn-primitives/hooks": "1.3.0", "@rn-primitives/slot": "1.2.0", "@rn-primitives/types": "1.2.0", "@rn-primitives/utils": "1.2.0" }, "peerDependencies": { "@rn-primitives/portal": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-TJDDr8VQfw9CRZ7xZ6kBYLVMqL1xFVC5ZZ4sfRmWP6PCT0lNks4XqGuTFLeVVlNLPSmzt9GKC2DZqzDXui8/NQ=="],
+
"@rn-primitives/hooks": ["@rn-primitives/hooks@1.3.0", "", { "dependencies": { "@rn-primitives/types": "1.2.0" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-BR97reSu7uVDpyMeQdRJHT0w8KdS6jdYnOL6xQtqS2q3H6N7vXBlX4LFERqJZphD+aziJFIAJ3HJF1vtt6XlpQ=="],
"@rn-primitives/label": ["@rn-primitives/label@1.2.0", "", { "dependencies": { "@radix-ui/react-label": "^2.1.7", "@rn-primitives/slot": "1.2.0", "@rn-primitives/types": "1.2.0" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-eThBr6vn2jS81ZS4JNcg0+02TkEircH4bZmjF4IZUDl4XRpevwK95NyOkbfhGYmpVbAuisAVxDmvNOQ4OVjfug=="],
@@ -547,6 +554,8 @@
"@rn-primitives/types": ["@rn-primitives/types@1.2.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-b+6zKgdKVqAfaFPSfhwlQL0dnPQXPpW890m3eguC0VDI1eOsoEvUfVb6lmgH4bum9MmI0xymq4tOUI/fsKLoCQ=="],
+ "@rn-primitives/utils": ["@rn-primitives/utils@1.2.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-vLXV5NuxIHDeb4Bw57FzdUh89/g8gz6GERm8TsbJaSUPsDXfnC/ffeYiZJb0LxNteKE3Nr8na4Jy2n26tFil7w=="],
+
"@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="],
"@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
@@ -1557,31 +1566,19 @@
"@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
- "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
-
- "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
-
"@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
- "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
-
"@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
- "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+ "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
- "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
-
- "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+ "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
- "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
-
- "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+ "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
- "@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
-
- "@radix-ui/react-tabs/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
+ "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
"@react-native/community-cli-plugin/metro": ["metro@0.83.5", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "accepts": "^2.0.0", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.33.3", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.5", "metro-cache": "0.83.5", "metro-cache-key": "0.83.5", "metro-config": "0.83.5", "metro-core": "0.83.5", "metro-file-map": "0.83.5", "metro-resolver": "0.83.5", "metro-runtime": "0.83.5", "metro-source-map": "0.83.5", "metro-symbolicate": "0.83.5", "metro-transform-plugins": "0.83.5", "metro-transform-worker": "0.83.5", "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ=="],
@@ -1753,20 +1750,6 @@
"@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
- "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
-
- "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
-
- "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
-
- "@radix-ui/react-popper/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
-
- "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
-
- "@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
-
- "@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
-
"@react-native/community-cli-plugin/metro/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"@react-native/community-cli-plugin/metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
diff --git a/drizzle/0000_wet_zemo.sql b/drizzle/0000_calm_captain_britain.sql
similarity index 86%
rename from drizzle/0000_wet_zemo.sql
rename to drizzle/0000_calm_captain_britain.sql
index 29ef2d4..dd48898 100644
--- a/drizzle/0000_wet_zemo.sql
+++ b/drizzle/0000_calm_captain_britain.sql
@@ -22,7 +22,8 @@ CREATE TABLE `users` (
`id` integer PRIMARY KEY NOT NULL,
`email` text NOT NULL,
`password_hash` text NOT NULL,
+ `avatar_url` text DEFAULT '' NOT NULL,
`created_at` integer NOT NULL
);
--> statement-breakpoint
-CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
\ No newline at end of file
+CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json
index 7d7b78c..ec310d8 100644
--- a/drizzle/meta/0000_snapshot.json
+++ b/drizzle/meta/0000_snapshot.json
@@ -1,7 +1,7 @@
{
"version": "6",
"dialect": "sqlite",
- "id": "a80d7cec-989f-4bd1-97a4-b7d30ff894c8",
+ "id": "a6d42745-9c63-42b7-aac0-76d74e9fff7a",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"items": {
@@ -135,6 +135,14 @@
"notNull": true,
"autoincrement": false
},
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
"created_at": {
"name": "created_at",
"type": "integer",
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 69f7322..574d4a5 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "6",
- "when": 1772274776298,
- "tag": "0000_wet_zemo",
+ "when": 1772545231738,
+ "tag": "0000_calm_captain_britain",
"breakpoints": true
}
]
diff --git a/drizzle/migrations.js b/drizzle/migrations.js
index b9853c0..ac28fda 100644
--- a/drizzle/migrations.js
+++ b/drizzle/migrations.js
@@ -1,6 +1,6 @@
// This file is required for Expo/React Native SQLite migrations - https://orm.drizzle.team/quick-sqlite/expo
-import m0000 from "./0000_wet_zemo.sql";
+import m0000 from "./0000_calm_captain_britain.sql";
import journal from "./meta/_journal.json";
export default {
diff --git a/package.json b/package.json
index ff9e788..3610ef3 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/native": "^7.1.28",
"@rn-primitives/avatar": "^1.2.0",
+ "@rn-primitives/dropdown-menu": "^1.2.0",
"@rn-primitives/label": "^1.2.0",
"@rn-primitives/popover": "^1.2.0",
"@rn-primitives/portal": "~1.3.0",
diff --git a/src/app/(auth)/_layout.tsx b/src/app/(auth)/_layout.tsx
index fe7d0c5..50750f0 100644
--- a/src/app/(auth)/_layout.tsx
+++ b/src/app/(auth)/_layout.tsx
@@ -2,9 +2,9 @@ import { Redirect, Stack } from "expo-router";
import { useAuth } from "@/shared/hooks/useAuth";
export default function AuthLayout() {
- const { isAuthenticated, user } = useAuth();
- if (isAuthenticated) return null;
- if (user) return ;
+ const { isAuthenticated } = useAuth();
+
+ if (isAuthenticated) return ;
return ;
}
diff --git a/src/app/(app)/_layout.tsx b/src/app/(main)/(tabs)/_layout.tsx
similarity index 95%
rename from src/app/(app)/_layout.tsx
rename to src/app/(main)/(tabs)/_layout.tsx
index 5687a08..00b642f 100644
--- a/src/app/(app)/_layout.tsx
+++ b/src/app/(main)/(tabs)/_layout.tsx
@@ -6,7 +6,7 @@ export default function TabLayout() {
return (
-
+
{t("tabs.home")}
diff --git a/src/app/(main)/(tabs)/about/_layout.tsx b/src/app/(main)/(tabs)/about/_layout.tsx
new file mode 100644
index 0000000..daa1127
--- /dev/null
+++ b/src/app/(main)/(tabs)/about/_layout.tsx
@@ -0,0 +1,28 @@
+import { Stack } from "expo-router";
+import { useTranslation } from "react-i18next";
+import { Platform } from "react-native";
+import { HeaderAvatar } from "@/shared/components/ui/header-avatar";
+
+export default function AboutLayout() {
+ const { t } = useTranslation();
+
+ return (
+
+ ,
+ }}
+ />
+
+ );
+}
diff --git a/src/app/(app)/about.tsx b/src/app/(main)/(tabs)/about/index.tsx
similarity index 100%
rename from src/app/(app)/about.tsx
rename to src/app/(main)/(tabs)/about/index.tsx
diff --git a/src/app/(main)/(tabs)/home/_layout.tsx b/src/app/(main)/(tabs)/home/_layout.tsx
new file mode 100644
index 0000000..e310e3f
--- /dev/null
+++ b/src/app/(main)/(tabs)/home/_layout.tsx
@@ -0,0 +1,28 @@
+import { Stack } from "expo-router";
+import { useTranslation } from "react-i18next";
+import { Platform } from "react-native";
+import { HeaderAvatar } from "@/shared/components/ui/header-avatar";
+
+export default function HomeLayout() {
+ const { t } = useTranslation();
+
+ return (
+
+ ,
+ }}
+ />
+
+ );
+}
diff --git a/src/app/(main)/(tabs)/home/index.tsx b/src/app/(main)/(tabs)/home/index.tsx
new file mode 100644
index 0000000..f219d1a
--- /dev/null
+++ b/src/app/(main)/(tabs)/home/index.tsx
@@ -0,0 +1,20 @@
+import { useTheme } from "@react-navigation/native";
+import { useTranslation } from "react-i18next";
+import { View } from "react-native";
+import { Text } from "@/shared/components/ui/text";
+
+export default function Home() {
+ const { colors } = useTheme();
+ const { t } = useTranslation();
+
+ return (
+
+
+ {t("tabs.home")}
+
+
+ );
+}
diff --git a/src/app/(main)/(tabs)/settings/_layout.tsx b/src/app/(main)/(tabs)/settings/_layout.tsx
new file mode 100644
index 0000000..693c3fd
--- /dev/null
+++ b/src/app/(main)/(tabs)/settings/_layout.tsx
@@ -0,0 +1,28 @@
+import { Stack } from "expo-router";
+import { useTranslation } from "react-i18next";
+import { Platform } from "react-native";
+import { HeaderAvatar } from "@/shared/components/ui/header-avatar";
+
+export default function SettingsLayout() {
+ const { t } = useTranslation();
+
+ return (
+
+ ,
+ }}
+ />
+
+ );
+}
diff --git a/src/app/(app)/settings.tsx b/src/app/(main)/(tabs)/settings/index.tsx
similarity index 100%
rename from src/app/(app)/settings.tsx
rename to src/app/(main)/(tabs)/settings/index.tsx
diff --git a/src/app/(main)/_layout.tsx b/src/app/(main)/_layout.tsx
new file mode 100644
index 0000000..5edda9b
--- /dev/null
+++ b/src/app/(main)/_layout.tsx
@@ -0,0 +1,9 @@
+import { Stack } from "expo-router";
+
+export default function AppLayout() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/(app)/index.tsx b/src/app/(main)/index.tsx
similarity index 53%
rename from src/app/(app)/index.tsx
rename to src/app/(main)/index.tsx
index 5b467ed..22e3087 100644
--- a/src/app/(app)/index.tsx
+++ b/src/app/(main)/index.tsx
@@ -2,9 +2,13 @@ import { Redirect } from "expo-router";
import { useAuth } from "@/shared/hooks/useAuth";
export default function Index() {
- const { user, isAuthenticated } = useAuth();
+ const { isAuthenticated } = useAuth();
if (isAuthenticated) return null;
- return ;
+ return (
+
+ );
}
diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx
index ef46efc..80dc702 100644
--- a/src/app/_layout.tsx
+++ b/src/app/_layout.tsx
@@ -28,7 +28,7 @@ export default function RootLayout() {
-
+
diff --git a/src/shared/components/sign-in-form.tsx b/src/shared/components/sign-in-form.tsx
index 36db6d0..1c093d6 100644
--- a/src/shared/components/sign-in-form.tsx
+++ b/src/shared/components/sign-in-form.tsx
@@ -37,7 +37,7 @@ export function SignInForm() {
async function onSubmit({ email, password }: SignInSchema) {
try {
await signIn(email, password);
- router.replace("/(app)");
+ router.replace("/(main)/(tabs)/home");
} catch (err) {
setError("root", {
message: err instanceof Error ? err.message : "Something went wrong",
diff --git a/src/shared/components/sign-up-form.tsx b/src/shared/components/sign-up-form.tsx
index ac381e9..7a07354 100644
--- a/src/shared/components/sign-up-form.tsx
+++ b/src/shared/components/sign-up-form.tsx
@@ -37,7 +37,7 @@ export function SignUpForm() {
async function onSubmit({ email, password }: SignUpSchema) {
try {
await signUp(email, password);
- router.replace("/(app)");
+ router.replace("/(auth)/signin");
} catch (err) {
setError("root", {
message: err instanceof Error ? err.message : "Something went wrong",
@@ -66,7 +66,7 @@ export function SignUpForm() {
render={({ field: { onChange, value } }) => (
& {
+ children?: React.ReactNode;
+ iconClassName?: string;
+ inset?: boolean;
+ }) {
+ const { open } = DropdownMenuPrimitive.useSubContext();
+ const icon =
+ Platform.OS === "web" ? ChevronRight : open ? ChevronUp : ChevronDown;
+ return (
+
+
+
+
+
+ );
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: DropdownMenuPrimitive.SubContentProps &
+ React.RefAttributes) {
+ return (
+
+
+
+ );
+}
+
+const FullWindowOverlay =
+ Platform.OS === "ios" ? RNFullWindowOverlay : React.Fragment;
+
+function DropdownMenuContent({
+ className,
+ overlayClassName,
+ overlayStyle,
+ portalHost,
+ ...props
+}: DropdownMenuPrimitive.ContentProps &
+ React.RefAttributes & {
+ overlayStyle?: StyleProp;
+ overlayClassName?: string;
+ portalHost?: string;
+ }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant,
+ ...props
+}: DropdownMenuPrimitive.ItemProps &
+ React.RefAttributes & {
+ className?: string;
+ inset?: boolean;
+ variant?: "default" | "destructive";
+ }) {
+ return (
+
+
+
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ ...props
+}: DropdownMenuPrimitive.CheckboxItemProps &
+ React.RefAttributes & {
+ children?: React.ReactNode;
+ }) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: DropdownMenuPrimitive.RadioItemProps &
+ React.RefAttributes & {
+ children?: React.ReactNode;
+ }) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: DropdownMenuPrimitive.LabelProps &
+ React.RefAttributes & {
+ className?: string;
+ inset?: boolean;
+ }) {
+ return (
+
+ );
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: DropdownMenuPrimitive.SeparatorProps &
+ React.RefAttributes) {
+ return (
+
+ );
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: TextProps & React.RefAttributes) {
+ return (
+
+ );
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuCheckboxItem,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuPortal,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+};
diff --git a/src/shared/components/ui/header-avatar.tsx b/src/shared/components/ui/header-avatar.tsx
new file mode 100644
index 0000000..f8fe503
--- /dev/null
+++ b/src/shared/components/ui/header-avatar.tsx
@@ -0,0 +1,33 @@
+import { useRouter } from "expo-router";
+import { Pressable } from "react-native";
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from "@/shared/components/ui/avatar";
+import { Text } from "@/shared/components/ui/text";
+import { useAuth } from "@/shared/hooks/useAuth";
+
+export function HeaderAvatar() {
+ const { user, signOut } = useAuth();
+ const router = useRouter();
+
+ const initials =
+ user?.email?.split("@")[0]?.slice(0, 2)?.toUpperCase() ?? "??";
+
+ async function handleSignOut() {
+ await signOut();
+ router.replace("/(auth)/signin");
+ }
+
+ return (
+
+
+
+
+ {initials}
+
+
+
+ );
+}
diff --git a/src/shared/db/schema.ts b/src/shared/db/schema.ts
index e3ed9e2..341e883 100644
--- a/src/shared/db/schema.ts
+++ b/src/shared/db/schema.ts
@@ -4,6 +4,7 @@ export const users = sqliteTable("users", {
id: integer("id").primaryKey().notNull(),
email: text("email").notNull().unique(),
passwordHash: text("password_hash").notNull(),
+ avatar_url: text("avatar_url").notNull().default(""),
createdAt: integer("created_at").notNull(),
});
diff --git a/src/shared/services/userService.ts b/src/shared/services/userService.ts
index 0595b97..faaad78 100644
--- a/src/shared/services/userService.ts
+++ b/src/shared/services/userService.ts
@@ -22,7 +22,7 @@ export async function getSessionUser(): Promise {
.limit(1);
if (!user) return null;
- return { id: user.id, email: user.email };
+ return { id: user.id, email: user.email, avatar_url: user.avatar_url };
}
export async function signUpUser(
@@ -44,7 +44,7 @@ export async function signUpUser(
const [user] = await db
.insert(users)
- .values({ email, passwordHash, createdAt: now })
+ .values({ email, passwordHash, createdAt: now, avatar_url: "" })
.returning();
await db.insert(sessions).values({
@@ -52,7 +52,7 @@ export async function signUpUser(
createdAt: now,
});
- return { id: user.id, email: user.email };
+ return { id: user.id, email: user.email, avatar_url: user.avatar_url };
}
export async function signInUser(
@@ -78,7 +78,7 @@ export async function signInUser(
createdAt: Date.now(),
});
- return { id: user.id, email: user.email };
+ return { id: user.id, email: user.email, avatar_url: user.avatar_url };
}
export async function signOutUser(userId: number): Promise {
diff --git a/src/shared/types/auth.ts b/src/shared/types/auth.ts
index 768622e..25dd2b8 100644
--- a/src/shared/types/auth.ts
+++ b/src/shared/types/auth.ts
@@ -9,4 +9,5 @@ export type AuthContextType = {
export type AuthUser = {
id: number;
email: string;
+ avatar_url: string;
};