kasy-cli 1.21.8 → 1.22.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 (269) hide show
  1. package/lib/commands/add.js +93 -80
  2. package/lib/commands/configure.js +100 -32
  3. package/lib/commands/doctor.js +28 -2
  4. package/lib/commands/new.js +86 -38
  5. package/lib/commands/notifications.js +1 -1
  6. package/lib/commands/remove.js +43 -15
  7. package/lib/commands/run.js +2 -2
  8. package/lib/commands/update.js +2 -2
  9. package/lib/scaffold/CHANGELOG.json +14 -0
  10. package/lib/scaffold/backends/api/generator.js +14 -14
  11. package/lib/scaffold/backends/api/patch/README.md +83 -0
  12. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  13. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
  14. package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
  15. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
  16. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
  17. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
  18. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
  19. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
  20. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
  21. package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
  22. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  23. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  24. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
  25. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  26. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
  27. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  28. package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
  29. package/lib/scaffold/backends/firebase/deploy.js +87 -13
  30. package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +14 -6
  31. package/lib/scaffold/backends/firebase/generator.js +5 -5
  32. package/lib/scaffold/backends/firebase/setup-from-scratch.js +69 -45
  33. package/lib/scaffold/backends/firebase/tokens.js +4 -4
  34. package/lib/scaffold/backends/supabase/deploy.js +63 -11
  35. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
  36. package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
  37. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
  38. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
  39. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
  40. package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
  41. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
  42. package/lib/scaffold/backends/supabase/generator.js +17 -17
  43. package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
  44. package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
  45. package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
  46. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
  47. package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
  48. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
  49. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
  50. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  51. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
  52. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
  53. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
  54. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
  55. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  56. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  57. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
  58. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  59. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
  60. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  61. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
  62. package/lib/scaffold/backends/supabase/tokens.js +3 -3
  63. package/lib/scaffold/catalog.js +9 -11
  64. package/lib/scaffold/generate.js +45 -31
  65. package/lib/scaffold/shared/generator-utils.js +188 -81
  66. package/lib/scaffold/shared/sort-imports.js +191 -0
  67. package/lib/scaffold/shared/template-strings.js +3 -3
  68. package/lib/utils/checks.js +2 -2
  69. package/lib/utils/i18n/messages-en.js +50 -35
  70. package/lib/utils/i18n/messages-es.js +50 -35
  71. package/lib/utils/i18n/messages-pt.js +52 -37
  72. package/lib/utils/updates.js +15 -15
  73. package/package.json +1 -1
  74. package/templates/firebase/.env.example +2 -2
  75. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  78. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  79. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  80. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  81. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  82. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  83. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  84. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  85. package/templates/firebase/firestore.rules +24 -5
  86. package/templates/firebase/functions/package-lock.json +22 -1
  87. package/templates/firebase/functions/package.json +2 -1
  88. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  89. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  90. package/templates/firebase/functions/src/index.ts +8 -2
  91. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  92. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  93. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  94. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  95. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  96. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  97. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  98. package/templates/firebase/lib/components/components.dart +4 -1
  99. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  100. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  101. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  102. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  103. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  104. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
  105. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  106. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  108. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  109. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  110. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  111. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  112. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
  113. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  114. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  115. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
  116. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  117. package/templates/firebase/lib/core/config/features.dart +5 -4
  118. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  119. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  120. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  121. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  122. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  123. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  124. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  125. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  126. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  127. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  128. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  129. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  130. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  131. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  132. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  133. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  134. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  135. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  136. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  137. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  138. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  139. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  140. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  141. package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
  142. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  143. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  144. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  145. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  146. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  147. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  148. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  149. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  150. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  151. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  152. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  153. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  154. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  155. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  156. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  157. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  158. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  159. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  160. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  161. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
  162. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  163. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  164. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  165. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  166. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  167. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  168. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  169. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  170. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  171. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  172. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  173. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  174. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  175. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  176. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  177. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  178. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  182. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  183. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  184. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  192. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  193. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  194. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  198. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  199. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  200. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  201. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  202. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  203. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  204. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  205. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  206. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  208. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  209. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  210. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  211. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  212. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  213. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  214. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  215. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  216. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  226. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  227. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  228. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  229. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  230. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  231. package/templates/firebase/lib/main.dart +6 -3
  232. package/templates/firebase/lib/router.dart +15 -23
  233. package/templates/firebase/pubspec.yaml +4 -5
  234. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  235. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  236. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  238. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  239. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  240. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  241. package/templates/firebase/test/test_utils.dart +6 -6
  242. package/templates/firebase/web/index.html +5 -2
  243. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  244. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  245. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  246. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  247. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  249. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  250. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  251. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  252. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  253. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  254. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  255. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  256. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  257. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  258. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  268. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  269. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -16,6 +16,10 @@ sealed class UserEntity with _$UserEntity {
16
16
  @JsonKey(name: 'avatar_url') String? avatarPath,
17
17
  bool? onboarded,
18
18
  String? locale,
19
+ // Access-control role. null/absent = normal user; "admin" unlocks the admin
20
+ // console's server data. Server-only: the `users` guard trigger blocks the
21
+ // client from writing it (set it from the Supabase dashboard / SQL editor).
22
+ String? role,
19
23
  }) = UserEntityData;
20
24
 
21
25
  const UserEntity._();
@@ -6,7 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
6
6
  import 'package:kasy_kit/core/config/app_env.dart';
7
7
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
8
8
 
9
- part 'environnements.freezed.dart';
9
+ part 'environments.freezed.dart';
10
10
 
11
11
  // URLs for terms of service and privacy policy
12
12
  const kTermsUrl = '';
@@ -38,10 +38,6 @@ sealed class Environment with _$Environment {
38
38
  /// (only if you want to use in-app purchases with RevenueCat)
39
39
  String? revenueCatIOSApiKey,
40
40
 
41
- /// RevenueCat Web Billing API key (rcb_xxx or rcb_sb_xxx)
42
- /// (only if you want to use subscriptions on web)
43
- String? revenueCatWebApiKey,
44
-
45
41
  /// this is used to open the app store page of your app for reviews
46
42
  String? appStoreId,
47
43
 
@@ -74,10 +70,6 @@ sealed class Environment with _$Environment {
74
70
  /// (only if you want to use in-app purchases with RevenueCat)
75
71
  String? revenueCatIOSApiKey,
76
72
 
77
- /// RevenueCat Web Billing API key (rcb_xxx or rcb_sb_xxx)
78
- /// (only if you want to use subscriptions on web)
79
- String? revenueCatWebApiKey,
80
-
81
73
  /// only if you want to use ads
82
74
  String? androidInterstitialAdUnitId,
83
75
 
@@ -114,7 +106,6 @@ sealed class Environment with _$Environment {
114
106
  appStoreId: '',
115
107
  revenueCatAndroidApiKey: AppEnv.rcAndroidApiKey,
116
108
  revenueCatIOSApiKey: AppEnv.rcIosApiKey,
117
- revenueCatWebApiKey: AppEnv.rcWebApiKey,
118
109
  mixpanelToken: AppEnv.mixpanelToken,
119
110
  authenticationMode: AuthenticationMode.anonymous,
120
111
  );
@@ -125,7 +116,6 @@ sealed class Environment with _$Environment {
125
116
  appStoreId: AppEnv.appStoreId,
126
117
  revenueCatAndroidApiKey: AppEnv.rcAndroidApiKey,
127
118
  revenueCatIOSApiKey: AppEnv.rcIosApiKey,
128
- revenueCatWebApiKey: AppEnv.rcWebApiKey,
129
119
  sentryDsn: AppEnv.sentryDsn,
130
120
  mixpanelToken: AppEnv.mixpanelToken,
131
121
  authenticationMode: AuthenticationMode.anonymous,
@@ -140,8 +130,8 @@ sealed class Environment with _$Environment {
140
130
  revenueCatIOSApiKey != null && revenueCatIOSApiKey!.isNotEmpty,
141
131
  TargetPlatform.android =>
142
132
  revenueCatAndroidApiKey != null && revenueCatAndroidApiKey!.isNotEmpty,
143
- _ => kIsWeb &&
144
- (revenueCatWebApiKey != null && revenueCatWebApiKey!.isNotEmpty),
133
+ // RevenueCat is mobile-only; web/desktop are never RevenueCat-configured.
134
+ _ => false,
145
135
  };
146
136
  }
147
137
 
@@ -0,0 +1,95 @@
1
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
2
+ import 'package:logger/logger.dart';
3
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_conversation_entity.dart';
4
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_message_entity.dart';
5
+ import 'package:supabase_flutter/supabase_flutter.dart';
6
+
7
+ final aiChatApiProvider = Provider<AiChatApi>(
8
+ (ref) => AiChatApi(client: Supabase.instance.client),
9
+ );
10
+
11
+ const _kConversationsTable = 'ai_conversations';
12
+ const _kMessagesTable = 'ai_messages';
13
+
14
+ /// One user has many conversations (public.ai_conversations), each with many
15
+ /// messages (public.ai_messages, linked by conversation_id).
16
+ class AiChatApi {
17
+ final SupabaseClient _client;
18
+ final Logger _logger = Logger();
19
+
20
+ AiChatApi({required SupabaseClient client}) : _client = client;
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Conversations
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /// Returns the user's conversations, most recently updated first.
27
+ Future<List<AiChatConversationEntity>> loadConversations(
28
+ String userId,
29
+ ) async {
30
+ final rows = await _client
31
+ .from(_kConversationsTable)
32
+ .select()
33
+ .eq('user_id', userId)
34
+ .order('updated_at', ascending: false);
35
+ return rows.map(AiChatConversationEntity.fromJson).toList();
36
+ }
37
+
38
+ /// Creates an empty conversation and returns it (with its generated id).
39
+ Future<AiChatConversationEntity> createConversation(String userId) async {
40
+ final row = await _client
41
+ .from(_kConversationsTable)
42
+ .insert({'user_id': userId})
43
+ .select()
44
+ .single();
45
+ return AiChatConversationEntity.fromJson(row);
46
+ }
47
+
48
+ /// Deletes a conversation; its messages cascade via the foreign key.
49
+ Future<void> deleteConversation(String userId, String conversationId) async {
50
+ await _client.from(_kConversationsTable).delete().eq('id', conversationId);
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Messages
55
+ // ---------------------------------------------------------------------------
56
+
57
+ /// Returns all messages in a conversation, oldest first.
58
+ Future<List<AiChatMessageEntity>> loadMessages(
59
+ String userId,
60
+ String conversationId,
61
+ ) async {
62
+ final rows = await _client
63
+ .from(_kMessagesTable)
64
+ .select()
65
+ .eq('conversation_id', conversationId)
66
+ .order('created_at', ascending: true);
67
+ return rows.map(AiChatMessageEntity.fromJson).toList();
68
+ }
69
+
70
+ /// Persists a message and denormalizes it onto the parent conversation
71
+ /// (last message preview + updated_at) so the list stays cheap to render.
72
+ Future<void> saveMessage(
73
+ String userId,
74
+ String conversationId,
75
+ AiChatMessageEntity message,
76
+ ) async {
77
+ try {
78
+ await _client.from(_kMessagesTable).insert({
79
+ 'user_id': userId,
80
+ 'conversation_id': conversationId,
81
+ ...message.toJson(),
82
+ });
83
+ await _client
84
+ .from(_kConversationsTable)
85
+ .update({
86
+ 'updated_at': message.createdAt.toUtc().toIso8601String(),
87
+ 'last_message_role': message.role,
88
+ 'last_message_content': message.content,
89
+ })
90
+ .eq('id', conversationId);
91
+ } catch (e) {
92
+ _logger.e('Failed to persist AI message: $e');
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,52 @@
1
+ /// Supabase row mapping for a single AI chat conversation.
2
+ /// Table: public.ai_conversations
3
+ ///
4
+ /// The last message is denormalized onto the conversation so the list can be
5
+ /// rendered with a single query (no need to read each conversation's messages
6
+ /// just to show a preview + timestamp).
7
+ class AiChatConversationEntity {
8
+ final String id;
9
+ final DateTime createdAt;
10
+ final DateTime updatedAt;
11
+
12
+ /// Role of the most recent message ('user' or 'assistant'), or null when the
13
+ /// conversation has no messages yet.
14
+ final String? lastMessageRole;
15
+
16
+ /// Content of the most recent message, or null when empty.
17
+ final String? lastMessageContent;
18
+
19
+ const AiChatConversationEntity({
20
+ required this.id,
21
+ required this.createdAt,
22
+ required this.updatedAt,
23
+ this.lastMessageRole,
24
+ this.lastMessageContent,
25
+ });
26
+
27
+ bool get isEmpty => lastMessageContent == null;
28
+
29
+ AiChatConversationEntity copyWith({
30
+ DateTime? updatedAt,
31
+ String? lastMessageRole,
32
+ String? lastMessageContent,
33
+ }) {
34
+ return AiChatConversationEntity(
35
+ id: id,
36
+ createdAt: createdAt,
37
+ updatedAt: updatedAt ?? this.updatedAt,
38
+ lastMessageRole: lastMessageRole ?? this.lastMessageRole,
39
+ lastMessageContent: lastMessageContent ?? this.lastMessageContent,
40
+ );
41
+ }
42
+
43
+ factory AiChatConversationEntity.fromJson(Map<String, dynamic> json) {
44
+ return AiChatConversationEntity(
45
+ id: json['id'] as String,
46
+ createdAt: DateTime.parse(json['created_at'] as String),
47
+ updatedAt: DateTime.parse(json['updated_at'] as String),
48
+ lastMessageRole: json['last_message_role'] as String?,
49
+ lastMessageContent: json['last_message_content'] as String?,
50
+ );
51
+ }
52
+ }
@@ -1,20 +1,20 @@
1
- /// Supabase row mapping for a single LLM chat message.
2
- /// Table: public.llm_messages
3
- class LlmChatMessageEntity {
1
+ /// Supabase row mapping for a single AI chat message.
2
+ /// Table: public.ai_messages
3
+ class AiChatMessageEntity {
4
4
  final String? id;
5
5
  final String role; // 'user' or 'assistant'
6
6
  final String content;
7
7
  final DateTime createdAt;
8
8
 
9
- const LlmChatMessageEntity({
9
+ const AiChatMessageEntity({
10
10
  this.id,
11
11
  required this.role,
12
12
  required this.content,
13
13
  required this.createdAt,
14
14
  });
15
15
 
16
- factory LlmChatMessageEntity.fromJson(Map<String, dynamic> json) {
17
- return LlmChatMessageEntity(
16
+ factory AiChatMessageEntity.fromJson(Map<String, dynamic> json) {
17
+ return AiChatMessageEntity(
18
18
  id: json['id'] as String?,
19
19
  role: json['role'] as String,
20
20
  content: json['content'] as String,
@@ -5,8 +5,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
5
5
  import 'package:kasy_kit/core/config/app_env.dart';
6
6
  import 'package:kasy_kit/core/states/translations.dart';
7
7
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
8
- import 'package:kasy_kit/features/llm_chat/api/llm_chat_api.dart';
9
- import 'package:kasy_kit/features/llm_chat/api/llm_chat_message_entity.dart';
8
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_api.dart';
9
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_message_entity.dart';
10
+ import 'package:kasy_kit/features/ai_chat/providers/ai_conversations_notifier.dart';
10
11
  import 'package:logger/logger.dart';
11
12
  import 'package:supabase_flutter/supabase_flutter.dart';
12
13
 
@@ -24,33 +25,33 @@ class ChatMessage {
24
25
  factory ChatMessage.assistant(String content) =>
25
26
  ChatMessage(role: 'assistant', content: content);
26
27
 
27
- factory ChatMessage.fromEntity(LlmChatMessageEntity entity) =>
28
+ factory ChatMessage.fromEntity(AiChatMessageEntity entity) =>
28
29
  ChatMessage(role: entity.role, content: entity.content);
29
30
 
30
- LlmChatMessageEntity toEntity() => LlmChatMessageEntity(
31
+ AiChatMessageEntity toEntity() => AiChatMessageEntity(
31
32
  role: role,
32
33
  content: content,
33
34
  createdAt: DateTime.now(),
34
35
  );
35
36
  }
36
37
 
37
- class LlmChatState {
38
+ class AiChatState {
38
39
  final List<ChatMessage> messages;
39
40
  final bool isReplying;
40
41
  final bool streamingStarted;
41
42
 
42
- const LlmChatState({
43
+ const AiChatState({
43
44
  required this.messages,
44
45
  this.isReplying = false,
45
46
  this.streamingStarted = false,
46
47
  });
47
48
 
48
- LlmChatState copyWith({
49
+ AiChatState copyWith({
49
50
  List<ChatMessage>? messages,
50
51
  bool? isReplying,
51
52
  bool? streamingStarted,
52
53
  }) {
53
- return LlmChatState(
54
+ return AiChatState(
54
55
  messages: messages ?? this.messages,
55
56
  isReplying: isReplying ?? this.isReplying,
56
57
  streamingStarted: streamingStarted ?? this.streamingStarted,
@@ -58,34 +59,46 @@ class LlmChatState {
58
59
  }
59
60
  }
60
61
 
61
- final llmChatNotifierProvider =
62
- AsyncNotifierProvider<LlmChatNotifier, LlmChatState>(LlmChatNotifier.new);
62
+ final aiChatNotifierProvider =
63
+ AsyncNotifierProvider<AiChatNotifier, AiChatState>(AiChatNotifier.new);
63
64
 
64
- class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
65
+ class AiChatNotifier extends AsyncNotifier<AiChatState> {
65
66
  final Logger _logger = Logger();
66
67
 
68
+ String? _streamingConversationId;
69
+
70
+ bool get _streamStillActive =>
71
+ ref.read(selectedConversationIdProvider) == _streamingConversationId;
72
+
67
73
  @override
68
- Future<LlmChatState> build() async {
74
+ Future<AiChatState> build() async {
75
+ final conversationId = ref.watch(selectedConversationIdProvider);
69
76
  final userId = ref.read(userStateNotifierProvider).user.idOrNull;
70
- if (userId == null) return const LlmChatState(messages: []);
77
+ if (conversationId == null || userId == null) {
78
+ return const AiChatState(messages: []);
79
+ }
71
80
 
72
81
  try {
73
- final entities = await ref.read(llmChatApiProvider).loadMessages(userId);
74
- return LlmChatState(
82
+ final entities = await ref
83
+ .read(aiChatApiProvider)
84
+ .loadMessages(userId, conversationId);
85
+ return AiChatState(
75
86
  messages: entities.map(ChatMessage.fromEntity).toList(),
76
87
  );
77
88
  } catch (e) {
78
- _logger.e('Failed to load LLM chat history: $e');
79
- return const LlmChatState(messages: []);
89
+ _logger.e('Failed to load AI chat history: $e');
90
+ return const AiChatState(messages: []);
80
91
  }
81
92
  }
82
93
 
83
94
  Future<void> sendMessage(String prompt) async {
95
+ final conversationId = ref.read(selectedConversationIdProvider);
84
96
  final current = switch (state) {
85
97
  AsyncData(:final value) => value,
86
98
  _ => null,
87
99
  };
88
- if (current == null || current.isReplying) return;
100
+ if (conversationId == null || current == null || current.isReplying) return;
101
+ _streamingConversationId = conversationId;
89
102
 
90
103
  final userMsg = ChatMessage.user(prompt);
91
104
  state = AsyncData(
@@ -96,20 +109,23 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
96
109
  ),
97
110
  );
98
111
 
99
- _persistMessage(userMsg);
112
+ _persistMessage(userMsg, conversationId);
100
113
 
101
- final allMessages = (state as AsyncData<LlmChatState>).value.messages;
114
+ final allMessages = (state as AsyncData<AiChatState>).value.messages;
102
115
  final history = allMessages.length > _kMaxContextMessages
103
116
  ? allMessages.sublist(allMessages.length - _kMaxContextMessages)
104
117
  : allMessages;
105
- await _requestReplyStream(history);
118
+ await _requestReplyStream(history, conversationId);
106
119
  }
107
120
 
108
- Future<void> _requestReplyStream(List<ChatMessage> history) async {
109
- final t = ref.read(translationsProvider).llm_chat;
110
- final String llmChatEndpoint = AppEnv.llmChatEndpoint;
121
+ Future<void> _requestReplyStream(
122
+ List<ChatMessage> history,
123
+ String conversationId,
124
+ ) async {
125
+ final t = ref.read(translationsProvider).ai_chat;
126
+ final String aiChatEndpoint = AppEnv.aiChatEndpoint;
111
127
 
112
- if (llmChatEndpoint.isEmpty) {
128
+ if (aiChatEndpoint.isEmpty) {
113
129
  _finalizeAssistantMessage(t.error_not_configured);
114
130
  return;
115
131
  }
@@ -132,7 +148,7 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
132
148
 
133
149
  try {
134
150
  final response = await dio.post<ResponseBody>(
135
- llmChatEndpoint,
151
+ aiChatEndpoint,
136
152
  data: {
137
153
  'message': history.last.content,
138
154
  'history': history
@@ -170,7 +186,7 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
170
186
 
171
187
  final fullContent = contentBuffer.toString();
172
188
  if (fullContent.isNotEmpty) {
173
- _persistMessage(ChatMessage.assistant(fullContent));
189
+ _persistMessage(ChatMessage.assistant(fullContent), conversationId);
174
190
  } else {
175
191
  _finalizeAssistantMessage(t.error_no_reply);
176
192
  return;
@@ -180,18 +196,19 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
180
196
  AsyncData(:final value) => value,
181
197
  _ => null,
182
198
  };
183
- if (latest != null) {
199
+ if (latest != null && _streamStillActive) {
184
200
  state = AsyncData(
185
201
  latest.copyWith(isReplying: false, streamingStarted: false),
186
202
  );
187
203
  }
188
204
  } catch (error) {
189
- _logger.e('LLM chat stream failed: $error');
205
+ _logger.e('AI chat stream failed: $error');
190
206
  _finalizeAssistantMessage(t.error_network);
191
207
  }
192
208
  }
193
209
 
194
210
  void _appendStreamingChunk(String partialContent) {
211
+ if (!_streamStillActive) return;
195
212
  final current = switch (state) {
196
213
  AsyncData(:final value) => value,
197
214
  _ => null,
@@ -207,20 +224,21 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
207
224
  : [...msgs, ChatMessage.assistant(partialContent)];
208
225
 
209
226
  state = AsyncData(
210
- LlmChatState(messages: newMsgs, isReplying: true, streamingStarted: true),
227
+ AiChatState(messages: newMsgs, isReplying: true, streamingStarted: true),
211
228
  );
212
229
  }
213
230
 
214
231
  void _finalizeAssistantMessage(String content) {
232
+ if (!_streamStillActive) return;
215
233
  final current = switch (state) {
216
234
  AsyncData(:final value) => value,
217
- _ => const LlmChatState(messages: []),
235
+ _ => const AiChatState(messages: []),
218
236
  };
219
237
  final msgs = current.messages;
220
238
  final newMsgs = current.streamingStarted
221
239
  ? [...msgs.sublist(0, msgs.length - 1), ChatMessage.assistant(content)]
222
240
  : [...msgs, ChatMessage.assistant(content)];
223
- state = AsyncData(LlmChatState(messages: newMsgs));
241
+ state = AsyncData(AiChatState(messages: newMsgs));
224
242
  }
225
243
 
226
244
  String _extractSSEDelta(String line) {
@@ -253,14 +271,24 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
253
271
  return '';
254
272
  }
255
273
 
256
- void _persistMessage(ChatMessage message) {
274
+ void _persistMessage(ChatMessage message, String conversationId) {
257
275
  final userId = ref.read(userStateNotifierProvider).user.idOrNull;
258
276
  if (userId == null) return;
277
+ final entity = message.toEntity();
259
278
  ref
260
- .read(llmChatApiProvider)
261
- .saveMessage(userId, message.toEntity())
279
+ .read(aiChatApiProvider)
280
+ .saveMessage(userId, conversationId, entity)
262
281
  .catchError((e) {
263
282
  _logger.e('Failed to persist message: $e');
264
283
  });
284
+ // Keep the conversation list preview + ordering in sync.
285
+ ref
286
+ .read(aiConversationsNotifierProvider.notifier)
287
+ .touch(
288
+ conversationId,
289
+ role: message.role,
290
+ content: message.content,
291
+ at: entity.createdAt,
292
+ );
265
293
  }
266
294
  }
@@ -8,7 +8,7 @@ import 'package:google_sign_in/google_sign_in.dart';
8
8
  import 'package:logger/logger.dart';
9
9
  import 'package:kasy_kit/core/data/api/base_api_exceptions.dart';
10
10
  import 'package:kasy_kit/core/data/entities/user_entity.dart';
11
- import 'package:kasy_kit/environnements.dart';
11
+ import 'package:kasy_kit/environments.dart';
12
12
  import 'package:kasy_kit/google_auth_options.dart';
13
13
  import 'package:kasy_kit/features/authentication/api/authentication_api_interface.dart';
14
14
  import 'package:kasy_kit/features/authentication/repositories/exceptions/authentication_exceptions.dart';
@@ -56,4 +56,50 @@ class FeatureRequestApi {
56
56
  throw ApiError(code: 0, message: '$e: $stacktrace');
57
57
  }
58
58
  }
59
+
60
+ /// Admin: every request (active + hidden), highest-voted first.
61
+ Future<List<FeatureRequestEntity>> getAll() async {
62
+ try {
63
+ final res = await _client
64
+ .from(_kFeatureRequestTable) //
65
+ .select()
66
+ .order('votes', ascending: false);
67
+ return res.map((e) => FeatureRequestEntity.fromJson(e)).toList();
68
+ } catch (e, stacktrace) {
69
+ Logger().e('$e: $stacktrace');
70
+ throw ApiError(code: 0, message: '$e: $stacktrace');
71
+ }
72
+ }
73
+
74
+ /// Admin: toggle whether a request is visible (and votable) to users.
75
+ /// Gated server-side by the "Feature requests admin update" RLS policy.
76
+ Future<void> setActive(String id, bool active) async {
77
+ try {
78
+ await _client
79
+ .from(_kFeatureRequestTable) //
80
+ .update({'active': active})
81
+ .eq('id', id);
82
+ } catch (e, stacktrace) {
83
+ Logger().e('$e: $stacktrace');
84
+ throw ApiError(code: 0, message: '$e: $stacktrace');
85
+ }
86
+ }
87
+
88
+ /// Admin: update the localized title/description maps (en/pt/es).
89
+ Future<void> updateTexts({
90
+ required String id,
91
+ required Map<String, String> title,
92
+ required Map<String, String> description,
93
+ }) async {
94
+ try {
95
+ await _client.from(_kFeatureRequestTable).update({
96
+ 'title': title,
97
+ 'description': description,
98
+ 'last_update_date': DateTime.now().toUtc().toIso8601String(),
99
+ }).eq('id', id);
100
+ } catch (e, stacktrace) {
101
+ Logger().e('$e: $stacktrace');
102
+ throw ApiError(code: 0, message: '$e: $stacktrace');
103
+ }
104
+ }
59
105
  }
@@ -1,11 +1,11 @@
1
1
  import 'package:kasy_kit/features/onboarding/api/entities/user_info_entity.dart';
2
2
 
3
3
  enum UserInfoKeys {
4
- genre,
4
+ gender,
5
5
  age,
6
6
  }
7
7
 
8
- enum Genre {
8
+ enum Gender {
9
9
  male,
10
10
  female,
11
11
  none,
@@ -44,11 +44,11 @@ class UserAgeInfo extends UserInfoDetail<AgeRange> {
44
44
  }
45
45
 
46
46
  factory UserAgeInfo.fromString(String value) {
47
- final sexe = AgeRange.values.firstWhere(
47
+ final range = AgeRange.values.firstWhere(
48
48
  (element) => element.name == value,
49
49
  orElse: () => AgeRange.none,
50
50
  );
51
- return UserAgeInfo(sexe);
51
+ return UserAgeInfo(range);
52
52
  }
53
53
 
54
54
 
@@ -62,32 +62,32 @@ class UserAgeInfo extends UserInfoDetail<AgeRange> {
62
62
  }
63
63
 
64
64
  /// ======================================
65
- /// user genre info
65
+ /// user gender info
66
66
  /// ======================================
67
- class UserSexeInfo extends UserInfoDetail<Genre> {
68
- UserSexeInfo(super.value);
67
+ class UserGenderInfo extends UserInfoDetail<Gender> {
68
+ UserGenderInfo(super.value);
69
69
 
70
- factory UserSexeInfo.fromEntity(UserInfoEntity entity) {
71
- final value = Genre.values.firstWhere(
70
+ factory UserGenderInfo.fromEntity(UserInfoEntity entity) {
71
+ final value = Gender.values.firstWhere(
72
72
  (element) => element.name == entity.value,
73
- orElse: () => Genre.none,
73
+ orElse: () => Gender.none,
74
74
  );
75
- return UserSexeInfo(value);
75
+ return UserGenderInfo(value);
76
76
  }
77
77
 
78
- factory UserSexeInfo.fromString(String value) {
79
- final genre = Genre.values.firstWhere(
78
+ factory UserGenderInfo.fromString(String value) {
79
+ final gender = Gender.values.firstWhere(
80
80
  (element) => element.name == value,
81
- orElse: () => Genre.none,
81
+ orElse: () => Gender.none,
82
82
  );
83
- return UserSexeInfo(genre);
83
+ return UserGenderInfo(gender);
84
84
  }
85
85
 
86
86
 
87
87
 
88
88
  @override
89
89
  UserInfoEntity toEntity(String userId) => UserInfoEntity(
90
- key: UserInfoKeys.genre.name,
90
+ key: UserInfoKeys.gender.name,
91
91
  value: value.name,
92
92
  userId: userId,
93
93
  );
@@ -6,7 +6,8 @@ import 'package:kasy_kit/core/data/api/tracking_api.dart';
6
6
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
7
7
  import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_background.dart';
8
8
  import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart';
9
- import 'package:kasy_kit/features/subscription/repositories/subscription_repository.dart';
9
+ import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_module_mockups.dart';
10
+ import 'package:kasy_kit/features/subscriptions/repositories/subscription_repository.dart';
10
11
  import 'package:kasy_kit/i18n/translations.g.dart';
11
12
  import 'package:permission_handler/permission_handler.dart';
12
13
 
@@ -29,10 +30,10 @@ class AttPermissionStep extends ConsumerWidget {
29
30
 
30
31
  return OnboardingBackground(
31
32
  child: OnboardingIllustrationScaffold(
32
- progress: 0.9,
33
+ step: 6,
33
34
  title: translations.title,
34
35
  description: translations.description,
35
- imageAsset: 'assets/images/onboarding/img3.jpg',
36
+ image: const TrackingPermissionMockup(),
36
37
  footerActions: [
37
38
  KasyButton(
38
39
  label: translations.continue_button,