tools-template-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/dist/index.js +380 -0
  4. package/dist/templates.js +44 -0
  5. package/package.json +49 -0
  6. package/supabase-expo-app/.env.example +2 -0
  7. package/supabase-expo-app/App.tsx +55 -0
  8. package/supabase-expo-app/README.md +179 -0
  9. package/supabase-expo-app/app.json +34 -0
  10. package/supabase-expo-app/assets/adaptive-icon.png +0 -0
  11. package/supabase-expo-app/assets/favicon.png +0 -0
  12. package/supabase-expo-app/assets/icon.png +0 -0
  13. package/supabase-expo-app/assets/splash-icon.png +0 -0
  14. package/supabase-expo-app/components/Account.tsx +234 -0
  15. package/supabase-expo-app/components/Auth.tsx +161 -0
  16. package/supabase-expo-app/components/Avatar.tsx +173 -0
  17. package/supabase-expo-app/index.ts +8 -0
  18. package/supabase-expo-app/lib/supabase.ts +63 -0
  19. package/supabase-expo-app/package-lock.json +8983 -0
  20. package/supabase-expo-app/package.json +32 -0
  21. package/supabase-expo-app/scripts/setup-supabase.sh +73 -0
  22. package/supabase-expo-app/supabase/schema.sql +57 -0
  23. package/supabase-expo-app/tsconfig.json +6 -0
  24. package/supabase-swiftui-app/Package.swift +32 -0
  25. package/supabase-swiftui-app/README.md +215 -0
  26. package/supabase-swiftui-app/SupabaseSwiftUIApp/AccountView.swift +181 -0
  27. package/supabase-swiftui-app/SupabaseSwiftUIApp/AuthView.swift +123 -0
  28. package/supabase-swiftui-app/SupabaseSwiftUIApp/AvatarView.swift +113 -0
  29. package/supabase-swiftui-app/SupabaseSwiftUIApp/ContentView.swift +18 -0
  30. package/supabase-swiftui-app/SupabaseSwiftUIApp/Supabase.swift +13 -0
  31. package/supabase-swiftui-app/SupabaseSwiftUIApp/SupabaseSwiftUIApp.swift +22 -0
  32. package/supabase-swiftui-app/scripts/setup-supabase.sh +67 -0
  33. package/supabase-swiftui-app/supabase/schema.sql +57 -0
  34. package/supabase-user-management/AGENTS.md +5 -0
  35. package/supabase-user-management/CLAUDE.md +1 -0
  36. package/supabase-user-management/README.md +178 -0
  37. package/supabase-user-management/app/account/account-form.tsx +174 -0
  38. package/supabase-user-management/app/account/avatar.tsx +109 -0
  39. package/supabase-user-management/app/account/page.tsx +28 -0
  40. package/supabase-user-management/app/auth/confirm/route.ts +26 -0
  41. package/supabase-user-management/app/error.tsx +14 -0
  42. package/supabase-user-management/app/favicon.ico +0 -0
  43. package/supabase-user-management/app/globals.css +130 -0
  44. package/supabase-user-management/app/layout.tsx +22 -0
  45. package/supabase-user-management/app/loading.tsx +7 -0
  46. package/supabase-user-management/app/login/actions.ts +45 -0
  47. package/supabase-user-management/app/login/page.tsx +90 -0
  48. package/supabase-user-management/app/page.tsx +16 -0
  49. package/supabase-user-management/components/ui/button.tsx +58 -0
  50. package/supabase-user-management/components.json +25 -0
  51. package/supabase-user-management/eslint.config.mjs +18 -0
  52. package/supabase-user-management/lib/supabase/client.ts +8 -0
  53. package/supabase-user-management/lib/supabase/middleware.ts +52 -0
  54. package/supabase-user-management/lib/supabase/server.ts +29 -0
  55. package/supabase-user-management/lib/utils.ts +6 -0
  56. package/supabase-user-management/next.config.ts +7 -0
  57. package/supabase-user-management/package-lock.json +9910 -0
  58. package/supabase-user-management/package.json +36 -0
  59. package/supabase-user-management/postcss.config.mjs +7 -0
  60. package/supabase-user-management/public/file.svg +1 -0
  61. package/supabase-user-management/public/globe.svg +1 -0
  62. package/supabase-user-management/public/next.svg +1 -0
  63. package/supabase-user-management/public/vercel.svg +1 -0
  64. package/supabase-user-management/public/window.svg +1 -0
  65. package/supabase-user-management/scripts/setup-supabase.sh +98 -0
  66. package/supabase-user-management/src/proxy.ts +12 -0
  67. package/supabase-user-management/supabase/schema.sql +57 -0
  68. package/supabase-user-management/tsconfig.json +34 -0
  69. package/supabase_flutter_app/.metadata +45 -0
  70. package/supabase_flutter_app/README.md +195 -0
  71. package/supabase_flutter_app/analysis_options.yaml +28 -0
  72. package/supabase_flutter_app/android/app/build.gradle.kts +44 -0
  73. package/supabase_flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  74. package/supabase_flutter_app/android/app/src/main/AndroidManifest.xml +54 -0
  75. package/supabase_flutter_app/android/app/src/main/kotlin/com/example/supabase_flutter_app/MainActivity.kt +5 -0
  76. package/supabase_flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  77. package/supabase_flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  78. package/supabase_flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  79. package/supabase_flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  80. package/supabase_flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  81. package/supabase_flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  82. package/supabase_flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  83. package/supabase_flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  84. package/supabase_flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  85. package/supabase_flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  86. package/supabase_flutter_app/android/build.gradle.kts +24 -0
  87. package/supabase_flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  88. package/supabase_flutter_app/android/gradle.properties +2 -0
  89. package/supabase_flutter_app/android/settings.gradle.kts +26 -0
  90. package/supabase_flutter_app/ios/Flutter/AppFrameworkInfo.plist +26 -0
  91. package/supabase_flutter_app/ios/Flutter/Debug.xcconfig +2 -0
  92. package/supabase_flutter_app/ios/Flutter/Release.xcconfig +2 -0
  93. package/supabase_flutter_app/ios/Podfile +43 -0
  94. package/supabase_flutter_app/ios/Runner/AppDelegate.swift +13 -0
  95. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  96. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
  97. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
  98. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
  99. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
  100. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
  101. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
  102. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
  103. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
  104. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
  105. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
  106. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
  107. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
  108. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
  109. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
  110. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
  111. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
  112. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  113. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  114. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  115. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
  116. package/supabase_flutter_app/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
  117. package/supabase_flutter_app/ios/Runner/Base.lproj/Main.storyboard +26 -0
  118. package/supabase_flutter_app/ios/Runner/Info.plist +61 -0
  119. package/supabase_flutter_app/ios/Runner/Runner-Bridging-Header.h +1 -0
  120. package/supabase_flutter_app/ios/Runner.xcodeproj/project.pbxproj +619 -0
  121. package/supabase_flutter_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  122. package/supabase_flutter_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  123. package/supabase_flutter_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  124. package/supabase_flutter_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
  125. package/supabase_flutter_app/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  126. package/supabase_flutter_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  127. package/supabase_flutter_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  128. package/supabase_flutter_app/ios/RunnerTests/RunnerTests.swift +12 -0
  129. package/supabase_flutter_app/lib/components/avatar.dart +153 -0
  130. package/supabase_flutter_app/lib/main.dart +70 -0
  131. package/supabase_flutter_app/lib/pages/account_page.dart +189 -0
  132. package/supabase_flutter_app/lib/pages/login_page.dart +150 -0
  133. package/supabase_flutter_app/linux/CMakeLists.txt +128 -0
  134. package/supabase_flutter_app/linux/flutter/CMakeLists.txt +88 -0
  135. package/supabase_flutter_app/linux/flutter/generated_plugin_registrant.cc +23 -0
  136. package/supabase_flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  137. package/supabase_flutter_app/linux/flutter/generated_plugins.cmake +26 -0
  138. package/supabase_flutter_app/linux/runner/CMakeLists.txt +26 -0
  139. package/supabase_flutter_app/linux/runner/main.cc +6 -0
  140. package/supabase_flutter_app/linux/runner/my_application.cc +148 -0
  141. package/supabase_flutter_app/linux/runner/my_application.h +21 -0
  142. package/supabase_flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  143. package/supabase_flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  144. package/supabase_flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +18 -0
  145. package/supabase_flutter_app/macos/Podfile +42 -0
  146. package/supabase_flutter_app/macos/Runner/AppDelegate.swift +13 -0
  147. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  148. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  149. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  150. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  151. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  152. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  153. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  154. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  155. package/supabase_flutter_app/macos/Runner/Base.lproj/MainMenu.xib +343 -0
  156. package/supabase_flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  157. package/supabase_flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  158. package/supabase_flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  159. package/supabase_flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  160. package/supabase_flutter_app/macos/Runner/DebugProfile.entitlements +12 -0
  161. package/supabase_flutter_app/macos/Runner/Info.plist +32 -0
  162. package/supabase_flutter_app/macos/Runner/MainFlutterWindow.swift +15 -0
  163. package/supabase_flutter_app/macos/Runner/Release.entitlements +8 -0
  164. package/supabase_flutter_app/macos/Runner.xcodeproj/project.pbxproj +705 -0
  165. package/supabase_flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  166. package/supabase_flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  167. package/supabase_flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  168. package/supabase_flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  169. package/supabase_flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  170. package/supabase_flutter_app/pubspec.lock +818 -0
  171. package/supabase_flutter_app/pubspec.yaml +26 -0
  172. package/supabase_flutter_app/scripts/setup-supabase.sh +72 -0
  173. package/supabase_flutter_app/supabase/schema.sql +57 -0
  174. package/supabase_flutter_app/test/widget_test.dart +30 -0
  175. package/supabase_flutter_app/web/favicon.png +0 -0
  176. package/supabase_flutter_app/web/icons/Icon-192.png +0 -0
  177. package/supabase_flutter_app/web/icons/Icon-512.png +0 -0
  178. package/supabase_flutter_app/web/icons/Icon-maskable-192.png +0 -0
  179. package/supabase_flutter_app/web/icons/Icon-maskable-512.png +0 -0
  180. package/supabase_flutter_app/web/index.html +38 -0
  181. package/supabase_flutter_app/web/manifest.json +35 -0
  182. package/supabase_flutter_app/windows/CMakeLists.txt +108 -0
  183. package/supabase_flutter_app/windows/flutter/CMakeLists.txt +109 -0
  184. package/supabase_flutter_app/windows/flutter/generated_plugin_registrant.cc +20 -0
  185. package/supabase_flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  186. package/supabase_flutter_app/windows/flutter/generated_plugins.cmake +26 -0
  187. package/supabase_flutter_app/windows/runner/CMakeLists.txt +40 -0
  188. package/supabase_flutter_app/windows/runner/Runner.rc +121 -0
  189. package/supabase_flutter_app/windows/runner/flutter_window.cpp +71 -0
  190. package/supabase_flutter_app/windows/runner/flutter_window.h +33 -0
  191. package/supabase_flutter_app/windows/runner/main.cpp +43 -0
  192. package/supabase_flutter_app/windows/runner/resource.h +16 -0
  193. package/supabase_flutter_app/windows/runner/resources/app_icon.ico +0 -0
  194. package/supabase_flutter_app/windows/runner/runner.exe.manifest +14 -0
  195. package/supabase_flutter_app/windows/runner/utils.cpp +65 -0
  196. package/supabase_flutter_app/windows/runner/utils.h +19 -0
  197. package/supabase_flutter_app/windows/runner/win32_window.cpp +288 -0
  198. package/supabase_flutter_app/windows/runner/win32_window.h +102 -0
@@ -0,0 +1,179 @@
1
+ # Supabase Expo User Management Template
2
+
3
+ 一个使用 Expo 和 Supabase 构建的用户管理 React Native 应用模板。
4
+
5
+ ## 功能特性
6
+
7
+ - ✅ 用户注册/登录
8
+ - ✅ 会话持久化(加密存储)
9
+ - ✅ 个人资料管理
10
+ - ✅ 头像上传
11
+ - ✅ Row Level Security (RLS)
12
+ - ✅ TypeScript
13
+ - ✅ Expo SDK 54
14
+
15
+ ## 技术栈
16
+
17
+ - **框架**: Expo (React Native)
18
+ - **数据库**: Supabase (PostgreSQL)
19
+ - **认证**: Supabase Auth
20
+ - **存储**: Supabase Storage
21
+ - **本地存储**: expo-secure-store + AsyncStorage (AES-256 加密)
22
+ - **语言**: TypeScript
23
+
24
+ ## 快速开始
25
+
26
+ ### 方法一:使用 Supabase Skills 自动化部署(推荐)
27
+
28
+ 使用 OpenClaw 的 Supabase Skills 可以一键完成所有配置!
29
+
30
+ ```bash
31
+ # 1. 运行自动化部署脚本
32
+ npm run setup-supabase
33
+
34
+ # 或者直接运行脚本
35
+ ./scripts/setup-supabase.sh
36
+ ```
37
+
38
+ 这个脚本会自动完成:
39
+ - ✅ 创建 Supabase 项目
40
+ - ✅ 部署数据库 Schema
41
+ - ✅ 配置 RLS 策略
42
+ - ✅ 创建存储桶
43
+ - ✅ 配置认证
44
+ - ✅ 生成环境变量文件
45
+
46
+ 详见 [SUPABASE_SKILLS_GUIDE.md](../SUPABASE_SKILLS_GUIDE.md) 获取完整教程。
47
+
48
+ ---
49
+
50
+ ### 方法二:手动配置
51
+
52
+ #### 1. 创建 Supabase 项目
53
+
54
+ 访问 [database.new](https://database.new) 或 [Supabase Dashboard](https://supabase.com/dashboard) 创建新项目。
55
+
56
+ #### 2. 设置数据库
57
+
58
+ 在 SQL Editor 中运行以下脚本:
59
+
60
+ ```sql
61
+ -- 复制 supabase/schema.sql 的内容
62
+ ```
63
+
64
+ 这会创建:
65
+ - `profiles` 表
66
+ - RLS 策略
67
+ - 用户触发器
68
+ - `avatars` 存储桶
69
+
70
+ #### 3. 配置环境变量
71
+
72
+ ```bash
73
+ cp .env.example .env
74
+ ```
75
+
76
+ 编辑 `.env`:
77
+
78
+ ```env
79
+ EXPO_PUBLIC_SUPABASE_URL=your_supabase_url
80
+ EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your_supabase_publishable_key
81
+ ```
82
+
83
+ 从 [API Keys 设置页面](https://supabase.com/dashboard/project/_/settings/api-keys) 获取密钥。
84
+
85
+ ### 4. 安装依赖
86
+
87
+ ```bash
88
+ npm install
89
+ ```
90
+
91
+ ### 5. 启动应用
92
+
93
+ ```bash
94
+ # 启动开发服务器
95
+ npx expo start
96
+
97
+ # iOS 模拟器
98
+ npm run ios
99
+
100
+ # Android 模拟器
101
+ npm run android
102
+ ```
103
+
104
+ ## 项目结构
105
+
106
+ ```
107
+ supabase-expo-app/
108
+ ├── components/
109
+ │ ├── Auth.tsx # 登录/注册组件
110
+ │ ├── Account.tsx # 账户管理组件
111
+ │ └── Avatar.tsx # 头像上传组件
112
+ ├── lib/
113
+ │ └── supabase.ts # Supabase 客户端(加密存储)
114
+ ├── supabase/
115
+ │ └── schema.sql # 数据库 Schema
116
+ ├── .env.example # 环境变量示例
117
+ ├── App.tsx # 主应用组件
118
+ ├── app.json # Expo 配置
119
+ └── package.json
120
+ ```
121
+
122
+ ## 数据库表结构
123
+
124
+ ### profiles 表
125
+
126
+ | 字段 | 类型 | 说明 |
127
+ |------|------|------|
128
+ | id | uuid | 主键,关联 auth.users |
129
+ | username | text | 用户名(唯一) |
130
+ | full_name | text | 姓名 |
131
+ | avatar_url | text | 头像 URL |
132
+ | website | text | 网站 |
133
+ | updated_at | timestamp | 更新时间 |
134
+
135
+ ## Row Level Security (RLS)
136
+
137
+ - **SELECT**: 所有人可见
138
+ - **INSERT**: 只能插入自己的资料
139
+ - **UPDATE**: 只能更新自己的资料
140
+
141
+ ## 会话安全
142
+
143
+ 使用 AES-256 加密存储会话:
144
+
145
+ 1. 生成 256-bit 加密密钥
146
+ 2. 密钥存储在 Expo SecureStore
147
+ 3. 会话数据加密后存储在 AsyncStorage
148
+
149
+ ## 环境变量说明
150
+
151
+ Expo 要求环境变量必须以 `EXPO_PUBLIC_` 开头:
152
+
153
+ - `EXPO_PUBLIC_SUPABASE_URL` - Supabase 项目 URL
154
+ - `EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY` - Supabase 发布密钥
155
+
156
+ ## 邮箱验证
157
+
158
+ 默认情况下,Supabase Auth 需要邮箱验证才能创建会话。
159
+
160
+ 测试时可禁用邮箱验证:
161
+ 1. 进入 [Auth Providers 设置](https://supabase.com/dashboard/project/_/auth/providers)
162
+ 2. 关闭 "Confirm email"
163
+
164
+ ## 下一步
165
+
166
+ - 实现 [Deep Linking](https://supabase.com/docs/guides/auth/native-mobile-deep-linking?platform=react-native) 支持邮箱验证
167
+ - 添加社交登录(Google, Apple 等)
168
+ - 启用双因素认证
169
+
170
+ ## 相关资源
171
+
172
+ - [Supabase 文档](https://supabase.com/docs)
173
+ - [Expo 文档](https://docs.expo.dev)
174
+ - [React Native 文档](https://reactnative.dev)
175
+ - [完整示例](https://github.com/supabase/supabase/tree/master/examples/user-management/expo-user-management)
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,34 @@
1
+ {
2
+ "expo": {
3
+ "name": "supabase-expo-app",
4
+ "slug": "supabase-expo-app",
5
+ "version": "1.0.0",
6
+ "orientation": "portrait",
7
+ "icon": "./assets/icon.png",
8
+ "userInterfaceStyle": "light",
9
+ "newArchEnabled": true,
10
+ "splash": {
11
+ "image": "./assets/splash-icon.png",
12
+ "resizeMode": "contain",
13
+ "backgroundColor": "#ffffff"
14
+ },
15
+ "ios": {
16
+ "supportsTablet": true
17
+ },
18
+ "android": {
19
+ "adaptiveIcon": {
20
+ "foregroundImage": "./assets/adaptive-icon.png",
21
+ "backgroundColor": "#ffffff"
22
+ },
23
+ "edgeToEdgeEnabled": true,
24
+ "predictiveBackGestureEnabled": false
25
+ },
26
+ "web": {
27
+ "favicon": "./assets/favicon.png"
28
+ },
29
+ "plugins": [
30
+ "expo-sqlite",
31
+ "expo-secure-store"
32
+ ]
33
+ }
34
+ }
@@ -0,0 +1,234 @@
1
+ import { useState, useEffect, useCallback } from 'react'
2
+ import { StyleSheet, View, Text, TextInput, TouchableOpacity, Alert, ActivityIndicator, Image } from 'react-native'
3
+ import { supabase } from '../lib/supabase'
4
+ import Avatar from './Avatar'
5
+
6
+ interface AccountProps {
7
+ session: any
8
+ }
9
+
10
+ export default function Account({ session }: AccountProps) {
11
+ const [loading, setLoading] = useState(true)
12
+ const [username, setUsername] = useState('')
13
+ const [fullName, setFullName] = useState('')
14
+ const [website, setWebsite] = useState('')
15
+ const [avatarUrl, setAvatarUrl] = useState('')
16
+
17
+ useEffect(() => {
18
+ if (session) {
19
+ getProfile()
20
+ }
21
+ }, [session])
22
+
23
+ async function getProfile() {
24
+ try {
25
+ setLoading(true)
26
+
27
+ const { data, error, status } = await supabase
28
+ .from('profiles')
29
+ .select(`username, full_name, website, avatar_url`)
30
+ .eq('id', session?.user?.id)
31
+ .single()
32
+
33
+ if (error && status !== 406) {
34
+ throw error
35
+ }
36
+
37
+ if (data) {
38
+ setUsername(data.username || '')
39
+ setFullName(data.full_name || '')
40
+ setWebsite(data.website || '')
41
+ setAvatarUrl(data.avatar_url || '')
42
+ }
43
+ } catch (error) {
44
+ if (error instanceof Error) {
45
+ Alert.alert('获取资料失败', error.message)
46
+ }
47
+ } finally {
48
+ setLoading(false)
49
+ }
50
+ }
51
+
52
+ async function updateProfile({
53
+ username,
54
+ fullName,
55
+ website,
56
+ avatar_url,
57
+ }: {
58
+ username: string
59
+ fullName: string
60
+ website: string
61
+ avatar_url: string
62
+ }) {
63
+ try {
64
+ setLoading(true)
65
+
66
+ const { error } = await supabase.from('profiles').upsert({
67
+ id: session?.user?.id,
68
+ username,
69
+ full_name: fullName,
70
+ website,
71
+ avatar_url: avatar_url,
72
+ updated_at: new Date().toISOString(),
73
+ })
74
+
75
+ if (error) throw error
76
+ Alert.alert('成功', '资料已更新!')
77
+ } catch (error) {
78
+ if (error instanceof Error) {
79
+ Alert.alert('更新资料失败', error.message)
80
+ }
81
+ } finally {
82
+ setLoading(false)
83
+ }
84
+ }
85
+
86
+ const handleSignOut = async () => {
87
+ await supabase.auth.signOut()
88
+ }
89
+
90
+ if (loading) {
91
+ return (
92
+ <View style={styles.loadingContainer}>
93
+ <ActivityIndicator size="large" color="#10b981" />
94
+ </View>
95
+ )
96
+ }
97
+
98
+ return (
99
+ <View style={styles.container}>
100
+ <View style={styles.header}>
101
+ <Text style={styles.title}>账户设置</Text>
102
+ <Text style={styles.subtitle}>{session?.user?.email}</Text>
103
+ </View>
104
+
105
+ <Avatar
106
+ size={100}
107
+ url={avatarUrl}
108
+ onUpload={(url: string) => {
109
+ setAvatarUrl(url)
110
+ updateProfile({ username, fullName, website, avatar_url: url })
111
+ }}
112
+ />
113
+
114
+ <View style={styles.form}>
115
+ <View style={styles.inputGroup}>
116
+ <Text style={styles.label}>用户名</Text>
117
+ <TextInput
118
+ style={styles.input}
119
+ value={username}
120
+ onChangeText={setUsername}
121
+ placeholder="输入用户名"
122
+ />
123
+ </View>
124
+
125
+ <View style={styles.inputGroup}>
126
+ <Text style={styles.label}>姓名</Text>
127
+ <TextInput
128
+ style={styles.input}
129
+ value={fullName}
130
+ onChangeText={setFullName}
131
+ placeholder="输入姓名"
132
+ />
133
+ </View>
134
+
135
+ <View style={styles.inputGroup}>
136
+ <Text style={styles.label}>网站</Text>
137
+ <TextInput
138
+ style={styles.input}
139
+ value={website}
140
+ onChangeText={setWebsite}
141
+ placeholder="https://example.com"
142
+ keyboardType="url"
143
+ autoCapitalize="none"
144
+ />
145
+ </View>
146
+
147
+ <TouchableOpacity
148
+ style={styles.button}
149
+ onPress={() => updateProfile({ username, fullName, website, avatar_url: avatarUrl })}
150
+ disabled={loading}
151
+ >
152
+ {loading ? (
153
+ <ActivityIndicator color="#fff" />
154
+ ) : (
155
+ <Text style={styles.buttonText}>更新资料</Text>
156
+ )}
157
+ </TouchableOpacity>
158
+
159
+ <TouchableOpacity
160
+ style={styles.signOutButton}
161
+ onPress={handleSignOut}
162
+ >
163
+ <Text style={styles.signOutText}>退出登录</Text>
164
+ </TouchableOpacity>
165
+ </View>
166
+ </View>
167
+ )
168
+ }
169
+
170
+ const styles = StyleSheet.create({
171
+ loadingContainer: {
172
+ flex: 1,
173
+ justifyContent: 'center',
174
+ alignItems: 'center',
175
+ },
176
+ container: {
177
+ flex: 1,
178
+ padding: 20,
179
+ backgroundColor: '#fff',
180
+ },
181
+ header: {
182
+ alignItems: 'center',
183
+ marginBottom: 30,
184
+ },
185
+ title: {
186
+ fontSize: 28,
187
+ fontWeight: 'bold',
188
+ },
189
+ subtitle: {
190
+ fontSize: 14,
191
+ color: '#666',
192
+ marginTop: 4,
193
+ },
194
+ form: {
195
+ gap: 16,
196
+ marginTop: 20,
197
+ },
198
+ inputGroup: {
199
+ gap: 8,
200
+ },
201
+ label: {
202
+ fontSize: 14,
203
+ fontWeight: '500',
204
+ color: '#333',
205
+ },
206
+ input: {
207
+ borderWidth: 1,
208
+ borderColor: '#ddd',
209
+ borderRadius: 8,
210
+ padding: 14,
211
+ fontSize: 16,
212
+ },
213
+ button: {
214
+ backgroundColor: '#10b981',
215
+ borderRadius: 8,
216
+ padding: 16,
217
+ alignItems: 'center',
218
+ marginTop: 8,
219
+ },
220
+ buttonText: {
221
+ color: '#fff',
222
+ fontSize: 16,
223
+ fontWeight: '600',
224
+ },
225
+ signOutButton: {
226
+ alignItems: 'center',
227
+ marginTop: 16,
228
+ padding: 12,
229
+ },
230
+ signOutText: {
231
+ color: '#ef4444',
232
+ fontSize: 14,
233
+ },
234
+ })
@@ -0,0 +1,161 @@
1
+ import { useState } from 'react'
2
+ import { StyleSheet, View, Text, TextInput, TouchableOpacity, Alert, ActivityIndicator } from 'react-native'
3
+ import { supabase } from '../lib/supabase'
4
+
5
+ interface AuthProps {
6
+ onAuthSuccess: () => void
7
+ }
8
+
9
+ export default function Auth({ onAuthSuccess }: AuthProps) {
10
+ const [email, setEmail] = useState('')
11
+ const [password, setPassword] = useState('')
12
+ const [loading, setLoading] = useState(false)
13
+ const [isSignUp, setIsSignUp] = useState(false)
14
+
15
+ async function signInWithEmail() {
16
+ setLoading(true)
17
+ const { error } = await supabase.auth.signInWithPassword({
18
+ email: email,
19
+ password: password,
20
+ })
21
+
22
+ if (error) {
23
+ Alert.alert('登录失败', error.message)
24
+ } else {
25
+ onAuthSuccess()
26
+ }
27
+ setLoading(false)
28
+ }
29
+
30
+ async function signUpWithEmail() {
31
+ setLoading(true)
32
+ const { error } = await supabase.auth.signUp({
33
+ email: email,
34
+ password: password,
35
+ })
36
+
37
+ if (error) {
38
+ Alert.alert('注册失败', error.message)
39
+ } else {
40
+ Alert.alert('注册成功', '请检查您的邮箱以验证账户')
41
+ }
42
+ setLoading(false)
43
+ }
44
+
45
+ return (
46
+ <View style={styles.container}>
47
+ <View style={styles.header}>
48
+ <Text style={styles.title}>用户管理</Text>
49
+ <Text style={styles.subtitle}>{isSignUp ? '创建新账户' : '登录您的账户'}</Text>
50
+ </View>
51
+
52
+ <View style={styles.form}>
53
+ <View style={styles.inputGroup}>
54
+ <Text style={styles.label}>邮箱</Text>
55
+ <TextInput
56
+ style={styles.input}
57
+ placeholder="your@email.com"
58
+ value={email}
59
+ onChangeText={setEmail}
60
+ autoCapitalize="none"
61
+ keyboardType="email-address"
62
+ />
63
+ </View>
64
+
65
+ <View style={styles.inputGroup}>
66
+ <Text style={styles.label}>密码</Text>
67
+ <TextInput
68
+ style={styles.input}
69
+ placeholder="••••••••"
70
+ value={password}
71
+ onChangeText={setPassword}
72
+ secureTextEntry
73
+ autoCapitalize="none"
74
+ />
75
+ </View>
76
+
77
+ <TouchableOpacity
78
+ style={styles.button}
79
+ onPress={isSignUp ? signUpWithEmail : signInWithEmail}
80
+ disabled={loading}
81
+ >
82
+ {loading ? (
83
+ <ActivityIndicator color="#fff" />
84
+ ) : (
85
+ <Text style={styles.buttonText}>{isSignUp ? '注册' : '登录'}</Text>
86
+ )}
87
+ </TouchableOpacity>
88
+
89
+ <TouchableOpacity
90
+ style={styles.switchButton}
91
+ onPress={() => setIsSignUp(!isSignUp)}
92
+ >
93
+ <Text style={styles.switchText}>
94
+ {isSignUp ? '已有账户?立即登录' : '没有账户?立即注册'}
95
+ </Text>
96
+ </TouchableOpacity>
97
+ </View>
98
+ </View>
99
+ )
100
+ }
101
+
102
+ const styles = StyleSheet.create({
103
+ container: {
104
+ flex: 1,
105
+ padding: 20,
106
+ justifyContent: 'center',
107
+ backgroundColor: '#fff',
108
+ },
109
+ header: {
110
+ marginBottom: 40,
111
+ },
112
+ title: {
113
+ fontSize: 32,
114
+ fontWeight: 'bold',
115
+ textAlign: 'center',
116
+ },
117
+ subtitle: {
118
+ fontSize: 16,
119
+ color: '#666',
120
+ textAlign: 'center',
121
+ marginTop: 8,
122
+ },
123
+ form: {
124
+ gap: 16,
125
+ },
126
+ inputGroup: {
127
+ gap: 8,
128
+ },
129
+ label: {
130
+ fontSize: 14,
131
+ fontWeight: '500',
132
+ color: '#333',
133
+ },
134
+ input: {
135
+ borderWidth: 1,
136
+ borderColor: '#ddd',
137
+ borderRadius: 8,
138
+ padding: 14,
139
+ fontSize: 16,
140
+ },
141
+ button: {
142
+ backgroundColor: '#10b981',
143
+ borderRadius: 8,
144
+ padding: 16,
145
+ alignItems: 'center',
146
+ marginTop: 8,
147
+ },
148
+ buttonText: {
149
+ color: '#fff',
150
+ fontSize: 16,
151
+ fontWeight: '600',
152
+ },
153
+ switchButton: {
154
+ alignItems: 'center',
155
+ marginTop: 16,
156
+ },
157
+ switchText: {
158
+ color: '#10b981',
159
+ fontSize: 14,
160
+ },
161
+ })