vibefast-cli 1.1.3 → 1.2.1

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 (300) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +63 -169
  3. package/dist/__tests__/recipes.test.js +25 -3
  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 +547 -543
  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 +35 -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 +79 -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/{imageAnalysisFunctions.ts → imageAnalysis.ts} +5 -5
  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 +57 -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 +59 -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 +51 -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/lib/ai/imageAnalysisAdapter.ts +0 -200
  291. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
  292. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
  293. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
  294. package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
  295. package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
  296. package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
  297. package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
  298. /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
  299. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
  300. /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,217 @@
1
+ import { Stack, useRouter } from 'expo-router';
2
+ import React from 'react';
3
+ import {
4
+ ActivityIndicator,
5
+ Alert,
6
+ Platform,
7
+ ScrollView,
8
+ View,
9
+ } from 'react-native';
10
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
11
+
12
+ import {
13
+ Button,
14
+ FocusAwareStatusBar,
15
+ Gallery,
16
+ Pressable,
17
+ Text,
18
+ } from '@/components/ui';
19
+ import type { GalleryItem } from '@/components/ui/gallery';
20
+ import { Trash2 } from '@/components/ui/icons';
21
+ import { ImageDetailModal } from '@/features/image-generator/components/image-detail-modal';
22
+ import { useImageGallery } from '@/features/image-generator/hooks/use-image-gallery';
23
+ import type { SavedImageMetadata } from '@/features/image-generator/services/image-gallery-service';
24
+ import { translate } from '@/lib';
25
+ import { useScreenOptions } from '@/lib/hooks/use-navigation-options';
26
+
27
+ // Set static options to prevent initial flash
28
+ export const unstable_settings = {
29
+ initialRouteName: 'gallery',
30
+ };
31
+
32
+ /**
33
+ * Screen component for displaying the local image gallery
34
+ */
35
+ export default function ImageGalleryScreen() {
36
+ const router = useRouter();
37
+ const insets = useSafeAreaInsets();
38
+ const { images, isLoading, error, removeImage, clearGallery } =
39
+ useImageGallery();
40
+
41
+ const [selectedImage, setSelectedImage] =
42
+ React.useState<SavedImageMetadata | null>(null);
43
+
44
+ const screenOptions = useScreenOptions(
45
+ {
46
+ title: `${translate('image_generator.gallery_title')} (${images.length})`,
47
+ headerRight: () =>
48
+ images.length > 0 ? (
49
+ <Pressable
50
+ onPress={handleClearGallery}
51
+ className="pl-2"
52
+ style={{ marginRight: 8 }}
53
+ testID="clear-gallery-button"
54
+ accessibilityLabel="Clear all images from gallery"
55
+ accessibilityRole="button"
56
+ >
57
+ <Text className="font-medium text-danger-500">
58
+ <Trash2 color="#ef4444" />
59
+ </Text>
60
+ </Pressable>
61
+ ) : null,
62
+ },
63
+ 'scrollable',
64
+ );
65
+
66
+ const handleImagePress = (item: GalleryItem) => {
67
+ const image = images.find((img) => img.id === item.id);
68
+ if (image) {
69
+ setSelectedImage(image);
70
+ }
71
+ };
72
+
73
+ const handleImageLongPress = (item: GalleryItem) => {
74
+ const image = images.find((img) => img.id === item.id);
75
+ if (!image) return;
76
+
77
+ Alert.alert(
78
+ translate('image_generator.image_options'),
79
+ `${translate('image_generator.image_options_description')}\n\nPrompt: ${image.prompt.substring(0, 100)}...\n\nModel:${image.model}`,
80
+ [
81
+ { text: translate('common.cancel'), style: 'cancel' },
82
+ {
83
+ text: translate('common.delete'),
84
+ style: 'destructive',
85
+ onPress: () => {
86
+ Alert.alert(
87
+ translate('image_detail.delete_image'),
88
+ translate('image_detail.delete_image_confirmation'),
89
+ [
90
+ { text: translate('common.cancel'), style: 'cancel' },
91
+ {
92
+ text: translate('common.delete'),
93
+ style: 'destructive',
94
+ onPress: () => removeImage(image.id),
95
+ },
96
+ ],
97
+ );
98
+ },
99
+ },
100
+ ],
101
+ );
102
+ };
103
+
104
+ // Convert SavedImageMetadata to GalleryItem format
105
+ const galleryItems: GalleryItem[] = images.map((img) => ({
106
+ id: img.id,
107
+ uri: img.localUri,
108
+ title: img.prompt,
109
+ }));
110
+
111
+ const handleCloseModal = () => {
112
+ setSelectedImage(null);
113
+ };
114
+
115
+ const handleDeleteFromModal = (imageId: string) => {
116
+ removeImage(imageId);
117
+ };
118
+
119
+ const handleClearGallery = () => {
120
+ if (images.length === 0) return;
121
+
122
+ Alert.alert(
123
+ translate('image_generator.clear_gallery'),
124
+ translate('image_generator.clear_gallery_confirmation'),
125
+ [
126
+ { text: translate('common.cancel'), style: 'cancel' },
127
+ {
128
+ text: translate('image_generator.clear_all'),
129
+ style: 'destructive',
130
+ onPress: clearGallery,
131
+ },
132
+ ],
133
+ );
134
+ };
135
+
136
+ if (isLoading) {
137
+ return (
138
+ <View className="flex-1 items-center justify-center bg-white dark:bg-neutral-950">
139
+ <FocusAwareStatusBar />
140
+ <ActivityIndicator size="large" />
141
+ <Text className="mt-4 text-neutral-900 dark:text-neutral-100">
142
+ {translate('common.loading')}...
143
+ </Text>
144
+ </View>
145
+ );
146
+ }
147
+
148
+ if (error) {
149
+ return (
150
+ <View className="flex-1 items-center justify-center bg-white p-4 dark:bg-neutral-950">
151
+ <FocusAwareStatusBar />
152
+ <Text className="mb-4 text-center text-neutral-900 dark:text-neutral-100">
153
+ {translate('common.error')}: {error}
154
+ </Text>
155
+ <Button
156
+ onPress={() => router.back()}
157
+ label={translate('image_generator.go_back')}
158
+ />
159
+ </View>
160
+ );
161
+ }
162
+
163
+ return (
164
+ <View className="flex-1">
165
+ <FocusAwareStatusBar />
166
+ <Stack.Screen
167
+ options={{
168
+ ...screenOptions,
169
+ title: `${translate('image_generator.gallery_title')} (${images.length})`,
170
+ }}
171
+ />
172
+
173
+ {images.length === 0 ? (
174
+ <View
175
+ className="flex-1 items-center justify-center p-8"
176
+ style={{ paddingBottom: insets.bottom + 32 }}
177
+ >
178
+ <Text className="mb-4 text-center text-xl font-semibold text-neutral-900 dark:text-neutral-100">
179
+ {translate('image_generator.no_images_title')}
180
+ </Text>
181
+ <Text className="mb-6 text-center text-neutral-500 dark:text-neutral-400">
182
+ {translate('image_generator.no_images_description')}
183
+ </Text>
184
+ <Button
185
+ onPress={() => router.back()}
186
+ label={translate('image_generator.generate_images_button')}
187
+ />
188
+ </View>
189
+ ) : (
190
+ <ScrollView
191
+ contentContainerStyle={{
192
+ padding: 16,
193
+ paddingTop: Platform.OS === 'ios' ? 84 : 16,
194
+ paddingBottom: insets.bottom + 16,
195
+ }}
196
+ >
197
+ <Gallery
198
+ items={galleryItems}
199
+ columns={2}
200
+ columnsTablet={4}
201
+ spacing={8}
202
+ borderRadius={12}
203
+ aspectRatio={1}
204
+ onPressItem={handleImagePress}
205
+ onLongPressItem={handleImageLongPress}
206
+ />
207
+ </ScrollView>
208
+ )}
209
+
210
+ <ImageDetailModal
211
+ image={selectedImage}
212
+ onDelete={handleDeleteFromModal}
213
+ onClose={handleCloseModal}
214
+ />
215
+ </View>
216
+ );
217
+ }
@@ -0,0 +1,251 @@
1
+ import { MaterialIcons } from '@expo/vector-icons';
2
+ import { router, Stack } from 'expo-router';
3
+ import React, { useState } from 'react';
4
+ import {
5
+ Keyboard,
6
+ ScrollView,
7
+ TouchableWithoutFeedback,
8
+ View,
9
+ } from 'react-native';
10
+
11
+ import { reportingApi } from '@/api-client/reporting';
12
+ import {
13
+ Button,
14
+ FocusAwareStatusBar,
15
+ Image,
16
+ Input,
17
+ Pressable,
18
+ Text,
19
+ } from '@/components/ui';
20
+ import { ReportContentModal } from '@/features/chatbot/components/report-content-modal';
21
+ import type { ReportReason } from '@/features/chatbot/constants/report-reasons';
22
+ import { ImageModelSelector } from '@/features/image-generator/components/image-model-selector';
23
+ import { ImagePlaceholder } from '@/features/image-generator/components/image-placeholder';
24
+ import { useImageGenerator } from '@/features/image-generator/hooks/use-image-generator';
25
+ import { useImageGeneratorSettings } from '@/features/image-generator/hooks/use-image-generator-settings';
26
+ import { ImageSaveService } from '@/features/image-generator/services/image-save-service';
27
+ import { translate } from '@/lib';
28
+ import { useThemeConfig } from '@/lib/use-theme-config';
29
+
30
+ export default function ImageGeneratorScreen() {
31
+ const {
32
+ settings,
33
+ isLoaded: isSettingsLoaded,
34
+ updateModel,
35
+ } = useImageGeneratorSettings();
36
+
37
+ const {
38
+ prompt,
39
+ setPrompt,
40
+ isLoading,
41
+ generatedImageData,
42
+ generatedImageUri,
43
+ handleGenerateImage,
44
+ } = useImageGenerator({
45
+ settings,
46
+ isSettingsLoaded,
47
+ });
48
+
49
+ const [isReportModalVisible, setIsReportModalVisible] = useState(false);
50
+ const theme = useThemeConfig();
51
+ // Use a ref to store a stable ID for the report modal
52
+ const reportIdRef = React.useRef<string>(`generated_image_${Date.now()}`);
53
+
54
+ const reportImageMutation = reportingApi.useReportAIGeneratedImage();
55
+
56
+ const handleSaveImage = async () => {
57
+ if (!generatedImageData) {
58
+ return;
59
+ }
60
+ await ImageSaveService.saveImageToGallery(generatedImageData.imageUri, {
61
+ prompt: generatedImageData.prompt,
62
+ provider: generatedImageData.provider,
63
+ model: generatedImageData.model,
64
+ remoteImageId: generatedImageData.imageId,
65
+ storageId: generatedImageData.storageId,
66
+ });
67
+ };
68
+
69
+ const handleReportImage = () => {
70
+ setIsReportModalVisible(true);
71
+ };
72
+
73
+ const handleReportSubmit = async (reportData: {
74
+ reason: ReportReason;
75
+ details?: string;
76
+ }) => {
77
+ if (!generatedImageData) {
78
+ return false;
79
+ }
80
+
81
+ try {
82
+ await reportImageMutation({
83
+ prompt: generatedImageData.prompt,
84
+ provider: generatedImageData.provider,
85
+ model: generatedImageData.model,
86
+ reason: reportData.reason,
87
+ details: reportData.details,
88
+ imageId: generatedImageData.imageId,
89
+ storageId: generatedImageData.storageId,
90
+ });
91
+
92
+ setIsReportModalVisible(false);
93
+ return true;
94
+ } catch (error) {
95
+ console.error('Failed to report image:', error);
96
+ return false;
97
+ }
98
+ };
99
+
100
+ const handleReportCancel = () => {
101
+ setIsReportModalVisible(false);
102
+ };
103
+
104
+ const isGenerateDisabled = isLoading || !prompt.trim();
105
+
106
+ // Create a mock message object for the ReportContentModal
107
+ const mockMessage = React.useMemo(() => {
108
+ const result =
109
+ isReportModalVisible && generatedImageData
110
+ ? {
111
+ id: generatedImageData.imageId ?? reportIdRef.current,
112
+ content: `Generated image with prompt: ${generatedImageData.prompt}`,
113
+ role: 'assistant' as const,
114
+ createdAt: new Date(),
115
+ }
116
+ : null;
117
+
118
+ console.log('🚩 [ImageGenerator] mockMessage created:', {
119
+ id: result?.id,
120
+ hasContent: !!result?.content,
121
+ isVisible: isReportModalVisible,
122
+ hasImageData: !!generatedImageData,
123
+ });
124
+
125
+ return result;
126
+ }, [isReportModalVisible, generatedImageData]);
127
+
128
+ const galleryButton = (
129
+ <Pressable
130
+ onPress={() => router.push('/image-generator/gallery')}
131
+ className="pl-2"
132
+ style={{ marginRight: 8 }}
133
+ testID="gallery-button"
134
+ accessibilityLabel="Open image gallery"
135
+ accessibilityRole="button"
136
+ >
137
+ <MaterialIcons
138
+ name="photo-library"
139
+ size={22}
140
+ color={theme.colors.foreground}
141
+ />
142
+ </Pressable>
143
+ );
144
+
145
+ return (
146
+ <>
147
+ <FocusAwareStatusBar />
148
+ <Stack.Screen
149
+ options={{
150
+ title: translate('image_generator.title'),
151
+ headerRight: () => galleryButton,
152
+ }}
153
+ />
154
+ <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
155
+ <View className="flex-1 px-4 pt-4">
156
+ <ScrollView
157
+ keyboardShouldPersistTaps="handled"
158
+ showsVerticalScrollIndicator={false}
159
+ automaticallyAdjustKeyboardInsets={true}
160
+ >
161
+ {/* Image Display Area */}
162
+ <View className="flex-1">
163
+ <View className="mb-4 flex-row items-center justify-between">
164
+ <Text className="text-lg font-semibold text-neutral-900 dark:text-neutral-100">
165
+ Generated Image
166
+ </Text>
167
+ <ImageModelSelector
168
+ testID="image-model-selector-inline"
169
+ settings={settings}
170
+ onModelChange={updateModel}
171
+ />
172
+ </View>
173
+ {generatedImageUri ? (
174
+ <Image
175
+ source={{ uri: generatedImageUri }}
176
+ className="aspect-square w-full rounded-lg"
177
+ accessibilityRole="image"
178
+ accessibilityLabel="Generated AI image"
179
+ testID="generated-image"
180
+ contentFit="contain"
181
+ />
182
+ ) : (
183
+ <ImagePlaceholder />
184
+ )}
185
+ </View>
186
+
187
+ {/* Input and Generate Section */}
188
+ <View className="gap-2 pt-8">
189
+ <View>
190
+ <Text className="mb-2 text-base font-semibold text-neutral-900 dark:text-neutral-100">
191
+ Image Prompt
192
+ </Text>
193
+ <Input
194
+ value={prompt}
195
+ onChangeText={setPrompt}
196
+ placeholder="Describe the image you want to generate..."
197
+ multiline
198
+ numberOfLines={3}
199
+ textAlignVertical="top"
200
+ testID="prompt-input"
201
+ editable={!isLoading}
202
+ accessibilityLabel="Image generation prompt input"
203
+ accessibilityHint="Enter a description for the AI to generate an image"
204
+ />
205
+ </View>
206
+
207
+ <Button
208
+ onPress={handleGenerateImage}
209
+ disabled={isGenerateDisabled}
210
+ testID="generate-button"
211
+ accessibilityLabel="Generate image"
212
+ accessibilityHint="Generate an AI image based on your prompt"
213
+ accessibilityState={{ disabled: isGenerateDisabled }}
214
+ label={isLoading ? 'Generating...' : 'Generate Image'}
215
+ loading={isLoading}
216
+ />
217
+
218
+ {/* Action buttons for generated image */}
219
+ {generatedImageUri && !isLoading && (
220
+ <View className="flex-row gap-2">
221
+ <Button
222
+ onPress={handleSaveImage}
223
+ variant="outline"
224
+ testID="save-gallery-button"
225
+ className="flex-1"
226
+ label="Save to Gallery"
227
+ />
228
+ <Button
229
+ onPress={handleReportImage}
230
+ variant="outline"
231
+ testID="report-image-button"
232
+ className="flex-1"
233
+ label="Report Image"
234
+ />
235
+ </View>
236
+ )}
237
+ </View>
238
+ </ScrollView>
239
+ <ReportContentModal
240
+ message={mockMessage}
241
+ onSubmit={async (reportData) => {
242
+ const success = await handleReportSubmit(reportData);
243
+ return success;
244
+ }}
245
+ onCancel={handleReportCancel}
246
+ />
247
+ </View>
248
+ </TouchableWithoutFeedback>
249
+ </>
250
+ );
251
+ }
@@ -0,0 +1,25 @@
1
+ import type React from 'react';
2
+
3
+ import { Image, Pressable } from '@/components/ui';
4
+ import type { SavedImageMetadata } from '@/features/image-generator/services/image-gallery-service';
5
+
6
+ export const GalleryImageItem: React.FC<{
7
+ image: SavedImageMetadata;
8
+ onPress(): void;
9
+ onLongPress(): void;
10
+ }> = ({ image, onPress, onLongPress }) => (
11
+ <Pressable
12
+ onPress={onPress}
13
+ onLongPress={onLongPress}
14
+ className="w-full"
15
+ testID={`gallery-image-${image.id}`}
16
+ accessibilityRole="button"
17
+ accessibilityLabel={`Generated image from ${image.provider} using ${image.model}`}
18
+ >
19
+ <Image
20
+ source={{ uri: image.localUri }}
21
+ className="aspect-square w-full rounded-lg"
22
+ contentFit="cover"
23
+ />
24
+ </Pressable>
25
+ );
@@ -0,0 +1,215 @@
1
+ import { Ionicons } from '@expo/vector-icons';
2
+ import React, { useMemo, useState } from 'react';
3
+ import { Alert, ScrollView, View } from 'react-native';
4
+
5
+ import { reportingApi } from '@/api-client/reporting';
6
+ import { Button, Image, Modal, Text, useModal } from '@/components/ui';
7
+ import { ReportContentModal } from '@/features/chatbot/components/report-content-modal';
8
+ import type { ReportReason } from '@/features/chatbot/constants/report-reasons';
9
+ import type { AppMessage } from '@/features/chatbot/types';
10
+ import type { SavedImageMetadata } from '@/features/image-generator/services/image-gallery-service';
11
+
12
+ type ImageDetailModalProps = {
13
+ image: SavedImageMetadata | null;
14
+ onDelete: (imageId: string) => void;
15
+ onClose: () => void;
16
+ };
17
+
18
+ /**
19
+ * Modal component for displaying detailed image information
20
+ */
21
+ export const ImageDetailModal: React.FC<ImageDetailModalProps> = ({
22
+ image,
23
+ onDelete,
24
+ onClose,
25
+ }) => {
26
+ const { ref, dismiss } = useModal();
27
+ const [isReportModalVisible, setIsReportModalVisible] = useState(false);
28
+
29
+ const reportImageMutation = reportingApi.useReportAIGeneratedImage();
30
+
31
+ React.useEffect(() => {
32
+ if (image) {
33
+ ref.current?.present();
34
+ } else {
35
+ ref.current?.dismiss();
36
+ }
37
+ }, [image, ref]);
38
+
39
+ const handleDelete = () => {
40
+ if (!image) return;
41
+
42
+ Alert.alert('Delete Image', 'Are you sure you want to delete this image?', [
43
+ { text: 'Cancel', style: 'cancel' },
44
+ {
45
+ text: 'Delete',
46
+ style: 'destructive',
47
+ onPress: () => {
48
+ onDelete(image.id);
49
+ dismiss();
50
+ onClose();
51
+ },
52
+ },
53
+ ]);
54
+ };
55
+
56
+ const handleReport = () => {
57
+ console.log('🚩 [ImageDetailModal] Report button pressed!');
58
+ setIsReportModalVisible(true);
59
+ };
60
+
61
+ const handleReportSubmit = async (reportData: {
62
+ reason: ReportReason;
63
+ details?: string;
64
+ }) => {
65
+ console.log('🚩 [ImageDetailModal] Report submit called with:', reportData);
66
+ if (!image) {
67
+ console.log('🚩 [ImageDetailModal] No image available for reporting');
68
+ return false;
69
+ }
70
+
71
+ try {
72
+ console.log('🚩 [ImageDetailModal] Submitting report for image:', {
73
+ prompt: image.prompt,
74
+ provider: image.provider,
75
+ model: image.model,
76
+ });
77
+
78
+ await reportImageMutation({
79
+ prompt: image.prompt,
80
+ provider: image.provider,
81
+ model: image.model,
82
+ reason: reportData.reason,
83
+ details: reportData.details,
84
+ imageId: image.remoteImageId,
85
+ storageId: image.storageId,
86
+ });
87
+
88
+ console.log('🚩 [ImageDetailModal] Report submitted successfully');
89
+ setIsReportModalVisible(false);
90
+ Alert.alert(
91
+ 'Report Submitted',
92
+ 'Thank you for your report. It has been submitted for review.',
93
+ );
94
+ return true;
95
+ } catch (error) {
96
+ console.error('🚩 [ImageDetailModal] Failed to report image:', error);
97
+ return false;
98
+ }
99
+ };
100
+
101
+ const handleReportCancel = () => {
102
+ console.log('🚩 [ImageDetailModal] Report cancelled');
103
+ setIsReportModalVisible(false);
104
+ };
105
+
106
+ const handleModalDismiss = () => {
107
+ onClose();
108
+ };
109
+
110
+ const formatDate = (timestamp: number): string => {
111
+ return new Date(timestamp).toLocaleString();
112
+ };
113
+
114
+ const reportMessage = useMemo<AppMessage | null>(() => {
115
+ if (!isReportModalVisible || !image) {
116
+ return null;
117
+ }
118
+ return {
119
+ id: image.remoteImageId ?? `gallery_image_${image.id}`,
120
+ role: 'assistant',
121
+ content: `Image generated with prompt: ${image.prompt}`,
122
+ createdAt: new Date(image.savedAt),
123
+ };
124
+ }, [image, isReportModalVisible]);
125
+
126
+ if (!image) return null;
127
+
128
+ return (
129
+ <>
130
+ <Modal
131
+ ref={ref}
132
+ snapPoints={['90%']}
133
+ title="Image Details"
134
+ onDismiss={handleModalDismiss}
135
+ >
136
+ <ScrollView className="flex-1 px-4 pb-4">
137
+ <View className="mb-6">
138
+ <View className="aspect-square w-full overflow-hidden rounded-lg bg-neutral-100 dark:bg-neutral-800">
139
+ <Image
140
+ source={{ uri: image.localUri }}
141
+ className="size-full"
142
+ contentFit="contain"
143
+ />
144
+ </View>
145
+ </View>
146
+
147
+ <View className="mb-4">
148
+ <Text className="mb-2 text-lg font-semibold text-neutral-900 dark:text-neutral-100">
149
+ Prompt
150
+ </Text>
151
+ <Text className="text-base text-neutral-700 dark:text-neutral-300">
152
+ {image.prompt}
153
+ </Text>
154
+ </View>
155
+
156
+ <View className="mb-4">
157
+ <Text className="mb-2 text-lg font-semibold text-neutral-900 dark:text-neutral-100">
158
+ Details
159
+ </Text>
160
+ <View className="gap-2">
161
+ <View className="flex-row justify-between">
162
+ <Text className="text-neutral-600 dark:text-neutral-400">
163
+ Provider:
164
+ </Text>
165
+ <Text className="font-medium text-neutral-900 dark:text-neutral-100">
166
+ {image.provider}
167
+ </Text>
168
+ </View>
169
+ <View className="flex-row justify-between">
170
+ <Text className="text-neutral-600 dark:text-neutral-400">
171
+ Model:
172
+ </Text>
173
+ <Text className="font-medium text-neutral-900 dark:text-neutral-100">
174
+ {image.model}
175
+ </Text>
176
+ </View>
177
+ <View className="flex-row justify-between">
178
+ <Text className="text-neutral-600 dark:text-neutral-400">
179
+ Created:
180
+ </Text>
181
+ <Text className="font-medium text-neutral-900 dark:text-neutral-100">
182
+ {formatDate(image.savedAt)}
183
+ </Text>
184
+ </View>
185
+ </View>
186
+ </View>
187
+
188
+ <View className="gap-3 pt-4">
189
+ <Button
190
+ variant="outline"
191
+ label="Report Image"
192
+ leftIcon={<Ionicons name="flag" size={16} color="#000000" />}
193
+ onPress={handleReport}
194
+ testID="report-image-button"
195
+ accessibilityLabel="Report this image for inappropriate content"
196
+ />
197
+ <Button
198
+ variant="destructive"
199
+ label="Delete Image"
200
+ onPress={handleDelete}
201
+ testID="delete-image-button"
202
+ accessibilityLabel="Delete this image"
203
+ />
204
+ </View>
205
+ </ScrollView>
206
+ </Modal>
207
+
208
+ <ReportContentModal
209
+ message={reportMessage}
210
+ onSubmit={handleReportSubmit}
211
+ onCancel={handleReportCancel}
212
+ />
213
+ </>
214
+ );
215
+ };