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,208 @@
1
+ import * as FileSystem from 'expo-file-system';
2
+ import * as ImageManipulator from 'expo-image-manipulator';
3
+ import { useCallback } from 'react';
4
+ import { create } from 'zustand';
5
+
6
+ import {
7
+ useAnalyzeImages as useAnalyzeImageMutation,
8
+ useImageAnalysis as useImageAnalysisQuery,
9
+ useUserImageAnalyses,
10
+ } from '@/api-client/supabase/image-analyzer';
11
+
12
+ // Temporary store for camera capture data to avoid route parameter issues
13
+ type CameraCaptureStore = {
14
+ capturedImageUri: string | null;
15
+ stepId: string | null;
16
+ setCapturedImage: (uri: string, stepId: string) => void;
17
+ clearCapturedImage: () => void;
18
+ };
19
+
20
+ export const useCameraCaptureStore = create<CameraCaptureStore>((set) => ({
21
+ capturedImageUri: null,
22
+ stepId: null,
23
+ setCapturedImage: (uri: string, stepId: string) =>
24
+ set({ capturedImageUri: uri, stepId }),
25
+ clearCapturedImage: () => set({ capturedImageUri: null, stepId: null }),
26
+ }));
27
+
28
+ export type AnalyzeImagesParams = {
29
+ imageUris: string[];
30
+ preferences: {
31
+ goal: string;
32
+ feedbackStyle: string;
33
+ };
34
+ onProgress?: (step: string, progress: number) => void;
35
+ };
36
+
37
+ export type CreateImageAnalysisParams = {
38
+ imageUris: string[];
39
+ preferences: {
40
+ analysisGoal: string;
41
+ feedbackStyle: string;
42
+ };
43
+ onProgress?: (step: string, progress: number) => void;
44
+ };
45
+
46
+ /**
47
+ * Hook for analyzing images using the Supabase Edge Function.
48
+ */
49
+ export function useAnalyzeImages() {
50
+ const analyzeImageMutation = useAnalyzeImageMutation();
51
+
52
+ return useCallback(
53
+ async (params: AnalyzeImagesParams): Promise<string | null> => {
54
+ const { imageUris, preferences, onProgress } = params;
55
+
56
+ try {
57
+ onProgress?.('Optimizing images...', 15);
58
+
59
+ // Resize & compress images to reduce memory (max width 1024, quality 0.7)
60
+ const optimizedUris = await Promise.all(
61
+ imageUris.map(async (uri) => {
62
+ try {
63
+ const { uri: optimizedUri } =
64
+ await ImageManipulator.manipulateAsync(
65
+ uri,
66
+ [{ resize: { width: 1024 } }],
67
+ {
68
+ compress: 0.7,
69
+ format: ImageManipulator.SaveFormat.JPEG,
70
+ base64: false,
71
+ },
72
+ );
73
+ return optimizedUri;
74
+ } catch (err) {
75
+ console.warn('Image optimization failed, using original', err);
76
+ return uri;
77
+ }
78
+ }),
79
+ );
80
+
81
+ onProgress?.('Converting images...', 20);
82
+
83
+ // Convert optimized URIs to base64
84
+ const base64Images = await Promise.all(
85
+ optimizedUris.map((uri) =>
86
+ FileSystem.readAsStringAsync(uri, { encoding: 'base64' }),
87
+ ),
88
+ );
89
+
90
+ onProgress?.('Starting analysis...', 40);
91
+
92
+ // Call the analyze image mutation
93
+ const result = await analyzeImageMutation.mutateAsync({
94
+ imagesAsBase64: base64Images,
95
+ preferences,
96
+ });
97
+
98
+ onProgress?.('Analysis in progress...', 50);
99
+
100
+ return result.analysisId;
101
+ } catch (error) {
102
+ console.error('Error starting image analysis:', error);
103
+ return null;
104
+ }
105
+ },
106
+ [analyzeImageMutation],
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Hook for creating and managing image analyses (legacy - for backward compatibility).
112
+ */
113
+ export function useCreateImageAnalysis() {
114
+ const analyzeImageMutation = useAnalyzeImageMutation();
115
+
116
+ return useCallback(
117
+ async (params: CreateImageAnalysisParams): Promise<string | null> => {
118
+ const { imageUris, preferences, onProgress } = params;
119
+
120
+ try {
121
+ onProgress?.('Optimizing images...', 15);
122
+
123
+ // Resize & compress images to reduce memory (max width 1024, quality 0.7)
124
+ const optimizedUris = await Promise.all(
125
+ imageUris.map(async (uri) => {
126
+ try {
127
+ const { uri: optimizedUri } =
128
+ await ImageManipulator.manipulateAsync(
129
+ uri,
130
+ [{ resize: { width: 1024 } }],
131
+ {
132
+ compress: 0.7,
133
+ format: ImageManipulator.SaveFormat.JPEG,
134
+ base64: false,
135
+ },
136
+ );
137
+ return optimizedUri;
138
+ } catch (err) {
139
+ console.warn('Image optimization failed, using original', err);
140
+ return uri;
141
+ }
142
+ }),
143
+ );
144
+
145
+ onProgress?.('Converting images...', 20);
146
+
147
+ // Convert optimized URIs to base64
148
+ const base64Images = await Promise.all(
149
+ optimizedUris.map((uri) =>
150
+ FileSystem.readAsStringAsync(uri, { encoding: 'base64' }),
151
+ ),
152
+ );
153
+
154
+ onProgress?.('Starting analysis...', 40);
155
+
156
+ // Create the analysis with uploaded images
157
+ const result = await analyzeImageMutation.mutateAsync({
158
+ imagesAsBase64: base64Images,
159
+ preferences: {
160
+ goal: preferences.analysisGoal,
161
+ feedbackStyle: preferences.feedbackStyle,
162
+ },
163
+ });
164
+
165
+ onProgress?.('Analysis in progress...', 50);
166
+
167
+ return result.analysisId;
168
+ } catch (error) {
169
+ console.error('Error creating image analysis:', error);
170
+ return null;
171
+ }
172
+ },
173
+ [analyzeImageMutation],
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Hook for polling analysis status by ID.
179
+ */
180
+ export function useAnalysisStatus(analysisId?: string) {
181
+ const { data } = useImageAnalysisQuery(analysisId ?? null);
182
+ return data;
183
+ }
184
+
185
+ /**
186
+ * Hook for fetching an image analysis by ID.
187
+ */
188
+ export function useImageAnalysis(analysisId?: string) {
189
+ const { data } = useImageAnalysisQuery(analysisId ?? null);
190
+ return data;
191
+ }
192
+
193
+ /**
194
+ * Hook for fetching user's image analysis history with optional pagination.
195
+ */
196
+ export function useImageAnalysisHistory(paginationOpts?: {
197
+ limit: number;
198
+ offset: number;
199
+ }) {
200
+ const { data } = useUserImageAnalyses({ limit: paginationOpts?.limit ?? 50 });
201
+ const pages = data?.pages ?? [];
202
+ const allAnalyses = pages.flatMap((page) => page?.analyses ?? []);
203
+ return {
204
+ page: allAnalyses,
205
+ isDone: allAnalyses.length < (paginationOpts?.limit ?? 50),
206
+ continueCursor: null,
207
+ };
208
+ }
@@ -0,0 +1,262 @@
1
+ export type AnalysisScores = {
2
+ overall: number;
3
+ [key: string]: number;
4
+ };
5
+
6
+ export type TraitAnalysis = {
7
+ score: number;
8
+ feedback: string;
9
+ strengths?: string[];
10
+ improvements?: string[];
11
+ // Legacy support for old format
12
+ Score?: number;
13
+ Feedback?: string;
14
+ };
15
+
16
+ // Generic trait scores - can work with any analysis type
17
+ export type GenericTraitScores = Record<string, TraitAnalysis>;
18
+
19
+ export type LocalAnalysis = {
20
+ id: string;
21
+ localImageUri?: string;
22
+ aiResults?: {
23
+ overallScore?: number;
24
+ overallMessage?: string;
25
+ confidence?: number;
26
+ traitScores?: GenericTraitScores; // Now generic instead of hard-coded
27
+ recommendations?: string[];
28
+ analysisType?: string;
29
+ processingTime?: number;
30
+ // Legacy support for old format
31
+ analysis?: string; // Legacy field name for overallMessage
32
+ [key: string]: any;
33
+ };
34
+ };
35
+
36
+ /**
37
+ * Extract scores from any analysis type (completely dynamic)
38
+ */
39
+ export function extractScoresFromAnalysis(
40
+ analysis: LocalAnalysis['aiResults'],
41
+ ): AnalysisScores | null {
42
+ if (!analysis) return null;
43
+
44
+ const scores: AnalysisScores = {
45
+ overall: analysis.overallScore || 0,
46
+ };
47
+
48
+ // Handle structured trait scores (new format)
49
+ if (analysis.traitScores && typeof analysis.traitScores === 'object') {
50
+ Object.entries(analysis.traitScores).forEach(([key, trait]) => {
51
+ if (trait && typeof trait === 'object') {
52
+ // Support both new format (score) and legacy format (Score)
53
+ const score = (trait as TraitAnalysis).score || (trait as any).Score;
54
+ if (typeof score === 'number') {
55
+ scores[key] = score;
56
+ }
57
+ }
58
+ });
59
+ }
60
+
61
+ // Handle legacy flat format (for backward compatibility)
62
+ Object.entries(analysis).forEach(([key, value]) => {
63
+ if (
64
+ key !== 'overallScore' &&
65
+ key !== 'overallMessage' &&
66
+ key !== 'confidence' &&
67
+ key !== 'traitScores' &&
68
+ key !== 'recommendations' &&
69
+ key !== 'analysisType' &&
70
+ key !== 'processingTime'
71
+ ) {
72
+ if (value && typeof value === 'object' && 'Score' in value) {
73
+ scores[key] = (value as any).Score;
74
+ } else if (typeof value === 'number') {
75
+ scores[key] = value;
76
+ }
77
+ }
78
+ });
79
+
80
+ return scores;
81
+ }
82
+
83
+ /**
84
+ * Process image URL from parameters with priority for multiple images
85
+ */
86
+ export function processImageUrl(
87
+ localImageUriParam: string | null,
88
+ localImageUrisParam: string | null,
89
+ fallbackImageUri: string | null,
90
+ ): string | null {
91
+ // Priority 1: Multiple images from localImageUris parameter
92
+ if (localImageUrisParam) {
93
+ try {
94
+ const parsedUris = JSON.parse(localImageUrisParam);
95
+ if (Array.isArray(parsedUris) && parsedUris.length > 0) {
96
+ return parsedUris[0]; // Return first image
97
+ }
98
+ } catch (error) {
99
+ console.warn('Failed to parse localImageUris:', error);
100
+ }
101
+ }
102
+
103
+ // Priority 2: Single image from localImageUri parameter
104
+ if (localImageUriParam) {
105
+ return localImageUriParam;
106
+ }
107
+
108
+ // Priority 3: Fallback image URI
109
+ return fallbackImageUri;
110
+ }
111
+
112
+ /**
113
+ * Format trait name for display (completely generic)
114
+ */
115
+ export function formatTraitName(traitKey: string): string {
116
+ // Convert camelCase to Title Case
117
+ return traitKey
118
+ .replace(/([A-Z])/g, ' $1')
119
+ .replace(/^./, (str) => str.toUpperCase())
120
+ .trim();
121
+ }
122
+
123
+ /**
124
+ * Get trait details for any analysis type (dynamic)
125
+ */
126
+ export function getTraitDetails(
127
+ traitKey: string,
128
+ traitScores: GenericTraitScores | undefined,
129
+ ): {
130
+ score: number;
131
+ feedback: string;
132
+ strengths: string[];
133
+ improvements: string[];
134
+ } | null {
135
+ if (!traitScores || !traitScores[traitKey]) {
136
+ return null;
137
+ }
138
+
139
+ const trait = traitScores[traitKey];
140
+
141
+ return {
142
+ score: trait.score || trait.Score || 0,
143
+ feedback: trait.feedback || trait.Feedback || 'No feedback available',
144
+ strengths: trait.strengths || [],
145
+ improvements: trait.improvements || [],
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Generic achievement system that works with any analysis type
151
+ */
152
+ export type Achievement = {
153
+ id: string;
154
+ title: string;
155
+ description: string;
156
+ icon: string;
157
+ color: string;
158
+ points: number;
159
+ condition: (scores: AnalysisScores) => boolean;
160
+ };
161
+
162
+ const GENERIC_ACHIEVEMENTS: Achievement[] = [
163
+ {
164
+ id: 'excellentScore',
165
+ title: 'Excellence',
166
+ description: 'Achieved an excellent overall score',
167
+ icon: 'trophy-outline',
168
+ color: '#FFD700',
169
+ points: 50,
170
+ condition: (scores) => scores.overall >= 90,
171
+ },
172
+ {
173
+ id: 'goodAnalysis',
174
+ title: 'Good Analysis',
175
+ description: 'Scored well in the analysis',
176
+ icon: 'thumbs-up-outline',
177
+ color: '#4CAF50',
178
+ points: 30,
179
+ condition: (scores) => scores.overall >= 75,
180
+ },
181
+ {
182
+ id: 'balanced',
183
+ title: 'Well Balanced',
184
+ description: 'Consistent scores across all traits',
185
+ icon: 'scale-outline',
186
+ color: '#2196F3',
187
+ points: 25,
188
+ condition: (scores) => {
189
+ const traitScores = Object.entries(scores)
190
+ .filter(([key]) => key !== 'overall')
191
+ .map(([, score]) => score);
192
+ if (traitScores.length < 2) return false;
193
+ const avg = traitScores.reduce((a, b) => a + b, 0) / traitScores.length;
194
+ const variance =
195
+ traitScores.reduce((acc, score) => acc + (score - avg) ** 2, 0) /
196
+ traitScores.length;
197
+ return variance < 100; // Low variance means balanced
198
+ },
199
+ },
200
+ {
201
+ id: 'highConfidence',
202
+ title: 'Clear Assessment',
203
+ description: 'Analysis completed with high confidence',
204
+ icon: 'eye-outline',
205
+ color: '#9C27B0',
206
+ points: 20,
207
+ condition: () => true, // We don't have confidence in scores, so always true
208
+ },
209
+ ];
210
+
211
+ /**
212
+ * Get unlocked achievements based on analysis scores
213
+ */
214
+ export function getUnlockedAchievements(scores: AnalysisScores): Achievement[] {
215
+ return GENERIC_ACHIEVEMENTS.filter((achievement) =>
216
+ achievement.condition(scores),
217
+ );
218
+ }
219
+
220
+ /**
221
+ * Creates a mock analysis for fallback (generic)
222
+ */
223
+ export function createMockAnalysis(analysisId: string): LocalAnalysis {
224
+ return {
225
+ id: analysisId,
226
+ localImageUri:
227
+ 'https://images.unsplash.com/photo-1579933113359-f65a43033118?w=800&q=80',
228
+ aiResults: {
229
+ overallScore: 85,
230
+ overallMessage:
231
+ 'Great analysis results with strong performance across multiple areas.',
232
+ confidence: 0.9,
233
+ traitScores: {
234
+ primaryTrait: {
235
+ score: 90,
236
+ feedback: 'Excellent primary characteristic',
237
+ strengths: ['Strong foundation', 'Good structure'],
238
+ improvements: ['Minor refinements possible'],
239
+ },
240
+ secondaryTrait: {
241
+ score: 80,
242
+ feedback: 'Very good secondary aspect',
243
+ strengths: ['Well-balanced', 'Consistent quality'],
244
+ improvements: ['Some enhancement opportunities'],
245
+ },
246
+ tertiaryTrait: {
247
+ score: 85,
248
+ feedback: 'Strong tertiary element',
249
+ strengths: ['Natural appeal', 'Good proportions'],
250
+ improvements: ['Fine-tuning recommended'],
251
+ },
252
+ },
253
+ recommendations: [
254
+ 'Continue maintaining current standards',
255
+ 'Focus on areas with the most potential',
256
+ 'Consider professional guidance for optimization',
257
+ ],
258
+ analysisType: 'generic_analysis',
259
+ processingTime: 2500,
260
+ },
261
+ };
262
+ }
@@ -0,0 +1,176 @@
1
+ import * as FileSystem from 'expo-file-system';
2
+ import { Alert, Platform, Share } from 'react-native';
3
+
4
+ import type { AnalysisScores, LocalAnalysis } from './analysis-service';
5
+ import { formatTraitName } from './analysis-service';
6
+
7
+ export type ShareableAnalysisData = {
8
+ overallScore: number;
9
+ traitScores: AnalysisScores;
10
+ imageUrl?: string | null;
11
+ analysisType?: string;
12
+ recommendations?: string[];
13
+ overallMessage?: string;
14
+ };
15
+
16
+ /**
17
+ * Service for sharing analysis results
18
+ */
19
+ export class ShareService {
20
+ /**
21
+ * Share analysis results with formatted text
22
+ */
23
+ static async shareAnalysisResults(
24
+ analysis: LocalAnalysis,
25
+ extractedScores: AnalysisScores,
26
+ imageUrl?: string | null,
27
+ ): Promise<void> {
28
+ try {
29
+ const shareData = await ShareService.prepareShareData({
30
+ overallScore: extractedScores.overall,
31
+ traitScores: extractedScores,
32
+ imageUrl,
33
+ analysisType: analysis.aiResults?.analysisType,
34
+ recommendations: analysis.aiResults?.recommendations,
35
+ overallMessage:
36
+ analysis.aiResults?.overallMessage || analysis.aiResults?.analysis, // Handle legacy field name
37
+ });
38
+
39
+ const result = await Share.share(shareData, {
40
+ dialogTitle: 'Share Analysis Results',
41
+ excludedActivityTypes: Platform.select({
42
+ ios: [
43
+ 'com.apple.UIKit.activity.AssignToContact',
44
+ 'com.apple.UIKit.activity.Print',
45
+ ],
46
+ default: undefined,
47
+ }),
48
+ });
49
+
50
+ if (result.action === Share.sharedAction) {
51
+ console.log('Analysis results shared successfully');
52
+ }
53
+ } catch (error) {
54
+ console.error('Error sharing analysis results:', error);
55
+ Alert.alert('Share Failed', 'Unable to share results. Please try again.');
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Prepare shareable data with formatted text
61
+ */
62
+ private static async prepareShareData(data: ShareableAnalysisData): Promise<{
63
+ message: string;
64
+ url?: string;
65
+ }> {
66
+ const formattedText = ShareService.formatAnalysisText(data);
67
+
68
+ const shareData: { message: string; url?: string } = {
69
+ message: formattedText,
70
+ };
71
+
72
+ // Add image if available (for platforms that support it)
73
+ if (data.imageUrl && Platform.OS !== 'web') {
74
+ try {
75
+ // Verify the image is accessible
76
+ const fileInfo = await FileSystem.getInfoAsync(data.imageUrl);
77
+ if (fileInfo.exists) {
78
+ shareData.url = data.imageUrl;
79
+ }
80
+ } catch (error) {
81
+ console.warn('Could not attach image to share:', error);
82
+ // Continue without image
83
+ }
84
+ }
85
+
86
+ return shareData;
87
+ }
88
+
89
+ /**
90
+ * Format analysis results into readable text
91
+ */
92
+ private static formatAnalysisText(data: ShareableAnalysisData): string {
93
+ const { overallScore, traitScores, analysisType, recommendations } = data;
94
+
95
+ let text = '๐Ÿ” My Analysis Results\n\n';
96
+
97
+ // Overall score
98
+ text += `Overall Score: ${overallScore}/100\n`;
99
+ text += `${ShareService.getScoreLevelEmoji(overallScore)} ${ShareService.getScoreLevel(overallScore)}\n\n`;
100
+
101
+ // Overall message/feedback (if available)
102
+ if (data.overallMessage) {
103
+ text += `๐Ÿ’ฌ Overall Assessment:\n${data.overallMessage}\n\n`;
104
+ }
105
+
106
+ // Individual trait scores
107
+ text += '๐Ÿ“Š Trait Breakdown:\n';
108
+ Object.entries(traitScores)
109
+ .filter(([key]) => key !== 'overall')
110
+ .sort(([, a], [, b]) => b - a) // Sort by score descending
111
+ .forEach(([key, score]) => {
112
+ const formattedName = formatTraitName(key);
113
+ text += `โ€ข ${formattedName}: ${score}/100 ${ShareService.getScoreLevelEmoji(score)}\n`;
114
+ });
115
+
116
+ // Recommendations (if available)
117
+ if (recommendations && recommendations.length > 0) {
118
+ text += '\n๐Ÿ’ก Key Recommendations:\n';
119
+ recommendations.slice(0, 3).forEach((rec, index) => {
120
+ text += `${index + 1}. ${rec}\n`;
121
+ });
122
+ }
123
+
124
+ // Analysis type context
125
+ if (analysisType) {
126
+ text += `\nAnalysis Type: ${analysisType}\n`;
127
+ }
128
+
129
+ text += '\nโœจ Get your own analysis with ShipAppsFast!';
130
+
131
+ return text;
132
+ }
133
+
134
+ /**
135
+ * Get score level description
136
+ */
137
+ private static getScoreLevel(score: number): string {
138
+ if (score >= 80) return 'Excellent';
139
+ if (score >= 70) return 'Very Good';
140
+ if (score >= 60) return 'Good';
141
+ if (score >= 50) return 'Fair';
142
+ return 'Needs Improvement';
143
+ }
144
+
145
+ /**
146
+ * Get appropriate emoji for score level
147
+ */
148
+ private static getScoreLevelEmoji(score: number): string {
149
+ if (score >= 80) return '๐ŸŒŸ';
150
+ if (score >= 70) return '๐Ÿ’š';
151
+ if (score >= 60) return '๐Ÿ’›';
152
+ if (score >= 50) return '๐Ÿงก';
153
+ return '๐Ÿ’™';
154
+ }
155
+
156
+ /**
157
+ * Share analysis results as a quick summary
158
+ */
159
+ static async shareQuickSummary(
160
+ overallScore: number,
161
+ topTrait: { name: string; score: number },
162
+ ): Promise<void> {
163
+ const text =
164
+ '๐Ÿ” Just completed my analysis!\n\n' +
165
+ `Overall Score: ${overallScore}/100 ${ShareService.getScoreLevelEmoji(overallScore)}\n` +
166
+ `Top Trait: ${topTrait.name} (${topTrait.score}/100)\n\n` +
167
+ 'โœจ Get your own analysis with ShipAppsFast!';
168
+
169
+ try {
170
+ await Share.share({ message: text });
171
+ } catch (error) {
172
+ console.error('Error sharing quick summary:', error);
173
+ Alert.alert('Share Failed', 'Unable to share. Please try again.');
174
+ }
175
+ }
176
+ }