vibefast-cli 1.1.5 → 1.3.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 (301) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +63 -169
  3. package/dist/__tests__/recipes.test.js +89 -85
  4. package/dist/__tests__/recipes.test.js.map +1 -1
  5. package/dist/commands/add.d.ts +1 -1
  6. package/dist/commands/add.d.ts.map +1 -1
  7. package/dist/commands/add.js +576 -588
  8. package/dist/commands/add.js.map +1 -1
  9. package/dist/commands/checklist.d.ts +1 -1
  10. package/dist/commands/checklist.d.ts.map +1 -1
  11. package/dist/commands/checklist.js +40 -39
  12. package/dist/commands/checklist.js.map +1 -1
  13. package/dist/commands/doctor.d.ts +1 -1
  14. package/dist/commands/doctor.js +22 -22
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/env.d.ts +1 -1
  17. package/dist/commands/env.d.ts.map +1 -1
  18. package/dist/commands/env.js +58 -53
  19. package/dist/commands/env.js.map +1 -1
  20. package/dist/commands/health.d.ts +1 -1
  21. package/dist/commands/health.d.ts.map +1 -1
  22. package/dist/commands/health.js +101 -93
  23. package/dist/commands/health.js.map +1 -1
  24. package/dist/commands/init.d.ts +1 -1
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +416 -296
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/remove.d.ts +1 -1
  29. package/dist/commands/remove.d.ts.map +1 -1
  30. package/dist/commands/remove.js +77 -64
  31. package/dist/commands/remove.js.map +1 -1
  32. package/dist/commands/status.d.ts +1 -1
  33. package/dist/commands/status.d.ts.map +1 -1
  34. package/dist/commands/status.js +15 -14
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/core/__tests__/detect.test.js +68 -34
  37. package/dist/core/__tests__/detect.test.js.map +1 -1
  38. package/dist/core/ast.d.ts +14 -0
  39. package/dist/core/ast.d.ts.map +1 -0
  40. package/dist/core/ast.js +239 -0
  41. package/dist/core/ast.js.map +1 -0
  42. package/dist/core/codemod.d.ts.map +1 -1
  43. package/dist/core/codemod.js +62 -44
  44. package/dist/core/codemod.js.map +1 -1
  45. package/dist/core/config.d.ts +10 -0
  46. package/dist/core/config.d.ts.map +1 -0
  47. package/dist/core/config.js +51 -0
  48. package/dist/core/config.js.map +1 -0
  49. package/dist/core/detect.d.ts +8 -2
  50. package/dist/core/detect.d.ts.map +1 -1
  51. package/dist/core/detect.js +52 -21
  52. package/dist/core/detect.js.map +1 -1
  53. package/dist/core/errors.d.ts.map +1 -1
  54. package/dist/core/errors.js +9 -8
  55. package/dist/core/errors.js.map +1 -1
  56. package/dist/core/exec.d.ts +16 -0
  57. package/dist/core/exec.d.ts.map +1 -0
  58. package/dist/core/exec.js +48 -0
  59. package/dist/core/exec.js.map +1 -0
  60. package/dist/core/manualSteps.d.ts +7 -0
  61. package/dist/core/manualSteps.d.ts.map +1 -0
  62. package/dist/core/manualSteps.js +59 -0
  63. package/dist/core/manualSteps.js.map +1 -0
  64. package/dist/core/paths.d.ts +3 -1
  65. package/dist/core/paths.d.ts.map +1 -1
  66. package/dist/core/paths.js +14 -10
  67. package/dist/core/paths.js.map +1 -1
  68. package/dist/core/spinner.d.ts +1 -1
  69. package/dist/core/spinner.d.ts.map +1 -1
  70. package/dist/core/spinner.js +38 -8
  71. package/dist/core/spinner.js.map +1 -1
  72. package/dist/core/vosk.d.ts.map +1 -1
  73. package/dist/core/vosk.js +50 -39
  74. package/dist/core/vosk.js.map +1 -1
  75. package/docs/manual-testing.md +91 -0
  76. package/package.json +6 -3
  77. package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
  78. package/recipes/audio-recorder/recipe.json +3 -3
  79. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
  80. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
  81. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
  82. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/index.ts +4 -0
  83. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
  84. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
  85. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
  86. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
  87. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
  88. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
  89. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
  90. package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +369 -0
  91. package/recipes/audio-recorder-supabase/packages/backend/supabase/migrations/recordings.sql +70 -0
  92. package/recipes/audio-recorder-supabase/recipe.json +52 -0
  93. package/recipes/audio-recorder-supabase@latest.zip +0 -0
  94. package/recipes/audio-recorder@latest.zip +0 -0
  95. package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -3
  96. package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +2 -2
  97. package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +5 -5
  98. package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -3
  99. package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +20 -4
  100. package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +7 -6
  101. package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +6 -4
  102. package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +1 -1
  103. package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +5 -4
  104. package/recipes/charts/recipe.json +4 -13
  105. package/recipes/charts@latest.zip +0 -0
  106. package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
  107. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
  108. package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
  109. package/recipes/chatbot/recipe.json +26 -92
  110. package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +515 -0
  111. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/app/index.tsx +257 -0
  112. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
  113. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-input-bar.tsx +485 -0
  114. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
  115. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +223 -0
  116. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
  117. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/image-preview-list.tsx +116 -0
  118. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
  119. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
  120. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
  121. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
  122. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-list.tsx +170 -0
  123. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
  124. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
  125. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
  126. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/models.ts +20 -0
  127. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
  128. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +142 -0
  129. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-config.ts +458 -0
  130. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +429 -0
  131. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
  132. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-conversation.ts +90 -0
  133. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
  134. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
  135. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +213 -0
  136. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/index.ts +86 -0
  137. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/models.ts +162 -0
  138. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/providers.ts +62 -0
  139. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/types.ts +40 -0
  140. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/file-uploader.ts +287 -0
  141. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/message-handler-service.ts +189 -0
  142. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/types/index.ts +70 -0
  143. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
  144. package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +243 -0
  145. package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +327 -0
  146. package/recipes/chatbot-supabase/packages/backend/supabase/functions/chat-stream/index.ts +347 -0
  147. package/recipes/chatbot-supabase/packages/backend/supabase/migrations/chatbot.sql +104 -0
  148. package/recipes/chatbot-supabase/recipe.json +106 -0
  149. package/recipes/chatbot-supabase@latest.zip +0 -0
  150. package/recipes/chatbot.zip +0 -0
  151. package/recipes/chatbot@latest.zip +0 -0
  152. package/recipes/image-analysis/packages/backend/convex/imageAnalysis/index.ts +2 -2
  153. package/recipes/image-analysis/packages/backend/convex/imageAnalysis.ts +0 -1
  154. package/recipes/image-analysis/recipe.json +15 -55
  155. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +304 -0
  156. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx +221 -0
  157. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +333 -0
  158. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading-screen.tsx +214 -0
  159. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading.tsx +191 -0
  160. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/results.tsx +137 -0
  161. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/trait-details.tsx +172 -0
  162. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-analysis-data.ts +160 -0
  163. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-results-screen.ts +151 -0
  164. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-badge.tsx +77 -0
  165. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-card.tsx +75 -0
  166. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-unlocked-modal.tsx +162 -0
  167. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievements-section.tsx +44 -0
  168. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/advice-list.tsx +42 -0
  169. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/circular-progress.tsx +233 -0
  170. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/content-card.tsx +38 -0
  171. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/error-state.tsx +42 -0
  172. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/index.ts +9 -0
  173. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/loading-state.tsx +26 -0
  174. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/profile-image.tsx +60 -0
  175. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/results-header.tsx +62 -0
  176. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/score-display.tsx +54 -0
  177. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/share-options-modal.tsx +110 -0
  178. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/traits-grid.tsx +74 -0
  179. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/analysis-config.ts +80 -0
  180. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/master-analysis-config.ts +157 -0
  181. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/index.ts +1 -0
  182. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-analysis.ts +38 -0
  183. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +208 -0
  184. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/analysis-service.ts +262 -0
  185. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/share-service.ts +176 -0
  186. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/trait-details-service.ts +289 -0
  187. package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +132 -0
  188. package/recipes/image-analysis-supabase/packages/backend/supabase/functions/analyze-image/index.ts +312 -0
  189. package/recipes/image-analysis-supabase/packages/backend/supabase/migrations/image_analysis.sql +42 -0
  190. package/recipes/image-analysis-supabase/recipe.json +90 -0
  191. package/recipes/image-analysis-supabase@latest.zip +0 -0
  192. package/recipes/image-analysis@latest.zip +0 -0
  193. package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +16 -2
  194. package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +11 -5
  195. package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +11 -5
  196. package/recipes/image-generator/packages/backend/convex/imageGeneration/index.ts +2 -2
  197. package/recipes/image-generator/recipe.json +16 -39
  198. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/_layout.tsx +26 -0
  199. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
  200. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/index.tsx +251 -0
  201. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/gallery-image.tsx +25 -0
  202. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
  203. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-model-selector.tsx +216 -0
  204. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
  205. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
  206. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
  207. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator.ts +103 -0
  208. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/models/models.ts +66 -0
  209. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-gallery-service.ts +96 -0
  210. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-save-service.ts +120 -0
  211. package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts +291 -0
  212. package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql +71 -0
  213. package/recipes/image-generator-supabase/recipe.json +86 -0
  214. package/recipes/image-generator-supabase@latest.zip +0 -0
  215. package/recipes/image-generator@latest.zip +0 -0
  216. package/recipes/ios-widget/recipe.json +15 -24
  217. package/recipes/ios-widget@latest.zip +0 -0
  218. package/recipes/onboarding/apps/native/src/features/onboarding/analytics/index.ts +9 -0
  219. package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding-with-analytics.tsx +141 -0
  220. package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding.tsx +173 -0
  221. package/recipes/onboarding/apps/native/src/features/onboarding/config/onboarding-flow-config.ts +189 -0
  222. package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/app/index.tsx +42 -0
  223. package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/data.ts +32 -0
  224. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/app/index.tsx +43 -0
  225. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +222 -0
  226. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +133 -0
  227. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +165 -0
  228. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +199 -0
  229. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +154 -0
  230. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +156 -0
  231. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +158 -0
  232. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +139 -0
  233. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +129 -0
  234. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +138 -0
  235. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +190 -0
  236. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +98 -0
  237. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +181 -0
  238. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +110 -0
  239. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +139 -0
  240. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +217 -0
  241. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +58 -0
  242. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/constants.ts +179 -0
  243. package/recipes/onboarding/apps/native/src/features/onboarding/hooks/use-onboarding-analytics.ts +323 -0
  244. package/recipes/onboarding/apps/native/src/features/onboarding/services/onboarding-analytics.ts +432 -0
  245. package/recipes/onboarding/recipe.json +15 -0
  246. package/recipes/onboarding@latest.zip +0 -0
  247. package/recipes/payments/recipe.json +28 -61
  248. package/recipes/payments-supabase/apps/native/src/features/payments/README.md +200 -0
  249. package/recipes/payments-supabase/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
  250. package/recipes/payments-supabase/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
  251. package/recipes/payments-supabase/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
  252. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
  253. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
  254. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
  255. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
  256. package/recipes/payments-supabase/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
  257. package/recipes/payments-supabase/apps/native/src/features/payments/index.ts +8 -0
  258. package/recipes/payments-supabase/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
  259. package/recipes/payments-supabase/packages/backend/src/services/payments.ts +201 -0
  260. package/recipes/payments-supabase/packages/backend/supabase/migrations/payments.sql +35 -0
  261. package/recipes/payments-supabase/recipe.json +72 -0
  262. package/recipes/payments-supabase@latest.zip +0 -0
  263. package/recipes/payments@latest.zip +0 -0
  264. package/recipes/quiz/apps/native/src/features/quiz/index.tsx +1 -2
  265. package/recipes/quiz/recipe.json +6 -9
  266. package/recipes/quiz@latest.zip +0 -0
  267. package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +1 -2
  268. package/recipes/tracker-app/recipe.json +7 -10
  269. package/recipes/tracker-app@latest.zip +0 -0
  270. package/recipes/voice-bot/recipe.json +8 -68
  271. package/recipes/voice-bot.zip +0 -0
  272. package/recipes/voice-bot@latest.zip +0 -0
  273. package/recipes/wake-word/recipe.json +10 -9
  274. package/recipes/wake-word.zip +0 -0
  275. package/recipes/wake-word@latest.zip +0 -0
  276. package/recipes/charts/apps/native/src/app/(root)/(protected)/charts/index.tsx +0 -3
  277. package/recipes/chatbot/packages/backend/convex/lib/rateLimit.ts +0 -100
  278. package/recipes/chatbot/packages/backend/convex/lib/telemetry.ts +0 -29
  279. package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -0
  280. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/front.jpg +0 -0
  281. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/side.jpg +0 -0
  282. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/threeQuarter.jpg +0 -0
  283. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/_layout.tsx +0 -5
  284. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/analysis-options.tsx +0 -50
  285. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/camera.tsx +0 -2
  286. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/index.tsx +0 -50
  287. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/loading.tsx +0 -50
  288. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/results.tsx +0 -2
  289. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/trait-details.tsx +0 -3
  290. package/recipes/image-analysis/packages/backend/convex/imageAnalysisFunctions.ts +0 -325
  291. package/recipes/image-analysis/packages/backend/convex/lib/ai/imageAnalysisAdapter.ts +0 -200
  292. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
  293. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
  294. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
  295. package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
  296. package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
  297. package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
  298. package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
  299. /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
  300. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
  301. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/index.tsx +0 -0
@@ -0,0 +1,162 @@
1
+ import { FontAwesome5 } from '@expo/vector-icons';
2
+ import { useRouter } from 'expo-router';
3
+ import { Platform, Text, View } from 'react-native';
4
+ import Animated, { FadeIn, FadeInDown } from 'react-native-reanimated';
5
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
+
7
+ import { Pressable } from '@/components/ui';
8
+
9
+ type AchievementUnlockedModalProps = {
10
+ showModal: boolean;
11
+ unlockedAchievements: any[];
12
+ onClose: () => void;
13
+ isDarkMode: boolean;
14
+ };
15
+
16
+ export function AchievementUnlockedModal({
17
+ showModal,
18
+ unlockedAchievements,
19
+ onClose,
20
+ isDarkMode,
21
+ }: AchievementUnlockedModalProps) {
22
+ const router = useRouter();
23
+ const insets = useSafeAreaInsets();
24
+
25
+ if (!showModal || unlockedAchievements.length === 0) return null;
26
+
27
+ return (
28
+ <Animated.View
29
+ entering={FadeIn.duration(200)}
30
+ className="absolute inset-0 items-center justify-center"
31
+ style={[
32
+ {
33
+ position: 'absolute',
34
+ top: 0,
35
+ left: 0,
36
+ right: 0,
37
+ bottom: 0,
38
+ elevation: Platform.OS === 'android' ? 1000 : undefined,
39
+ zIndex: 9999,
40
+ },
41
+ ]}
42
+ >
43
+ <Pressable
44
+ className="absolute inset-0 bg-black/70"
45
+ onPress={onClose}
46
+ style={{
47
+ position: 'absolute',
48
+ top: 0,
49
+ left: 0,
50
+ right: 0,
51
+ bottom: 0,
52
+ paddingTop: insets.top,
53
+ paddingBottom: insets.bottom,
54
+ }}
55
+ />
56
+
57
+ <View
58
+ className="mx-auto w-screen flex-1 items-center justify-center px-4"
59
+ style={{ zIndex: 10000 }}
60
+ >
61
+ <Animated.View
62
+ entering={FadeInDown.duration(400).delay(200)}
63
+ className={`mx-auto w-full max-w-[90%] rounded-2xl p-5 ${
64
+ isDarkMode ? 'bg-neutral-800' : 'bg-white'
65
+ }`}
66
+ style={{
67
+ elevation: Platform.OS === 'android' ? 1001 : undefined,
68
+ zIndex: 10001,
69
+ shadowColor: '#000',
70
+ shadowOffset: {
71
+ width: 0,
72
+ height: 2,
73
+ },
74
+ shadowOpacity: 0.25,
75
+ shadowRadius: 3.84,
76
+ }}
77
+ >
78
+ <View className="mb-4 items-center">
79
+ <Text
80
+ className={`mb-4 text-2xl font-bold ${
81
+ isDarkMode ? 'text-white' : 'text-neutral-900'
82
+ }`}
83
+ >
84
+ Achievement{unlockedAchievements.length > 1 ? 's' : ''} Unlocked!
85
+ </Text>
86
+
87
+ <View className="w-full gap-2">
88
+ {unlockedAchievements.map((achievement) => (
89
+ <Animated.View
90
+ key={achievement.id}
91
+ entering={FadeInDown.duration(400).delay(300)}
92
+ className="mb-2 flex-row items-center rounded-xl p-3"
93
+ style={{ backgroundColor: `${achievement.color}20` }}
94
+ >
95
+ <View
96
+ className="mr-3 size-12 items-center justify-center rounded-full"
97
+ style={{ backgroundColor: achievement.color }}
98
+ >
99
+ <FontAwesome5
100
+ name={achievement.icon}
101
+ size={20}
102
+ color="white"
103
+ />
104
+ </View>
105
+ <View className="flex-1">
106
+ <Text
107
+ className={`font-bold ${
108
+ isDarkMode ? 'text-white' : 'text-neutral-900'
109
+ }`}
110
+ >
111
+ {achievement.title}
112
+ </Text>
113
+ <Text
114
+ className={`text-sm ${
115
+ isDarkMode ? 'text-neutral-300' : 'text-neutral-600'
116
+ }`}
117
+ >
118
+ {achievement.description}
119
+ </Text>
120
+ <Text
121
+ className="mt-1 text-sm font-medium"
122
+ style={{ color: achievement.color }}
123
+ >
124
+ +{achievement.points} points
125
+ </Text>
126
+ </View>
127
+ </Animated.View>
128
+ ))}
129
+ </View>
130
+ </View>
131
+
132
+ <View className="flex-row gap-2">
133
+ <Pressable
134
+ onPress={onClose}
135
+ className={`flex-1 items-center rounded-xl px-6 py-4 font-semibold ${
136
+ isDarkMode ? 'bg-neutral-700' : 'bg-neutral-200'
137
+ }`}
138
+ >
139
+ <Text
140
+ className={`font-medium ${
141
+ isDarkMode ? 'text-white' : 'text-neutral-800'
142
+ }`}
143
+ >
144
+ Continue
145
+ </Text>
146
+ </Pressable>
147
+
148
+ <Pressable
149
+ onPress={() => {
150
+ onClose();
151
+ router.navigate('/leaderboard');
152
+ }}
153
+ className="flex-1 items-center rounded-xl bg-indigo-500 px-6 py-4 font-semibold"
154
+ >
155
+ <Text className="font-medium text-white">Leaderboard</Text>
156
+ </Pressable>
157
+ </View>
158
+ </Animated.View>
159
+ </View>
160
+ </Animated.View>
161
+ );
162
+ }
@@ -0,0 +1,44 @@
1
+ import { View } from 'react-native';
2
+
3
+ import { Text } from '@/components/ui';
4
+ import { useThemeConfig } from '@/lib/use-theme-config';
5
+
6
+ import type { Achievement } from '../../services/analysis-service';
7
+ import { AchievementCard } from './achievement-card';
8
+
9
+ type AchievementsSectionProps = {
10
+ achievements: Achievement[];
11
+ className?: string;
12
+ };
13
+
14
+ export function AchievementsSection({
15
+ achievements,
16
+ className = '',
17
+ }: AchievementsSectionProps) {
18
+ const theme = useThemeConfig();
19
+ const isDarkMode = theme.dark;
20
+
21
+ if (achievements.length === 0) {
22
+ return null;
23
+ }
24
+
25
+ return (
26
+ <View className={`mx-6 mb-6 ${className}`}>
27
+ {/* Section Title */}
28
+ <Text
29
+ className={`mb-4 text-xl font-bold ${
30
+ isDarkMode ? 'text-white' : 'text-neutral-900'
31
+ }`}
32
+ >
33
+ 🏆 Achievements Earned
34
+ </Text>
35
+
36
+ {/* Full-width Achievements */}
37
+ <View className="gap-y-3">
38
+ {achievements.map((achievement) => (
39
+ <AchievementCard key={achievement.id} achievement={achievement} />
40
+ ))}
41
+ </View>
42
+ </View>
43
+ );
44
+ }
@@ -0,0 +1,42 @@
1
+ import { View } from 'react-native';
2
+
3
+ import { Text } from '@/components/ui';
4
+ import { useThemeConfig } from '@/lib/use-theme-config';
5
+
6
+ type AdviceListProps = {
7
+ items: string[];
8
+ bulletType?: 'dot' | 'emoji';
9
+ bulletColor?: string;
10
+ emoji?: string;
11
+ };
12
+
13
+ export function AdviceList({
14
+ items,
15
+ bulletType = 'dot',
16
+ bulletColor = 'bg-green-500',
17
+ emoji = '💡',
18
+ }: AdviceListProps) {
19
+ const theme = useThemeConfig();
20
+ const isDarkMode = theme.dark;
21
+
22
+ return (
23
+ <>
24
+ {items.map((item, index) => (
25
+ <View key={index} className="mb-3 flex-row">
26
+ {bulletType === 'dot' ? (
27
+ <View className={`mr-3 mt-1 size-2 rounded-full ${bulletColor}`} />
28
+ ) : (
29
+ <Text className="mr-3 text-base">{emoji}</Text>
30
+ )}
31
+ <Text
32
+ className={`flex-1 text-base leading-6 ${
33
+ isDarkMode ? 'text-neutral-300' : 'text-neutral-700'
34
+ }`}
35
+ >
36
+ {item}
37
+ </Text>
38
+ </View>
39
+ ))}
40
+ </>
41
+ );
42
+ }
@@ -0,0 +1,233 @@
1
+ import { useEffect } from 'react';
2
+ import { Platform, Text, View } from 'react-native';
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withSpring,
7
+ withTiming,
8
+ } from 'react-native-reanimated';
9
+ import Svg, { Circle, LinearGradient, Stop } from 'react-native-svg';
10
+
11
+ import { Pressable } from '@/components/ui';
12
+ import { useResponsiveSize } from '@/lib/hooks';
13
+ import { useThemeConfig } from '@/lib/use-theme-config';
14
+
15
+ type CircularProgressProps = {
16
+ value: number;
17
+ size?: number;
18
+ strokeWidth?: number;
19
+ label?: string;
20
+ onPress?: () => void;
21
+ showViewAdvice?: boolean;
22
+ isCard?: boolean;
23
+ isSubscribed?: boolean;
24
+ gradientColors?: {
25
+ high: string[];
26
+ medium: string[];
27
+ low: string[];
28
+ };
29
+ };
30
+
31
+ export function CircularProgress({
32
+ value,
33
+ size = 120,
34
+ strokeWidth = 12,
35
+ label,
36
+ onPress,
37
+ showViewAdvice = false,
38
+ isCard = true,
39
+ isSubscribed = true,
40
+ gradientColors = {
41
+ high: ['#4ADE80', '#22C55E'],
42
+ medium: ['#FBBF24', '#F59E0B'],
43
+ low: ['#FB7185', '#E11D48'],
44
+ },
45
+ }: CircularProgressProps) {
46
+ const { isTablet } = useResponsiveSize();
47
+ const theme = useThemeConfig();
48
+ const isDarkMode = theme.dark;
49
+
50
+ const radius = (size - strokeWidth) / 2;
51
+ const circumference = radius * 2 * Math.PI;
52
+ const progress = useSharedValue(0);
53
+
54
+ useEffect(() => {
55
+ progress.value = withTiming(value / 100, { duration: 1500 });
56
+ }, [value, progress]);
57
+
58
+ const animatedStyle = useAnimatedStyle(() => {
59
+ return {
60
+ transform: [{ scale: withSpring(1 + progress.value * 0.05) }],
61
+ };
62
+ });
63
+
64
+ const getGradientColors = (score: number) => {
65
+ if (score >= 70) return gradientColors.high;
66
+ if (score >= 50) return gradientColors.medium;
67
+ return gradientColors.low;
68
+ };
69
+
70
+ const colors = getGradientColors(value);
71
+ const scoreLevel = value >= 70 ? 'High' : value >= 50 ? 'Medium' : 'Low';
72
+
73
+ const handlePress = () => {
74
+ if (isSubscribed && onPress) {
75
+ onPress();
76
+ } else if (!isSubscribed) {
77
+ // Handle subscription modal if needed
78
+ console.log('Subscription required');
79
+ }
80
+ };
81
+
82
+ const cardStyle = isCard
83
+ ? {
84
+ borderWidth: 1.5,
85
+ borderColor: `${colors[0]}40`,
86
+ ...Platform.select({
87
+ ios: {
88
+ shadowColor: colors[0],
89
+ shadowOffset: { width: 0, height: 4 },
90
+ shadowOpacity: 0.3,
91
+ shadowRadius: 12,
92
+ backgroundColor: isDarkMode
93
+ ? 'rgba(31, 41, 55, 0.3)'
94
+ : 'rgba(255, 255, 255, 0.7)',
95
+ },
96
+ android: {
97
+ elevation: 4,
98
+ backgroundColor: isDarkMode
99
+ ? 'rgba(31, 41, 55, 0.95)'
100
+ : 'rgba(255, 255, 255, 0.95)',
101
+ },
102
+ }),
103
+ }
104
+ : undefined;
105
+
106
+ return (
107
+ <Pressable
108
+ onPress={handlePress}
109
+ className={`${
110
+ isCard
111
+ ? `relative overflow-hidden rounded-2xl border p-4 ${
112
+ isDarkMode
113
+ ? 'border-neutral-700/80 bg-neutral-800/30 backdrop-blur-3xl'
114
+ : 'border-neutral-200/80 bg-white/70 backdrop-blur-3xl'
115
+ }`
116
+ : ''
117
+ } ${isCard ? (isTablet ? 'h-[305px]' : 'h-[255px]') : ''}`}
118
+ style={cardStyle}
119
+ enableRipple={!!onPress}
120
+ rippleConfig={{
121
+ color: isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
122
+ }}
123
+ >
124
+ <View
125
+ className={`items-center ${isCard ? 'h-full justify-between' : ''}`}
126
+ >
127
+ {/* Title */}
128
+ {label && (
129
+ <Text
130
+ className={`${isTablet ? 'text-2xl' : 'text-lg'} -mx-2 mb-3 font-semibold leading-[1.2] ${
131
+ isDarkMode ? 'text-white' : 'text-neutral-900'
132
+ }`}
133
+ >
134
+ {label}
135
+ </Text>
136
+ )}
137
+
138
+ {/* Circular Progress */}
139
+ <Animated.View
140
+ className="mb-3 items-center justify-center"
141
+ style={animatedStyle}
142
+ >
143
+ <View style={{ width: size, height: size }}>
144
+ <Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
145
+ {/* Background Circle */}
146
+ <Circle
147
+ cx={size / 2}
148
+ cy={size / 2}
149
+ r={radius}
150
+ strokeWidth={strokeWidth}
151
+ stroke={isDarkMode ? '#374151' : '#d1d5db'}
152
+ fill="transparent"
153
+ opacity={isDarkMode ? 0.8 : 1}
154
+ />
155
+ {/* Progress Circle with Gradient */}
156
+ <LinearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
157
+ <Stop offset="0" stopColor={colors[0]} />
158
+ <Stop offset="1" stopColor={colors[1]} />
159
+ </LinearGradient>
160
+ <Circle
161
+ cx={size / 2}
162
+ cy={size / 2}
163
+ r={radius}
164
+ strokeWidth={strokeWidth}
165
+ stroke="url(#grad)"
166
+ fill="transparent"
167
+ strokeDasharray={circumference}
168
+ strokeDashoffset={circumference - (value / 100) * circumference}
169
+ strokeLinecap="round"
170
+ transform={`rotate(-90, ${size / 2}, ${size / 2})`}
171
+ />
172
+ </Svg>
173
+ <View className="absolute inset-0 size-full items-center justify-center">
174
+ {/* Score Level Badge */}
175
+ <View
176
+ className="mb-2 rounded-full px-2 py-0.5"
177
+ style={{ backgroundColor: `${colors[0]}20` }}
178
+ >
179
+ <Text
180
+ className={`${isTablet ? 'text-[10px]' : 'text-[8.8px]'}`}
181
+ style={{ color: colors[0], fontWeight: '600' }}
182
+ >
183
+ {scoreLevel}
184
+ </Text>
185
+ </View>
186
+ <Text
187
+ className={`${isTablet ? 'text-[48px]' : 'text-[40px]'} font-extrabold leading-none tracking-[0.9px] ${
188
+ isDarkMode ? 'text-white' : 'text-neutral-900'
189
+ }`}
190
+ >
191
+ {value.toFixed(0)}
192
+ </Text>
193
+ </View>
194
+ </View>
195
+ </Animated.View>
196
+
197
+ {/* View Advice Button */}
198
+ {showViewAdvice && (
199
+ <Pressable
200
+ onPress={handlePress}
201
+ className="w-full flex-row items-center justify-center"
202
+ enableRipple={true}
203
+ rippleConfig={{
204
+ color: isDarkMode ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
205
+ radius: 20,
206
+ }}
207
+ >
208
+ <Text
209
+ className={`${
210
+ isDarkMode ? 'text-neutral-400' : 'text-neutral-600'
211
+ } ${isTablet ? 'text-lg' : 'text-base'} mr-2`}
212
+ >
213
+ View Advice
214
+ </Text>
215
+ <View
216
+ className={`size-6 items-center justify-center rounded-full ${
217
+ isDarkMode ? 'bg-neutral-700' : 'bg-neutral-100'
218
+ }`}
219
+ >
220
+ <Text
221
+ className={`${
222
+ isDarkMode ? 'text-neutral-400' : 'text-neutral-600'
223
+ } ${isTablet ? 'text-lg' : 'text-base'}`}
224
+ >
225
+
226
+ </Text>
227
+ </View>
228
+ </Pressable>
229
+ )}
230
+ </View>
231
+ </Pressable>
232
+ );
233
+ }
@@ -0,0 +1,38 @@
1
+ import { View } from 'react-native';
2
+
3
+ import { Text } from '@/components/ui';
4
+ import { useThemeConfig } from '@/lib/use-theme-config';
5
+
6
+ type ContentCardProps = {
7
+ title: string;
8
+ children: React.ReactNode;
9
+ className?: string;
10
+ };
11
+
12
+ export function ContentCard({
13
+ title,
14
+ children,
15
+ className = '',
16
+ }: ContentCardProps) {
17
+ const theme = useThemeConfig();
18
+ const isDarkMode = theme.dark;
19
+
20
+ return (
21
+ <View className={`mx-6 mb-6 ${className}`}>
22
+ <View
23
+ className={`rounded-2xl p-4 shadow-sm ${
24
+ isDarkMode ? 'bg-neutral-800' : 'bg-white'
25
+ }`}
26
+ >
27
+ <Text
28
+ className={`mb-4 text-lg font-semibold ${
29
+ isDarkMode ? 'text-white' : 'text-neutral-900'
30
+ }`}
31
+ >
32
+ {title}
33
+ </Text>
34
+ {children}
35
+ </View>
36
+ </View>
37
+ );
38
+ }
@@ -0,0 +1,42 @@
1
+ import { Ionicons } from '@expo/vector-icons';
2
+ import { memo } from 'react';
3
+ import { View } from 'react-native';
4
+
5
+ import { Pressable, Text } from '@/components/ui';
6
+ import { useThemeConfig } from '@/lib/use-theme-config';
7
+
8
+ type ErrorStateProps = {
9
+ error: string;
10
+ onRetry: () => void;
11
+ };
12
+
13
+ export const ErrorState = memo(function ErrorState({
14
+ error,
15
+ onRetry,
16
+ }: ErrorStateProps) {
17
+ const theme = useThemeConfig();
18
+ const isDarkMode = theme.dark;
19
+
20
+ return (
21
+ <View
22
+ className={`flex-1 items-center justify-center px-6 ${isDarkMode ? 'bg-neutral-900' : 'bg-neutral-50'}`}
23
+ >
24
+ <Ionicons
25
+ name="alert-circle-outline"
26
+ size={48}
27
+ color={isDarkMode ? '#F87171' : '#EF4444'}
28
+ />
29
+ <Text
30
+ className={`mt-4 text-center text-lg ${isDarkMode ? 'text-white' : 'text-neutral-900'}`}
31
+ >
32
+ {error}
33
+ </Text>
34
+ <Pressable
35
+ className="mt-8 rounded-xl bg-primary-600 px-8 py-3"
36
+ onPress={onRetry}
37
+ >
38
+ <Text className="text-base font-semibold text-white">Go Back</Text>
39
+ </Pressable>
40
+ </View>
41
+ );
42
+ });
@@ -0,0 +1,9 @@
1
+ export { AdviceList } from './advice-list';
2
+ export { CircularProgress } from './circular-progress';
3
+ export { ContentCard } from './content-card';
4
+ export { ErrorState } from './error-state';
5
+ export { LoadingState } from './loading-state';
6
+ export { ProfileImage } from './profile-image';
7
+ export { ResultsHeader } from './results-header';
8
+ export { ScoreDisplay } from './score-display';
9
+ export { TraitsGrid } from './traits-grid';
@@ -0,0 +1,26 @@
1
+ import { memo } from 'react';
2
+ import { ActivityIndicator, View } from 'react-native';
3
+
4
+ import { Text } from '@/components/ui';
5
+ import { useThemeConfig } from '@/lib/use-theme-config';
6
+
7
+ export const LoadingState = memo(function LoadingState() {
8
+ const theme = useThemeConfig();
9
+ const isDarkMode = theme.dark;
10
+
11
+ return (
12
+ <View
13
+ className={`flex-1 items-center justify-center ${isDarkMode ? 'bg-neutral-900' : 'bg-neutral-50'}`}
14
+ >
15
+ <ActivityIndicator
16
+ size="large"
17
+ color={isDarkMode ? '#60A5FA' : '#3B82F6'}
18
+ />
19
+ <Text
20
+ className={`mt-4 ${isDarkMode ? 'text-white' : 'text-neutral-900'}`}
21
+ >
22
+ Loading analysis...
23
+ </Text>
24
+ </View>
25
+ );
26
+ });
@@ -0,0 +1,60 @@
1
+ import { Image } from 'expo-image';
2
+ import { memo } from 'react';
3
+ import { ActivityIndicator, View } from 'react-native';
4
+
5
+ import { Text } from '@/components/ui';
6
+ import { useResponsiveSize } from '@/lib/hooks';
7
+ import { useThemeConfig } from '@/lib/use-theme-config';
8
+
9
+ type ProfileImageProps = {
10
+ imageUrl: string | null;
11
+ hasError: boolean;
12
+ onError: () => void;
13
+ };
14
+
15
+ export const ProfileImage = memo(function ProfileImage({
16
+ imageUrl,
17
+ hasError,
18
+ onError,
19
+ }: ProfileImageProps) {
20
+ const theme = useThemeConfig();
21
+ const isDarkMode = theme.dark;
22
+ const { isTablet } = useResponsiveSize();
23
+ const size = isTablet ? 200 : 150;
24
+
25
+ return (
26
+ <View className="mb-4 mt-16 items-center justify-center">
27
+ <View
28
+ className={`relative overflow-hidden rounded-full border ${isDarkMode ? 'border-white/10' : 'border-black/10'}`}
29
+ style={{ width: size, height: size, borderWidth: 1 }}
30
+ >
31
+ {imageUrl && !hasError ? (
32
+ <Image
33
+ source={{ uri: imageUrl }}
34
+ style={{ width: '100%', height: '100%', borderRadius: size / 2 }}
35
+ contentFit="cover"
36
+ onError={onError}
37
+ transition={200}
38
+ />
39
+ ) : (
40
+ <View
41
+ className={`size-full items-center justify-center rounded-full ${isDarkMode ? 'bg-neutral-700' : 'bg-neutral-200'}`}
42
+ >
43
+ {hasError ? (
44
+ <Text
45
+ className={`px-2 text-center text-sm ${isDarkMode ? 'text-neutral-300' : 'text-neutral-500'}`}
46
+ >
47
+ Unable to load image
48
+ </Text>
49
+ ) : (
50
+ <ActivityIndicator
51
+ size="large"
52
+ color={isDarkMode ? '#60A5FA' : '#3B82F6'}
53
+ />
54
+ )}
55
+ </View>
56
+ )}
57
+ </View>
58
+ </View>
59
+ );
60
+ });