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
@@ -0,0 +1,163 @@
1
+ import 'package:flutter/foundation.dart' show kIsWeb;
2
+ import 'package:flutter/material.dart';
3
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
4
+ import 'package:intl/intl.dart';
5
+ import 'package:kasy_kit/core/data/models/user.dart';
6
+ import 'package:kasy_kit/core/states/user_state_notifier.dart';
7
+ import 'package:kasy_kit/core/theme/theme.dart';
8
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_conversation_entity.dart';
9
+ import 'package:kasy_kit/features/ai_chat/ui/widgets/ai_chat_avatars.dart';
10
+ import 'package:kasy_kit/i18n/translations.g.dart';
11
+
12
+ /// A single row in the conversation list (Figma `mail` list item): avatar +
13
+ /// title + last-message preview + timestamp. Avatar/title reflect the author
14
+ /// of the most recent message — assistant (app identity) or the user.
15
+ class AiConversationTile extends ConsumerStatefulWidget {
16
+ const AiConversationTile({
17
+ super.key,
18
+ required this.conversation,
19
+ required this.selected,
20
+ required this.onTap,
21
+ required this.onDelete,
22
+ });
23
+
24
+ final AiChatConversationEntity conversation;
25
+ final bool selected;
26
+ final VoidCallback onTap;
27
+ final VoidCallback onDelete;
28
+
29
+ @override
30
+ ConsumerState<AiConversationTile> createState() => _AiConversationTileState();
31
+ }
32
+
33
+ class _AiConversationTileState extends ConsumerState<AiConversationTile> {
34
+ bool _hovered = false;
35
+
36
+ @override
37
+ Widget build(BuildContext context) {
38
+ final conversation = widget.conversation;
39
+ final bool fromUser = conversation.lastMessageRole == 'user';
40
+
41
+ final String title = fromUser
42
+ ? _userDisplayName(ref) ?? t.ai_chat.title
43
+ : t.ai_chat.title;
44
+ final Widget avatar = fromUser
45
+ ? const AiChatUserAvatar(diameter: 36)
46
+ : const AiChatAssistantAvatar(diameter: 36);
47
+ final String preview =
48
+ conversation.lastMessageContent ?? t.ai_chat.conversations_empty;
49
+
50
+ final Color titleColor = context.colors.onBackground;
51
+ final Color subtitleColor = context.colors.muted;
52
+
53
+ final Widget tile = Container(
54
+ padding: const EdgeInsets.all(KasySpacing.smd),
55
+ decoration: BoxDecoration(
56
+ color: widget.selected
57
+ ? context.colors.accentSoft
58
+ : (_hovered ? context.colors.surfaceNeutralSoft : null),
59
+ borderRadius: BorderRadius.circular(16),
60
+ ),
61
+ child: Row(
62
+ crossAxisAlignment: CrossAxisAlignment.start,
63
+ children: [
64
+ avatar,
65
+ const SizedBox(width: KasySpacing.smd),
66
+ Expanded(
67
+ child: Column(
68
+ crossAxisAlignment: CrossAxisAlignment.start,
69
+ children: [
70
+ Row(
71
+ children: [
72
+ Expanded(
73
+ child: Text(
74
+ title,
75
+ maxLines: 1,
76
+ overflow: TextOverflow.ellipsis,
77
+ style: context.textTheme.bodyMedium?.copyWith(
78
+ fontWeight: FontWeight.w600,
79
+ color: titleColor,
80
+ ),
81
+ ),
82
+ ),
83
+ const SizedBox(width: KasySpacing.xs),
84
+ _trailing(context, subtitleColor),
85
+ ],
86
+ ),
87
+ const SizedBox(height: 2),
88
+ Text(
89
+ preview,
90
+ maxLines: 1,
91
+ overflow: TextOverflow.ellipsis,
92
+ style: context.textTheme.bodySmall?.copyWith(
93
+ color: subtitleColor,
94
+ ),
95
+ ),
96
+ ],
97
+ ),
98
+ ),
99
+ ],
100
+ ),
101
+ );
102
+
103
+ final Widget tappable = Semantics(
104
+ button: true,
105
+ selected: widget.selected,
106
+ label: title,
107
+ child: GestureDetector(
108
+ behavior: HitTestBehavior.opaque,
109
+ onTap: widget.onTap,
110
+ child: tile,
111
+ ),
112
+ );
113
+
114
+ // Hover reveals the delete affordance on pointer devices (web / desktop).
115
+ if (!kIsWeb) return tappable;
116
+ return MouseRegion(
117
+ onEnter: (_) => setState(() => _hovered = true),
118
+ onExit: (_) => setState(() => _hovered = false),
119
+ child: tappable,
120
+ );
121
+ }
122
+
123
+ /// On hover (desktop), shows a delete button; otherwise the timestamp.
124
+ Widget _trailing(BuildContext context, Color color) {
125
+ if (kIsWeb && _hovered) {
126
+ return SizedBox(
127
+ height: 16,
128
+ child: InkResponse(
129
+ onTap: widget.onDelete,
130
+ radius: 18,
131
+ child: Icon(KasyIcons.trash, size: 16, color: context.colors.error),
132
+ ),
133
+ );
134
+ }
135
+ return Text(
136
+ _formatTimestamp(context, widget.conversation.updatedAt),
137
+ style: context.textTheme.bodySmall?.copyWith(color: color),
138
+ );
139
+ }
140
+
141
+ String? _userDisplayName(WidgetRef ref) {
142
+ final user = ref.watch(userStateNotifierProvider).user;
143
+ if (user is! AuthenticatedUserData) return null;
144
+ final String? name = user.name?.trim();
145
+ if (name != null && name.isNotEmpty) return name;
146
+ final String local = user.email.split('@').first.trim();
147
+ return local.isNotEmpty ? local : null;
148
+ }
149
+
150
+ String _formatTimestamp(BuildContext context, DateTime time) {
151
+ final String locale = Localizations.localeOf(context).toString();
152
+ final DateTime now = DateTime.now();
153
+ final bool sameDay =
154
+ now.year == time.year && now.month == time.month && now.day == time.day;
155
+ if (sameDay) {
156
+ return DateFormat.jm(locale).format(time);
157
+ }
158
+ if (now.year == time.year) {
159
+ return DateFormat.MMMd(locale).format(time);
160
+ }
161
+ return DateFormat.yMd(locale).format(time);
162
+ }
163
+ }
@@ -9,6 +9,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
9
9
  import 'package:google_sign_in/google_sign_in.dart';
10
10
  import 'package:kasy_kit/core/data/entities/user_entity.dart';
11
11
  import 'package:kasy_kit/features/authentication/api/authentication_api_interface.dart';
12
+ import 'package:kasy_kit/features/authentication/api/popup_dismiss_watcher.dart'
13
+ if (dart.library.js_interop) 'package:kasy_kit/features/authentication/api/popup_dismiss_watcher_web.dart';
12
14
  import 'package:kasy_kit/features/authentication/repositories/exceptions/authentication_exceptions.dart';
13
15
  import 'package:kasy_kit/google_auth_options.dart';
14
16
  import 'package:logger/logger.dart';
@@ -144,6 +146,37 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
144
146
  }
145
147
  }
146
148
 
149
+ /// Firebase Auth web error codes that mean the user dismissed the provider
150
+ /// popup (closed the window / cancelled the account chooser) — a user cancel,
151
+ /// not a real failure.
152
+ bool _isUserCancelledPopup(String code) =>
153
+ code == 'popup-closed-by-user' ||
154
+ code == 'cancelled-popup-request' ||
155
+ code == 'web-context-cancelled' ||
156
+ code == 'user-cancelled' ||
157
+ code == 'canceled';
158
+
159
+ /// Runs a Firebase web popup sign-in ([signInWithPopup] / [linkWithPopup])
160
+ /// alongside a window-refocus watcher. On web those futures never reject when
161
+ /// the user closes the popup manually, so without this the caller would hang
162
+ /// in a loading state forever. If the window refocuses (popup dismissed)
163
+ /// before the popup resolves, we surface a [UserCancelledSignInException].
164
+ Future<UserCredential> _popupOrCancel(
165
+ Future<UserCredential> Function() run,
166
+ ) async {
167
+ final watch = watchAuthPopupDismiss();
168
+ try {
169
+ final result = await Future.any<UserCredential?>([
170
+ run(),
171
+ watch.dismissed.then<UserCredential?>((_) => null),
172
+ ]);
173
+ if (result == null) throw const UserCancelledSignInException();
174
+ return result;
175
+ } finally {
176
+ watch.cancel();
177
+ }
178
+ }
179
+
147
180
  @override
148
181
  Future<Credentials> signinWithGoogle() async {
149
182
  if (kIsWeb) {
@@ -156,13 +189,15 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
156
189
  // 'login_hint': 'user@example.com'
157
190
  // });
158
191
  try {
159
- final credentials = await _auth.signInWithPopup(googleProvider);
192
+ final credentials = await _popupOrCancel(
193
+ () => _auth.signInWithPopup(googleProvider),
194
+ );
160
195
  return Credentials(
161
196
  id: credentials.user!.uid,
162
197
  token: credentials.credential?.token.toString() ?? '',
163
198
  );
164
199
  } on FirebaseAuthException catch (e) {
165
- if (e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
200
+ if (_isUserCancelledPopup(e.code)) {
166
201
  throw const UserCancelledSignInException();
167
202
  }
168
203
  rethrow;
@@ -198,15 +233,17 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
198
233
  if (kIsWeb) {
199
234
  final googleProvider = GoogleAuthProvider();
200
235
  try {
201
- final userCredential = _auth.currentUser != null
202
- ? await _auth.currentUser!.linkWithPopup(googleProvider)
203
- : await _auth.signInWithPopup(googleProvider);
236
+ final userCredential = await _popupOrCancel(
237
+ () => _auth.currentUser != null
238
+ ? _auth.currentUser!.linkWithPopup(googleProvider)
239
+ : _auth.signInWithPopup(googleProvider),
240
+ );
204
241
  return Credentials(
205
242
  id: userCredential.user!.uid,
206
243
  token: userCredential.credential?.token.toString() ?? '',
207
244
  );
208
245
  } on FirebaseAuthException catch (e) {
209
- if (e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
246
+ if (_isUserCancelledPopup(e.code)) {
210
247
  throw const UserCancelledSignInException();
211
248
  }
212
249
  if (e.code == 'credential-already-in-use') {
@@ -215,7 +252,9 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
215
252
  if (anonymousUser != null && anonymousUser.isAnonymous) {
216
253
  await anonymousUser.delete();
217
254
  }
218
- final userCredential = await _auth.signInWithPopup(googleProvider);
255
+ final userCredential = await _popupOrCancel(
256
+ () => _auth.signInWithPopup(googleProvider),
257
+ );
219
258
  return Credentials(id: userCredential.user!.uid, token: userCredential.credential?.token.toString() ?? '');
220
259
  }
221
260
  rethrow;
@@ -243,7 +282,7 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
243
282
  final result = await _auth.signInWithCredential(credential);
244
283
  return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
245
284
  } on FirebaseAuthException catch (e) {
246
- if (e.code == 'canceled' || e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
285
+ if (_isUserCancelledPopup(e.code)) {
247
286
  throw const UserCancelledSignInException();
248
287
  }
249
288
  rethrow;
@@ -286,7 +325,7 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
286
325
  appleProvider.addScope('email');
287
326
  try {
288
327
  final value = kIsWeb
289
- ? await _auth.signInWithPopup(appleProvider)
328
+ ? await _popupOrCancel(() => _auth.signInWithPopup(appleProvider))
290
329
  : await _auth.signInWithProvider(appleProvider);
291
330
  return Credentials(
292
331
  id: value.user!.uid,
@@ -311,11 +350,11 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
311
350
  // No anonymous session — fall back to regular sign-in.
312
351
  try {
313
352
  final result = kIsWeb
314
- ? await _auth.signInWithPopup(appleProvider)
353
+ ? await _popupOrCancel(() => _auth.signInWithPopup(appleProvider))
315
354
  : await _auth.signInWithProvider(appleProvider);
316
355
  return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
317
356
  } on FirebaseAuthException catch (e) {
318
- if (e.code == 'canceled' || e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
357
+ if (_isUserCancelledPopup(e.code)) {
319
358
  throw const UserCancelledSignInException();
320
359
  }
321
360
  rethrow;
@@ -341,7 +380,7 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
341
380
  return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
342
381
  }
343
382
  final result = kIsWeb
344
- ? await _auth.signInWithPopup(appleProvider)
383
+ ? await _popupOrCancel(() => _auth.signInWithPopup(appleProvider))
345
384
  : await _auth.signInWithProvider(appleProvider);
346
385
  return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
347
386
  }
@@ -365,7 +404,7 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
365
404
  final result = await _auth.signInWithCredential(credential);
366
405
  return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
367
406
  } on FirebaseAuthException catch (e) {
368
- if (e.code == 'canceled' || e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request') {
407
+ if (_isUserCancelledPopup(e.code)) {
369
408
  throw const UserCancelledSignInException();
370
409
  }
371
410
  rethrow;
@@ -0,0 +1,12 @@
1
+ import 'dart:async';
2
+
3
+ /// Watches for the user dismissing a provider auth popup.
4
+ ///
5
+ /// Native/non-web stub: there is no browser popup window to watch, so
6
+ /// [PopupDismissWatch.dismissed] never completes and [PopupDismissWatch.cancel]
7
+ /// is a no-op. The real implementation lives in `popup_dismiss_watcher_web.dart`
8
+ /// and is selected via conditional import on web.
9
+ typedef PopupDismissWatch = ({Future<void> dismissed, void Function() cancel});
10
+
11
+ PopupDismissWatch watchAuthPopupDismiss() =>
12
+ (dismissed: Completer<void>().future, cancel: () {});
@@ -0,0 +1,35 @@
1
+ import 'dart:async';
2
+ import 'dart:js_interop';
3
+
4
+ import 'package:web/web.dart' as web;
5
+
6
+ typedef PopupDismissWatch = ({Future<void> dismissed, void Function() cancel});
7
+
8
+ /// Web implementation: Firebase's `signInWithPopup` / `linkWithPopup` never
9
+ /// reject when the user closes the popup window manually, leaving the sign-in
10
+ /// future pending forever. We detect that dismissal indirectly: when the popup
11
+ /// closes, the main window regains focus.
12
+ ///
13
+ /// [dismissed] completes ~1.2s after the main window refocuses (a debounce so a
14
+ /// successful sign-in — which resolves its own future first — wins the race).
15
+ /// Always call [cancel] once the sign-in settles to remove the listener.
16
+ PopupDismissWatch watchAuthPopupDismiss() {
17
+ final completer = Completer<void>();
18
+ Timer? settle;
19
+
20
+ final handler = ((web.Event _) {
21
+ settle?.cancel();
22
+ settle = Timer(const Duration(milliseconds: 1200), () {
23
+ if (!completer.isCompleted) completer.complete();
24
+ });
25
+ }).toJS;
26
+
27
+ web.window.addEventListener('focus', handler);
28
+
29
+ void cancel() {
30
+ settle?.cancel();
31
+ web.window.removeEventListener('focus', handler);
32
+ }
33
+
34
+ return (dismissed: completer.future, cancel: cancel);
35
+ }
@@ -1,101 +1,141 @@
1
1
  import 'package:flutter/material.dart';
2
- import 'package:flutter/services.dart';
3
2
  import 'package:flutter_riverpod/flutter_riverpod.dart';
4
3
  import 'package:go_router/go_router.dart';
5
4
  import 'package:kasy_kit/components/components.dart';
6
5
  import 'package:kasy_kit/core/theme/theme.dart';
7
- import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
6
+ import 'package:kasy_kit/core/widgets/kasy_pressable_depth.dart';
7
+ import 'package:kasy_kit/core/widgets/page_background.dart';
8
8
  import 'package:kasy_kit/features/authentication/providers/models/email.dart';
9
9
  import 'package:kasy_kit/features/authentication/providers/models/recover_state.dart';
10
10
  import 'package:kasy_kit/features/authentication/providers/recover_provider.dart';
11
+ import 'package:kasy_kit/features/authentication/ui/widgets/auth_card_scaffold.dart';
11
12
  import 'package:kasy_kit/features/authentication/ui/widgets/recover_password_result.dart';
12
13
  import 'package:kasy_kit/i18n/translations.g.dart';
13
14
 
14
15
  final _formKey = GlobalKey<FormState>();
15
16
 
17
+ /// Reset-password screen. Mirrors the sign-in / sign-up layout: a centered
18
+ /// card (logo, title, single email field, button) on a plain background, so it
19
+ /// looks identical to those pages on web/desktop instead of a stretched mobile
20
+ /// sub-page.
16
21
  class RecoverPasswordPage extends ConsumerWidget {
17
22
  const RecoverPasswordPage({super.key});
18
23
 
24
+ void _submit(BuildContext context, WidgetRef ref) {
25
+ if (!_formKey.currentState!.validate()) return;
26
+ FocusScope.of(context).unfocus();
27
+ ref.read(recoverStateProvider.notifier).send();
28
+ }
29
+
19
30
  @override
20
31
  Widget build(BuildContext context, WidgetRef ref) {
21
32
  final state = ref.watch(recoverStateProvider);
33
+ final bool isSending = state is RecoverStateSending;
34
+ final bool isSent = state is RecoverStateSent;
22
35
 
23
36
  return PopScope(
24
37
  canPop: context.canPop(),
25
38
  onPopInvokedWithResult: (didPop, _) {
26
- if (!didPop) {
27
- return;
28
- }
39
+ if (!didPop) return;
29
40
  ref.invalidate(recoverStateProvider);
30
41
  },
31
- child: AnnotatedRegion<SystemUiOverlayStyle>(
32
- value: SystemUiOverlayStyle.dark,
33
- child: KasyOverlayScaffold(
34
- title: t.auth.recover.title,
35
- appBarStyle: KasyAppBarStyle.subpageSimple,
36
- onBack: () {
37
- if (context.canPop()) {
38
- context.pop();
39
- }
40
- },
41
- slivers: [
42
- switch (state) {
43
- RecoverStateData() => SliverToBoxAdapter(
44
- child: ScrollConfiguration(
45
- behavior: const KasyKitScrollBehavior(),
46
- child: Form(
47
- key: _formKey,
48
- child: Column(
49
- crossAxisAlignment: CrossAxisAlignment.stretch,
50
- children: [
51
- const SizedBox(height: KasySpacing.lg),
52
- KasyTextField(
53
- key: const Key('email_input'),
54
- label: t.auth.recover.email_label,
55
- contentType: KasyTextFieldContentType.email,
56
- validator: (value) {
57
- try {
58
- state.email.validate();
59
- } on EmailException catch (_) {
60
- return t.auth.signin.email_invalid;
61
- }
62
- return null;
63
- },
64
- onChanged: (value) => ref
65
- .read(recoverStateProvider.notifier)
66
- .setEmail(value),
67
- ),
68
- const SizedBox(height: KasySpacing.lg),
69
- KasyButton(
70
- key: const Key('recover_button'),
71
- label: t.auth.recover.submit,
72
- onPressed: () {
73
- if (!_formKey.currentState!.validate()) return;
74
- ref.read(recoverStateProvider.notifier).send();
75
- },
76
- expand: true,
77
- ),
78
- ],
42
+ child: Scaffold(
43
+ resizeToAvoidBottomInset: false,
44
+ body: Stack(
45
+ children: [
46
+ if (isSent)
47
+ const Background(child: RecoverPasswordSent())
48
+ else
49
+ Form(
50
+ key: _formKey,
51
+ child: AuthCardScaffold(
52
+ title: t.auth.recover.title,
53
+ subtitle: t.auth.recover.subtitle,
54
+ children: [
55
+ KasyTextField(
56
+ key: const Key('email_input'),
57
+ label: t.auth.recover.email_label,
58
+ contentType: KasyTextFieldContentType.email,
59
+ textInputAction: TextInputAction.done,
60
+ validator: (value) {
61
+ try {
62
+ state.email.validate();
63
+ } on EmailException catch (_) {
64
+ return t.auth.signin.email_invalid;
65
+ }
66
+ return null;
67
+ },
68
+ onChanged: (value) => ref
69
+ .read(recoverStateProvider.notifier)
70
+ .setEmail(value),
71
+ onSubmitted:
72
+ isSending ? null : (_) => _submit(context, ref),
79
73
  ),
80
- ),
81
- ),
82
- ),
83
- RecoverStateSending() => SliverFillRemaining(
84
- child: Center(
85
- child: CircularProgressIndicator.adaptive(
86
- valueColor: AlwaysStoppedAnimation<Color>(
87
- context.colors.primary,
88
- ),
74
+ const SizedBox(height: KasySpacing.lg),
75
+ KasyButton(
76
+ key: const Key('recover_button'),
77
+ label: t.auth.recover.submit,
78
+ isLoading: isSending,
79
+ onPressed:
80
+ isSending ? null : () => _submit(context, ref),
81
+ expand: true,
89
82
  ),
90
- ),
91
- ),
92
- RecoverStateSent() => const SliverFillRemaining(
93
- child: RecoverPasswordSent(),
83
+ const SizedBox(height: KasySpacing.md),
84
+ const _BackToSigninPrompt(),
85
+ ],
94
86
  ),
87
+ ),
88
+ ],
89
+ ),
90
+ ),
91
+ );
92
+ }
93
+ }
94
+
95
+ /// Text link back to sign‑in, styled like the "Don't have an account? Sign up"
96
+ /// prompts. Replaces the old floating back orb so reaching login is one obvious
97
+ /// tap right under the action.
98
+ class _BackToSigninPrompt extends StatelessWidget {
99
+ const _BackToSigninPrompt();
100
+
101
+ @override
102
+ Widget build(BuildContext context) {
103
+ return Center(
104
+ child: FittedBox(
105
+ fit: BoxFit.scaleDown,
106
+ child: Row(
107
+ mainAxisAlignment: MainAxisAlignment.center,
108
+ mainAxisSize: MainAxisSize.min,
109
+ children: [
110
+ Text(
111
+ t.auth.recover.remember,
112
+ style: context.textTheme.bodyMedium?.copyWith(
113
+ color: context.colors.onSurface.withValues(alpha: 0.62),
114
+ fontWeight: FontWeight.w500,
115
+ ),
116
+ ),
117
+ const SizedBox(width: 4),
118
+ KasyPressableDepth(
119
+ semanticLabel: t.auth.recover.signin_link,
120
+ onPressed: () {
121
+ if (context.canPop()) {
122
+ context.pop();
123
+ } else {
124
+ context.go('/signin');
125
+ }
95
126
  },
96
- ],
97
- ),
127
+ child: Text(
128
+ t.auth.recover.signin_link,
129
+ style: context.textTheme.bodyMedium?.copyWith(
130
+ color: context.colors.onSurface,
131
+ fontWeight: FontWeight.w800,
132
+ ),
133
+ ),
134
+ ),
135
+ ],
98
136
  ),
137
+ ),
99
138
  );
100
139
  }
101
140
  }
141
+