kasy-cli 1.21.9 → 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.
- package/lib/commands/add.js +93 -80
- package/lib/commands/configure.js +100 -32
- package/lib/commands/doctor.js +28 -2
- package/lib/commands/new.js +80 -37
- package/lib/commands/notifications.js +1 -1
- package/lib/commands/remove.js +43 -15
- package/lib/commands/run.js +2 -2
- package/lib/commands/update.js +2 -2
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/api/generator.js +14 -14
- package/lib/scaffold/backends/api/patch/README.md +83 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
- package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
- 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
- package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/firebase/deploy.js +87 -13
- package/lib/scaffold/backends/firebase/generator.js +5 -5
- package/lib/scaffold/backends/firebase/tokens.js +4 -4
- package/lib/scaffold/backends/supabase/deploy.js +63 -11
- package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
- package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
- package/lib/scaffold/backends/supabase/generator.js +17 -17
- package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
- package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
- 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
- package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/supabase/tokens.js +3 -3
- package/lib/scaffold/catalog.js +9 -11
- package/lib/scaffold/generate.js +45 -31
- package/lib/scaffold/shared/generator-utils.js +188 -81
- package/lib/scaffold/shared/sort-imports.js +191 -0
- package/lib/scaffold/shared/template-strings.js +3 -3
- package/lib/utils/checks.js +2 -2
- package/lib/utils/i18n/messages-en.js +50 -35
- package/lib/utils/i18n/messages-es.js +50 -35
- package/lib/utils/i18n/messages-pt.js +52 -37
- package/lib/utils/updates.js +15 -15
- package/package.json +1 -1
- package/templates/firebase/.env.example +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
- package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
- package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
- package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
- package/templates/firebase/firestore.rules +24 -5
- package/templates/firebase/functions/package-lock.json +22 -1
- package/templates/firebase/functions/package.json +2 -1
- package/templates/firebase/functions/src/admin/functions.ts +113 -0
- package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
- package/templates/firebase/functions/src/index.ts +8 -2
- package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
- package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
- package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
- package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/components.dart +4 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
- package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
- package/templates/firebase/lib/components/kasy_button.dart +23 -99
- package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
- package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
- package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
- package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
- package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
- package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
- package/templates/firebase/lib/components/kasy_toast.dart +2 -2
- package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
- package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
- package/templates/firebase/lib/core/config/app_env.dart +5 -11
- package/templates/firebase/lib/core/config/features.dart +5 -4
- package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
- package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
- package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
- package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
- package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
- package/templates/firebase/lib/core/data/models/user.dart +11 -0
- package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
- package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
- package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
- package/templates/firebase/lib/core/states/logout_action.dart +25 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
- package/templates/firebase/lib/core/theme/colors.dart +488 -188
- package/templates/firebase/lib/core/theme/radius.dart +22 -11
- package/templates/firebase/lib/core/theme/shadows.dart +66 -0
- package/templates/firebase/lib/core/theme/texts.dart +75 -41
- package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
- package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
- package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
- package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
- package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
- package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
- package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
- package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
- package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
- package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
- package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
- package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
- package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
- package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
- package/templates/firebase/lib/features/home/home_feed.dart +289 -0
- package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
- package/templates/firebase/lib/features/home/home_page.dart +11 -250
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
- package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
- package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
- package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
- package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
- package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
- package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
- package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
- package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
- package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
- package/templates/firebase/lib/i18n/en.i18n.json +171 -46
- package/templates/firebase/lib/i18n/es.i18n.json +175 -50
- package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
- package/templates/firebase/lib/main.dart +6 -3
- package/templates/firebase/lib/router.dart +15 -23
- package/templates/firebase/pubspec.yaml +4 -5
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
- package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
- package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
- package/templates/firebase/test/test_utils.dart +6 -6
- package/templates/firebase/web/index.html +5 -2
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
- package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
- package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
- package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
- package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
- package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
- package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
- /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
package/lib/commands/doctor.js
CHANGED
|
@@ -62,15 +62,16 @@ async function getActiveModules(kitSetup, projectDir) {
|
|
|
62
62
|
if (kitSetup.analyticsProvider === 'mixpanel') modules.push('analytics');
|
|
63
63
|
if (kitSetup.withFacebookPixel) modules.push('facebook');
|
|
64
64
|
if (kitSetup.subscriptionModule) modules.push('revenuecat');
|
|
65
|
+
if (kitSetup.stripeModule) modules.push('stripe');
|
|
65
66
|
if (kitSetup.withOnboarding) modules.push('onboarding');
|
|
66
67
|
if (kitSetup.webCompat) modules.push('web');
|
|
67
68
|
if (kitSetup.feedbackModule) modules.push('feedback');
|
|
68
69
|
|
|
69
|
-
// Read features.dart for
|
|
70
|
+
// Read features.dart for ai_chat, widget
|
|
70
71
|
const featuresPath = path.join(projectDir, 'lib', 'core', 'config', 'features.dart');
|
|
71
72
|
if (await fs.pathExists(featuresPath)) {
|
|
72
73
|
const content = await fs.readFile(featuresPath, 'utf8');
|
|
73
|
-
if (/const bool
|
|
74
|
+
if (/const bool withAiChat\s*=\s*true/.test(content)) modules.push('ai_chat');
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// Check ci by directory
|
|
@@ -114,6 +115,7 @@ async function runProjectChecks(projectDir, t, options = {}) {
|
|
|
114
115
|
|
|
115
116
|
await runIosReleaseChecks(projectDir, t, options.language, config);
|
|
116
117
|
await runRevenueCatChecks(projectDir, t, config);
|
|
118
|
+
await runStripeChecks(projectDir, t, config);
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
async function runIosReleaseChecks(projectDir, t, language, config = {}) {
|
|
@@ -249,6 +251,30 @@ async function runRevenueCatChecks(projectDir, t, config) {
|
|
|
249
251
|
}
|
|
250
252
|
}
|
|
251
253
|
|
|
254
|
+
async function runStripeChecks(projectDir, t, config) {
|
|
255
|
+
if (!config.stripeModule) return;
|
|
256
|
+
|
|
257
|
+
ui.log.step(kleur.bold(t('doctor.stripe.title')));
|
|
258
|
+
ui.log.message(kleur.dim(`– ${t('doctor.stripe.serverSide')}`));
|
|
259
|
+
|
|
260
|
+
if (config.backendProvider === 'firebase') {
|
|
261
|
+
ui.log.message(kleur.dim(`– ${t('doctor.stripe.secretsFirebase')}`));
|
|
262
|
+
if (config.firebaseProjectId) {
|
|
263
|
+
const region = config.functionsRegion || 'us-central1';
|
|
264
|
+
const url = `https://${region}-${config.firebaseProjectId}.cloudfunctions.net/stripeFunctions-stripeWebhook`;
|
|
265
|
+
ui.log.success(`${t('doctor.stripe.webhookUrl')}:\n${kleur.cyan(url)}`);
|
|
266
|
+
}
|
|
267
|
+
} else if (config.backendProvider === 'supabase') {
|
|
268
|
+
ui.log.message(kleur.dim(`– ${t('doctor.stripe.secretsSupabase')}`));
|
|
269
|
+
if (config.supabaseProjectId) {
|
|
270
|
+
const url = `https://${config.supabaseProjectId}.supabase.co/functions/v1/stripe-webhook`;
|
|
271
|
+
ui.log.success(`${t('doctor.stripe.webhookUrl')}:\n${kleur.cyan(url)}`);
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
ui.log.message(kleur.dim(`– ${t('doctor.stripe.secretsApi')}`));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
252
278
|
async function runDoctor(options = {}) {
|
|
253
279
|
const language = options.language || detectDefaultLanguage();
|
|
254
280
|
const t = createTranslator(language);
|
package/lib/commands/new.js
CHANGED
|
@@ -41,14 +41,14 @@ const KASY_AUDIENCE = process.env.KASY_INTERNAL === '1' ? 'internal' : 'public';
|
|
|
41
41
|
const { generateFirebaseProject } = require('../scaffold/backends/firebase/generator');
|
|
42
42
|
const { generateSupabaseProject } = require('../scaffold/backends/supabase/generator');
|
|
43
43
|
const { generateApiProject } = require('../scaffold/backends/api/generator');
|
|
44
|
-
const { createProjectAndGetKeys, setupLinkedProject, checkLoggedIn, getOrgsList, getProjectsByOrg, getProjectKeys } = require('../scaffold/backends/supabase/deploy');
|
|
44
|
+
const { createProjectAndGetKeys, setupLinkedProject, checkLoggedIn, getOrgsList, getProjectsByOrg, getProjectKeys, classifyCreateError } = require('../scaffold/backends/supabase/deploy');
|
|
45
45
|
const { writeSupabaseGoogleAuthOptions, readSupabaseGoogleCredentials, getGoogleClientSecretViaGcloud, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, writeGoogleIosUrlSchemeFromClientId } = require('../scaffold/shared/post-build');
|
|
46
46
|
const { toPackageName } = require('../scaffold/backends/firebase/tokens');
|
|
47
47
|
const { setupFromScratch, setupExistingProject, listBillingAccounts, listGcpOrganizations, checkGcloudAuth, getFirebaseAccount, getGcloudInstallInstructions, enableAuthProviders, ensureFirebaseAuthInitialized, registerDebugSha1 } = require('../scaffold/backends/firebase/setup-from-scratch');
|
|
48
48
|
const { enableAuthViaFirebaseCli } = require('../scaffold/backends/firebase/enable-auth-via-cli');
|
|
49
49
|
const { createFcmServiceAccountKey } = require('../scaffold/shared/fcm-service-account');
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// Default region for creating Supabase projects via the API.
|
|
52
52
|
// Supabase regions: https://supabase.com/docs/guides/platform/regions
|
|
53
53
|
const DEFAULT_SUPABASE_REGION = 'sa-east-1'; // São Paulo
|
|
54
54
|
|
|
@@ -383,6 +383,16 @@ function printSuccessCard(tr, answers, targetDir) {
|
|
|
383
383
|
|
|
384
384
|
const lines = [];
|
|
385
385
|
|
|
386
|
+
// Show the app's bundle ID (Android applicationId / iOS bundle identifier) so
|
|
387
|
+
// the user knows exactly which identifier their project was created with — it's
|
|
388
|
+
// baked into the Android build, the iOS project and the Firebase app
|
|
389
|
+
// registration, and changing it later is a manual, multi-file operation.
|
|
390
|
+
if (answers.bundleId) {
|
|
391
|
+
lines.push(`📦 ${tr('new.success.bundleId')}: ${kleur.cyan(answers.bundleId)}`);
|
|
392
|
+
lines.push(kleur.dim(` ${tr('new.success.bundleId.hint')}`));
|
|
393
|
+
lines.push('');
|
|
394
|
+
}
|
|
395
|
+
|
|
386
396
|
if (answers.modules?.length > 0) {
|
|
387
397
|
const byId = Object.fromEntries(FEATURE_CATALOG.map((f) => [f.id, f]));
|
|
388
398
|
const visible = answers.modules.filter((id) => byId[id]);
|
|
@@ -693,9 +703,12 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
693
703
|
if (!isQuick) ui.log.info(paintLime(`── ${tr(key)} ──`));
|
|
694
704
|
};
|
|
695
705
|
|
|
696
|
-
// ── Bundle ID —
|
|
706
|
+
// ── Bundle ID — asked in every interactive run (Quick included). It's baked into
|
|
707
|
+
// Android/iOS/Firebase and painful to change after the fact, so it's worth one
|
|
708
|
+
// Enter to confirm even in Quick (the field is pre-filled with the value derived
|
|
709
|
+
// from the app name). Only --yes (non-interactive) keeps the derived value silently.
|
|
697
710
|
section('new.advanced.section.config');
|
|
698
|
-
if (!
|
|
711
|
+
if (!yes) {
|
|
699
712
|
const bundleId = await ui.text({
|
|
700
713
|
message: tr('new.firebase.q.bundleId'),
|
|
701
714
|
placeholder: tr('new.firebase.q.bundleId.hint'),
|
|
@@ -1160,13 +1173,24 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1160
1173
|
core.supabaseAnonKey = supabaseCreateResult.supabaseAnonKey;
|
|
1161
1174
|
ui.log.success(tr('new.supabase.created'));
|
|
1162
1175
|
} else {
|
|
1163
|
-
|
|
1164
|
-
|
|
1176
|
+
// Tailor the message to WHY it failed, instead of always dumping the raw
|
|
1177
|
+
// API string + a login hint that may be irrelevant.
|
|
1178
|
+
const failKind = classifyCreateError(supabaseCreateResult.error);
|
|
1179
|
+
if (failKind === 'free_limit') {
|
|
1180
|
+
ui.log.error(tr('new.supabase.createFailed.freeLimit'));
|
|
1181
|
+
ui.note(tr('new.supabase.freeLimit.options'), tr('new.supabase.freeLimit.title'));
|
|
1182
|
+
ui.log.message(kleur.cyan('https://supabase.com/dashboard/projects'));
|
|
1183
|
+
} else if (failKind === 'login') {
|
|
1184
|
+
ui.log.error(`${tr('new.supabase.createFailed')}: ${supabaseCreateResult.error}`);
|
|
1185
|
+
ui.log.message(tr('new.supabase.loginHint'));
|
|
1186
|
+
} else {
|
|
1187
|
+
ui.log.error(`${tr('new.supabase.createFailed')}: ${supabaseCreateResult.error}`);
|
|
1188
|
+
}
|
|
1165
1189
|
Object.assign(core, await promptSupabaseManual(tr, cancel));
|
|
1166
1190
|
supabaseCreate = false;
|
|
1167
1191
|
}
|
|
1168
1192
|
} else {
|
|
1169
|
-
//
|
|
1193
|
+
// Use an existing Supabase project: org → project → keys → password (same setup flow, without creating)
|
|
1170
1194
|
const loginCheck = await checkLoggedIn();
|
|
1171
1195
|
if (!loginCheck.ok) {
|
|
1172
1196
|
showLoginRequired();
|
|
@@ -1321,8 +1345,8 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1321
1345
|
// Visual groups in display order (header key → feature ids in this group)
|
|
1322
1346
|
const groups = [
|
|
1323
1347
|
{ header: null, ids: ['sentry', 'analytics', 'facebook'] },
|
|
1324
|
-
{ header: 'new.modules.header.monetization', ids: ['revenuecat'] },
|
|
1325
|
-
{ header: 'new.modules.header.features', ids: ['onboarding', 'web', 'widget', '
|
|
1348
|
+
{ header: 'new.modules.header.monetization', ids: ['revenuecat', 'stripe'] },
|
|
1349
|
+
{ header: 'new.modules.header.features', ids: ['onboarding', 'web', 'widget', 'ai_chat', 'local_reminders'] },
|
|
1326
1350
|
{ header: 'new.modules.header.feedback', ids: ['feedback'] },
|
|
1327
1351
|
{ header: 'new.modules.header.ci', ids: ['ci'] },
|
|
1328
1352
|
];
|
|
@@ -1371,7 +1395,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1371
1395
|
// philosophy. User can opt-in to enter everything inline if they prefer.
|
|
1372
1396
|
let configureCredsNow = false;
|
|
1373
1397
|
if (!isQuick) {
|
|
1374
|
-
const FEATURES_WITH_CREDS = ['revenuecat', 'sentry', 'analytics', '
|
|
1398
|
+
const FEATURES_WITH_CREDS = ['revenuecat', 'stripe', 'sentry', 'analytics', 'ai_chat', 'facebook'];
|
|
1375
1399
|
if (modules.some((m) => FEATURES_WITH_CREDS.includes(m))) {
|
|
1376
1400
|
section('new.advanced.section.creds');
|
|
1377
1401
|
configureCredsNow = await ui.confirm({
|
|
@@ -1445,21 +1469,35 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1445
1469
|
}
|
|
1446
1470
|
}
|
|
1447
1471
|
|
|
1448
|
-
//
|
|
1449
|
-
|
|
1450
|
-
|
|
1472
|
+
// Stripe (web payments) — server-side keys only. In quick/deferred mode they
|
|
1473
|
+
// are configured later via `kasy configure stripe`. The app needs no Stripe
|
|
1474
|
+
// client key (hosted Checkout). Keys are deployed to the backend, never baked
|
|
1475
|
+
// into the app.
|
|
1476
|
+
if (modules.includes('stripe')) {
|
|
1451
1477
|
if (askCreds) {
|
|
1452
|
-
|
|
1453
|
-
message: tr('new.firebase.q.
|
|
1478
|
+
moduleAnswers.stripeSecretKey = ((await ui.password({
|
|
1479
|
+
message: tr('new.firebase.q.stripe.secretKey'),
|
|
1480
|
+
onCancel: cancel,
|
|
1481
|
+
})) || '').trim();
|
|
1482
|
+
moduleAnswers.stripeWebhookSecret = ((await ui.text({
|
|
1483
|
+
message: tr('new.firebase.q.stripe.webhookSecret'),
|
|
1484
|
+
placeholder: tr('new.firebase.q.stripe.webhookSecret.hint'),
|
|
1454
1485
|
validate: (v) => {
|
|
1455
|
-
|
|
1456
|
-
|
|
1486
|
+
const s = (v || '').trim();
|
|
1487
|
+
if (!s) return undefined; // optional — blank is fine
|
|
1488
|
+
return /^whsec_/.test(s) ? undefined : tr('new.firebase.q.stripe.webhookSecret.invalid');
|
|
1457
1489
|
},
|
|
1458
1490
|
onCancel: cancel,
|
|
1459
|
-
});
|
|
1460
|
-
moduleAnswers.
|
|
1491
|
+
})) || '').trim();
|
|
1492
|
+
moduleAnswers.stripeProductId = ((await ui.text({
|
|
1493
|
+
message: tr('new.firebase.q.stripe.productId'),
|
|
1494
|
+
placeholder: tr('new.firebase.q.stripe.productId.hint'),
|
|
1495
|
+
onCancel: cancel,
|
|
1496
|
+
})) || '').trim();
|
|
1461
1497
|
} else {
|
|
1462
|
-
moduleAnswers.
|
|
1498
|
+
moduleAnswers.stripeSecretKey = '';
|
|
1499
|
+
moduleAnswers.stripeWebhookSecret = '';
|
|
1500
|
+
moduleAnswers.stripeProductId = '';
|
|
1463
1501
|
}
|
|
1464
1502
|
}
|
|
1465
1503
|
|
|
@@ -1485,11 +1523,11 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1485
1523
|
});
|
|
1486
1524
|
}
|
|
1487
1525
|
|
|
1488
|
-
//
|
|
1489
|
-
if (modules.includes('
|
|
1526
|
+
// AI Chat credentials.
|
|
1527
|
+
if (modules.includes('ai_chat')) {
|
|
1490
1528
|
if (askCreds) {
|
|
1491
|
-
moduleAnswers.
|
|
1492
|
-
message: tr('add.prompt.
|
|
1529
|
+
moduleAnswers.aiProvider = await ui.select({
|
|
1530
|
+
message: tr('add.prompt.aiProvider'),
|
|
1493
1531
|
initialValue: 'openai',
|
|
1494
1532
|
options: [
|
|
1495
1533
|
{ value: 'openai', label: 'OpenAI (gpt-4o-mini)' },
|
|
@@ -1497,16 +1535,16 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1497
1535
|
],
|
|
1498
1536
|
onCancel: cancel,
|
|
1499
1537
|
});
|
|
1500
|
-
moduleAnswers.
|
|
1501
|
-
message: tr('add.prompt.
|
|
1538
|
+
moduleAnswers.aiSystemPrompt = await ui.text({
|
|
1539
|
+
message: tr('add.prompt.aiSystemPrompt'),
|
|
1502
1540
|
onCancel: cancel,
|
|
1503
1541
|
});
|
|
1504
|
-
moduleAnswers.
|
|
1505
|
-
message: tr('add.prompt.
|
|
1542
|
+
moduleAnswers.aiApiKey = await ui.password({
|
|
1543
|
+
message: tr('add.prompt.aiApiKey'),
|
|
1506
1544
|
onCancel: cancel,
|
|
1507
1545
|
});
|
|
1508
1546
|
} else {
|
|
1509
|
-
moduleAnswers.
|
|
1547
|
+
moduleAnswers.aiConfigureLater = true;
|
|
1510
1548
|
}
|
|
1511
1549
|
}
|
|
1512
1550
|
|
|
@@ -1652,12 +1690,12 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1652
1690
|
await fs.writeJson(path.join(kasyDir, 'config.json'), { functionsRegion: firebaseRegion }, { spaces: 2 });
|
|
1653
1691
|
} catch (_) {}
|
|
1654
1692
|
|
|
1655
|
-
// Write functions/.env with
|
|
1656
|
-
if (modules.includes('
|
|
1693
|
+
// Write functions/.env with AI non-secret vars (provider + system prompt)
|
|
1694
|
+
if (modules.includes('ai_chat') && !moduleAnswers.aiConfigureLater) {
|
|
1657
1695
|
try {
|
|
1658
1696
|
const envLines = [];
|
|
1659
|
-
if (moduleAnswers.
|
|
1660
|
-
if (moduleAnswers.
|
|
1697
|
+
if (moduleAnswers.aiProvider) envLines.push(`AI_PROVIDER=${moduleAnswers.aiProvider}`);
|
|
1698
|
+
if (moduleAnswers.aiSystemPrompt?.trim()) envLines.push(`AI_SYSTEM_PROMPT=${moduleAnswers.aiSystemPrompt.trim()}`);
|
|
1661
1699
|
if (envLines.length > 0) {
|
|
1662
1700
|
await fs.ensureDir(path.join(targetDir, 'functions'));
|
|
1663
1701
|
await fs.appendFile(path.join(targetDir, 'functions', '.env'), envLines.join('\n') + '\n', 'utf8');
|
|
@@ -1774,10 +1812,15 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1774
1812
|
metaAccessToken: moduleAnswers.metaAccessToken,
|
|
1775
1813
|
metaDatasetId: moduleAnswers.metaDatasetId,
|
|
1776
1814
|
} : {}),
|
|
1777
|
-
...(answers.modules?.includes('
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1815
|
+
...(answers.modules?.includes('ai_chat') && !moduleAnswers.aiConfigureLater ? {
|
|
1816
|
+
aiApiKey: moduleAnswers.aiApiKey,
|
|
1817
|
+
aiProvider: moduleAnswers.aiProvider,
|
|
1818
|
+
aiSystemPrompt: moduleAnswers.aiSystemPrompt,
|
|
1819
|
+
} : {}),
|
|
1820
|
+
...(answers.modules?.includes('stripe') ? {
|
|
1821
|
+
stripeSecretKey: moduleAnswers.stripeSecretKey,
|
|
1822
|
+
stripeWebhookSecret: moduleAnswers.stripeWebhookSecret,
|
|
1823
|
+
stripeProductId: moduleAnswers.stripeProductId,
|
|
1781
1824
|
} : {}),
|
|
1782
1825
|
};
|
|
1783
1826
|
const google = googleWebClientId && googleClientSecret
|
|
@@ -81,7 +81,7 @@ async function assertLocalNotificationsEnabled(projectDir, t) {
|
|
|
81
81
|
throw new Error(t('notifications.error.noFeatures'));
|
|
82
82
|
}
|
|
83
83
|
const content = await fs.readFile(featuresPath, 'utf8');
|
|
84
|
-
if (!/const bool
|
|
84
|
+
if (!/const bool withLocalReminders\s*=\s*true/.test(content)) {
|
|
85
85
|
throw new Error(t('notifications.error.notEnabled'));
|
|
86
86
|
}
|
|
87
87
|
}
|
package/lib/commands/remove.js
CHANGED
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
writeNoOpFeatureRequestRepository,
|
|
25
25
|
writeMainDart,
|
|
26
26
|
writeNoOpSubscriptionStubs,
|
|
27
|
+
writeStripeOnlySubscription,
|
|
27
28
|
writeNoOpSentryUsages,
|
|
28
29
|
} = require('../scaffold/shared/generator-utils');
|
|
29
30
|
const { toPackageName } = require('../scaffold/backends/firebase/tokens');
|
|
@@ -49,8 +50,8 @@ const MODULE_DEPS = {
|
|
|
49
50
|
const MODULE_DEFINES = {
|
|
50
51
|
sentry: ['SENTRY_DSN'],
|
|
51
52
|
analytics: ['MIXPANEL_TOKEN'],
|
|
52
|
-
revenuecat: ['RC_ANDROID_API_KEY', 'RC_IOS_API_KEY'
|
|
53
|
-
|
|
53
|
+
revenuecat: ['RC_ANDROID_API_KEY', 'RC_IOS_API_KEY'],
|
|
54
|
+
ai_chat: ['AI_CHAT_ENDPOINT'],
|
|
54
55
|
};
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -65,7 +66,6 @@ const MODULE_ENV_KEYS = {
|
|
|
65
66
|
'RC_TEST_KEY',
|
|
66
67
|
'RC_IOS_PROD_KEY',
|
|
67
68
|
'RC_ANDROID_PROD_KEY',
|
|
68
|
-
'RC_WEB_API_KEY',
|
|
69
69
|
// Legacy single-key format — only present in projects generated before
|
|
70
70
|
// the test/prod split. Listed here so removal cleans them up too.
|
|
71
71
|
'RC_ANDROID_API_KEY',
|
|
@@ -80,7 +80,8 @@ const MODULE_FEATURE_FLAGS = {
|
|
|
80
80
|
onboarding: 'withOnboarding',
|
|
81
81
|
web: 'withWeb',
|
|
82
82
|
feedback: 'withFeedback',
|
|
83
|
-
|
|
83
|
+
ai_chat: 'withAiChat',
|
|
84
|
+
stripe: 'withStripe',
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -95,6 +96,7 @@ async function getActiveModules(kitSetup, projectDir) {
|
|
|
95
96
|
if (kitSetup.analyticsProvider === 'mixpanel') modules.push('analytics');
|
|
96
97
|
if (kitSetup.withFacebookPixel) modules.push('facebook');
|
|
97
98
|
if (kitSetup.subscriptionModule) modules.push('revenuecat');
|
|
99
|
+
if (kitSetup.stripeModule) modules.push('stripe');
|
|
98
100
|
if (kitSetup.withOnboarding) modules.push('onboarding');
|
|
99
101
|
if (kitSetup.webCompat) modules.push('web');
|
|
100
102
|
if (kitSetup.feedbackModule) modules.push('feedback');
|
|
@@ -102,7 +104,7 @@ async function getActiveModules(kitSetup, projectDir) {
|
|
|
102
104
|
const featuresPath = path.join(projectDir, 'lib', 'core', 'config', 'features.dart');
|
|
103
105
|
if (await fs.pathExists(featuresPath)) {
|
|
104
106
|
const content = await fs.readFile(featuresPath, 'utf8');
|
|
105
|
-
if (/const bool
|
|
107
|
+
if (/const bool withAiChat\s*=\s*true/.test(content)) modules.push('ai_chat');
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
if (await fs.pathExists(path.join(projectDir, '.github', 'workflows'))) modules.push('ci');
|
|
@@ -226,9 +228,11 @@ function revertKitSetupFlag(config, module) {
|
|
|
226
228
|
case 'facebook':
|
|
227
229
|
config.withFacebookPixel = false;
|
|
228
230
|
break;
|
|
231
|
+
case 'stripe':
|
|
232
|
+
config.stripeModule = false;
|
|
233
|
+
break;
|
|
229
234
|
case 'revenuecat':
|
|
230
235
|
config.subscriptionModule = false;
|
|
231
|
-
config.revenuecatWeb = false;
|
|
232
236
|
break;
|
|
233
237
|
case 'onboarding':
|
|
234
238
|
config.withOnboarding = false;
|
|
@@ -239,7 +243,7 @@ function revertKitSetupFlag(config, module) {
|
|
|
239
243
|
case 'feedback':
|
|
240
244
|
config.feedbackModule = false;
|
|
241
245
|
break;
|
|
242
|
-
case '
|
|
246
|
+
case 'ai_chat':
|
|
243
247
|
case 'widget':
|
|
244
248
|
case 'ci':
|
|
245
249
|
// Controlled by features.dart / patch — no kit_setup flag to revert
|
|
@@ -254,18 +258,33 @@ function revertKitSetupFlag(config, module) {
|
|
|
254
258
|
* For modules whose stubs are regenerated (analytics, facebook, sentry),
|
|
255
259
|
* no deletion is needed — the stub writer overwrites the file in-place.
|
|
256
260
|
*/
|
|
257
|
-
async function removeModuleFiles(projectDir, module, backend) {
|
|
261
|
+
async function removeModuleFiles(projectDir, module, backend, modulesAfterRemoval = []) {
|
|
258
262
|
// Modules that have dedicated directories to remove
|
|
259
263
|
const dirRemovals = {
|
|
260
|
-
|
|
264
|
+
ai_chat: [path.join('lib', 'features', 'ai_chat')],
|
|
261
265
|
onboarding: [path.join('lib', 'features', 'onboarding')],
|
|
262
266
|
widget: [path.join('lib', 'core', 'home_widgets')],
|
|
263
|
-
revenuecat:
|
|
267
|
+
// revenuecat/stripe: the subscription/ dir is SHARED by both payment modules
|
|
268
|
+
// and is handled below (removed only when neither remains).
|
|
264
269
|
// feedback: handled below (dir deleted, no-op stub recreated afterward)
|
|
265
270
|
// ci: handled below (multiple top-level paths)
|
|
266
271
|
// analytics/facebook/sentry: no dir — stub overwrites the file in place
|
|
267
272
|
};
|
|
268
273
|
|
|
274
|
+
// The subscription/ core is shared by RevenueCat (mobile) and Stripe (web).
|
|
275
|
+
// Remove it only when NEITHER payment module remains. If the other one stays,
|
|
276
|
+
// keep the dir — the stub regeneration step (writeStripeOnlySubscription /
|
|
277
|
+
// no-op) reshapes it for the remaining module.
|
|
278
|
+
if (module === 'revenuecat' || module === 'stripe') {
|
|
279
|
+
const stillHasPayments =
|
|
280
|
+
modulesAfterRemoval.includes('revenuecat') || modulesAfterRemoval.includes('stripe');
|
|
281
|
+
if (!stillHasPayments) {
|
|
282
|
+
const subDir = path.join(projectDir, 'lib', 'features', 'subscriptions');
|
|
283
|
+
if (await fs.pathExists(subDir)) await fs.remove(subDir);
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
269
288
|
if (module === 'feedback') {
|
|
270
289
|
// Delete the full feedbacks dir; writeNoOpFeatureRequestRepository recreates
|
|
271
290
|
// only the repository stub (the rest of the UI/api code is gone).
|
|
@@ -295,9 +314,9 @@ async function removeModuleFiles(projectDir, module, backend) {
|
|
|
295
314
|
if (await fs.pathExists(fullPath)) await fs.remove(fullPath);
|
|
296
315
|
}
|
|
297
316
|
|
|
298
|
-
//
|
|
299
|
-
if (module === '
|
|
300
|
-
const edgeFnPath = path.join(projectDir, 'supabase', 'functions', '
|
|
317
|
+
// AI Chat on Supabase: also remove the copied edge function
|
|
318
|
+
if (module === 'ai_chat' && backend === 'supabase') {
|
|
319
|
+
const edgeFnPath = path.join(projectDir, 'supabase', 'functions', 'ai-chat');
|
|
301
320
|
if (await fs.pathExists(edgeFnPath)) await fs.remove(edgeFnPath);
|
|
302
321
|
}
|
|
303
322
|
}
|
|
@@ -405,7 +424,7 @@ async function runRemove(module, options = {}) {
|
|
|
405
424
|
}
|
|
406
425
|
|
|
407
426
|
// 10. Remove files and directories added by the module's patch
|
|
408
|
-
await removeModuleFiles(projectDir, normalized, kitSetup.backendProvider);
|
|
427
|
+
await removeModuleFiles(projectDir, normalized, kitSetup.backendProvider, modulesAfterRemoval);
|
|
409
428
|
|
|
410
429
|
// 11. Regenerate no-op stubs for the removed module
|
|
411
430
|
const packageName = toPackageName(kitSetup.appName);
|
|
@@ -423,6 +442,15 @@ async function runRemove(module, options = {}) {
|
|
|
423
442
|
await writeNoOpAdminHomeWidgets(projectDir).catch(() => {});
|
|
424
443
|
}
|
|
425
444
|
if (normalized === 'revenuecat') {
|
|
445
|
+
if (modulesAfterRemoval.includes('stripe')) {
|
|
446
|
+
// Both → Stripe-only: keep the core, strip the RevenueCat (purchases_flutter) files.
|
|
447
|
+
await writeStripeOnlySubscription(projectDir, packageName).catch(() => {});
|
|
448
|
+
} else {
|
|
449
|
+
await writeNoOpSubscriptionStubs(projectDir, packageName).catch(() => {});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (normalized === 'stripe' && !modulesAfterRemoval.includes('revenuecat')) {
|
|
453
|
+
// Stripe-only → none: the dir was removed; write the no-op subscription stubs.
|
|
426
454
|
await writeNoOpSubscriptionStubs(projectDir, packageName).catch(() => {});
|
|
427
455
|
}
|
|
428
456
|
|
|
@@ -452,7 +480,7 @@ async function runRemove(module, options = {}) {
|
|
|
452
480
|
|
|
453
481
|
// 14. build_runner (only for modules whose removal changes codegen inputs)
|
|
454
482
|
const needsBuildRunner = [
|
|
455
|
-
'revenuecat', 'analytics', 'sentry', 'onboarding', '
|
|
483
|
+
'revenuecat', 'analytics', 'sentry', 'onboarding', 'ai_chat', 'feedback',
|
|
456
484
|
].includes(normalized);
|
|
457
485
|
if (needsBuildRunner) {
|
|
458
486
|
const spinner = ui.timedSpinner();
|
package/lib/commands/run.js
CHANGED
|
@@ -319,8 +319,8 @@ async function runRun(directory, options = {}) {
|
|
|
319
319
|
|
|
320
320
|
// Override RC keys based on device. We only touch RC_ANDROID_API_KEY /
|
|
321
321
|
// RC_IOS_API_KEY when the launch.json already declares them — that's the
|
|
322
|
-
// signal the project uses RevenueCat. Web
|
|
323
|
-
//
|
|
322
|
+
// signal the project uses RevenueCat. Web payments use Stripe (server-side
|
|
323
|
+
// only), so there are no web dart-defines to touch here.
|
|
324
324
|
const usesRc = dartDefines.some(
|
|
325
325
|
(a) => a.startsWith('--dart-define=RC_ANDROID_API_KEY=') ||
|
|
326
326
|
a.startsWith('--dart-define=RC_IOS_API_KEY='),
|
package/lib/commands/update.js
CHANGED
|
@@ -26,7 +26,7 @@ const execAsync = promisify(exec);
|
|
|
26
26
|
const CHANGELOG_PATH = path.join(__dirname, '..', 'scaffold', 'CHANGELOG.json');
|
|
27
27
|
|
|
28
28
|
const NEEDS_BUILD_RUNNER = [
|
|
29
|
-
'revenuecat', 'analytics', 'sentry', 'onboarding', '
|
|
29
|
+
'revenuecat', 'analytics', 'sentry', 'onboarding', 'ai_chat', 'feedback',
|
|
30
30
|
];
|
|
31
31
|
const COMPONENTS_UPDATE_TARGET = 'components';
|
|
32
32
|
const CORE_UPDATE_TARGET = 'core';
|
|
@@ -129,7 +129,7 @@ async function getActiveModules(kitSetup, projectDir) {
|
|
|
129
129
|
const featuresPath = path.join(projectDir, 'lib', 'core', 'config', 'features.dart');
|
|
130
130
|
if (await fs.pathExists(featuresPath)) {
|
|
131
131
|
const content = await fs.readFile(featuresPath, 'utf8');
|
|
132
|
-
if (/const bool
|
|
132
|
+
if (/const bool withAiChat\s*=\s*true/.test(content)) modules.push('ai_chat');
|
|
133
133
|
}
|
|
134
134
|
if (await fs.pathExists(path.join(projectDir, '.github', 'workflows'))) modules.push('ci');
|
|
135
135
|
const pubspecPath = path.join(projectDir, 'pubspec.yaml');
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.22.0": {
|
|
3
|
+
"modules": {
|
|
4
|
+
"components": {
|
|
5
|
+
"pt": "Navegação repaginada para desktop/web: novo KasyWebHeader, KasySidebar e KasyBottomBar mais limpos e consistentes, com KasyAppBar se adaptando ao desktop.",
|
|
6
|
+
"en": "Revamped desktop/web navigation: new KasyWebHeader, cleaner and more consistent KasySidebar and KasyBottomBar, with KasyAppBar adapting to desktop.",
|
|
7
|
+
"es": "Navegación renovada para escritorio/web: nuevo KasyWebHeader, KasySidebar y KasyBottomBar más limpios y consistentes, con KasyAppBar adaptándose al escritorio."
|
|
8
|
+
},
|
|
9
|
+
"core": {
|
|
10
|
+
"pt": "Cor do texto sobre o accent (accentForeground) agora é automática: ela se ajusta sozinha pela luminância do accent (clara em accents escuros, escura em claros), então trocar a cor da marca continua legível em ambos os temas.",
|
|
11
|
+
"en": "Text color on top of the accent (accentForeground) is now automatic: it adapts to the accent's luminance (light on dark accents, dark on light ones), so changing the brand color stays legible in both themes.",
|
|
12
|
+
"es": "El color del texto sobre el accent (accentForeground) ahora es automático: se ajusta solo según la luminancia del accent (claro en accents oscuros, oscuro en claros), así cambiar el color de marca sigue siendo legible en ambos temas."
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
2
16
|
"1.21.0": {
|
|
3
17
|
"modules": {
|
|
4
18
|
"components": {
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API (REST) project generator.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Thin wrapper over generateProject().
|
|
5
|
+
* The common generation logic (copy, pub get, slang, build_runner, flutterfire)
|
|
6
|
+
* lives in cli/lib/scaffold/generate.js.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
* 3.
|
|
12
|
-
* 4.
|
|
8
|
+
* Specific hook (applyBackendSetup):
|
|
9
|
+
* 1. Applies api/patch/ over the copied Firebase template
|
|
10
|
+
* 2. Replaces pubspec.yaml with the API template (pubspec.yaml.tpl)
|
|
11
|
+
* 3. Removes Firebase-only artifacts (feedbacks kept — the patch replaces the APIs)
|
|
12
|
+
* 4. Writes environment overrides (apiBaseUrl)
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const path = require('node:path');
|
|
16
16
|
const fs = require('fs-extra');
|
|
17
17
|
const { applyPatch } = require('../../engine');
|
|
18
18
|
const { generateProject } = require('../../generate');
|
|
19
|
-
const {
|
|
19
|
+
const { writeEnvironmentsOverrides } = require('../../shared/generator-utils');
|
|
20
20
|
const { removeBackendSpecificArtifacts } = require('../../shared/backend-config');
|
|
21
21
|
|
|
22
22
|
const API_PATCH_DIR = path.join(__dirname, 'patch');
|
|
@@ -34,7 +34,7 @@ async function generateApiProject(targetDir, options) {
|
|
|
34
34
|
// 1. Patch API (auth via HTTP, storage null, notifications via REST)
|
|
35
35
|
const { filesApplied } = await applyPatch(API_PATCH_DIR, dir, tokens, pathReplacements);
|
|
36
36
|
|
|
37
|
-
// 1b. README
|
|
37
|
+
// 1b. Backend README (excluded from applyPatch) — copy it manually
|
|
38
38
|
const language = opts.language ?? 'pt';
|
|
39
39
|
const readmeLang = ['en', 'pt', 'es'].includes(language) ? language : 'en';
|
|
40
40
|
const candidates = [
|
|
@@ -48,7 +48,7 @@ async function generateApiProject(targetDir, options) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// 2.
|
|
51
|
+
// 2. Replace pubspec.yaml with the API backend deps
|
|
52
52
|
const pubspecContent = await fs.readFile(API_PUBSPEC, 'utf8');
|
|
53
53
|
let pubspecReplaced = pubspecContent;
|
|
54
54
|
for (const [from, to] of Object.entries(tokens)) {
|
|
@@ -56,11 +56,11 @@ async function generateApiProject(targetDir, options) {
|
|
|
56
56
|
}
|
|
57
57
|
await fs.outputFile(path.join(dir, 'pubspec.yaml'), pubspecReplaced, 'utf8');
|
|
58
58
|
|
|
59
|
-
// 3.
|
|
59
|
+
// 3. Remove Firebase-only artifacts (feedbacks kept — the API patch replaces the APIs)
|
|
60
60
|
await removeBackendSpecificArtifacts(dir, 'api', opts.modules ?? []);
|
|
61
61
|
|
|
62
|
-
// 4.
|
|
63
|
-
await
|
|
62
|
+
// 4. Write environment overrides with the API URL
|
|
63
|
+
await writeEnvironmentsOverrides(dir, 'api', tokens, { apiBaseUrl: opts.apiBaseUrl });
|
|
64
64
|
|
|
65
65
|
return {
|
|
66
66
|
detail: `${filesApplied} patch files`,
|
|
@@ -79,6 +79,89 @@ Após editar qualquer `.i18n.json`, sempre rodar `dart run slang`.
|
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
82
|
+
## Admin (console interno)
|
|
83
|
+
|
|
84
|
+
O app tem um **console de admin** (aba Usuários, Solicitações, etc.) liberado só para quem tem `role == "admin"`. O `role` é um campo de **controle de acesso** que o **seu backend controla** — o app nunca pode escrever nele.
|
|
85
|
+
|
|
86
|
+
### Campo `role` no usuário
|
|
87
|
+
|
|
88
|
+
O seu endpoint de usuário (`GET /users/{id}`) deve devolver o `role` junto com os outros dados:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{ "id": "...", "email": "ana@b.com", "name": "Ana", "onboarded": true, "role": "admin" }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `role` ausente / `null` → usuário normal.
|
|
95
|
+
- `role: "admin"` → libera o console de admin.
|
|
96
|
+
|
|
97
|
+
**Regra de segurança (obrigatória):** o `role` só pode ser definido no servidor (banco/painel). O backend deve **rejeitar** qualquer tentativa do cliente de gravar `role` (ex.: num `PATCH /users/{id}`), senão qualquer pessoa vira admin. Defina-o manualmente no seu banco para promover alguém.
|
|
98
|
+
|
|
99
|
+
### Endpoint: listar usuários
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
GET /admin/users
|
|
103
|
+
Auth: Authorization: Bearer <token> (enviado automaticamente pelo app)
|
|
104
|
+
O servidor DEVE validar role == "admin" e responder 403 caso contrário.
|
|
105
|
+
|
|
106
|
+
200 OK:
|
|
107
|
+
{
|
|
108
|
+
"users": [
|
|
109
|
+
{
|
|
110
|
+
"id": "...",
|
|
111
|
+
"email": "ana@b.com" | null,
|
|
112
|
+
"name": "Ana" | null,
|
|
113
|
+
"createdAt": 1700000000000, // epoch em milissegundos | null
|
|
114
|
+
"subscriber": true // tem assinatura ativa?
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"totalUsers": 142, // tamanho real da coleção
|
|
118
|
+
"truncated": false // true quando há mais usuários do que os retornados
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Devolva os usuários **mais recentes** (com um limite, ex.: 1000). O app faz busca, ordenação e paginação localmente — por isso uma chamada só já basta e a experiência fica instantânea.
|
|
123
|
+
|
|
124
|
+
### Endpoint: moderar solicitações (aba Solicitações)
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
GET /admin/feature-requests → lista TODAS (ativas + ocultas), mais votadas primeiro
|
|
128
|
+
PATCH /admin/feature-requests/{id} body: {"active": true|false}
|
|
129
|
+
PATCH /admin/feature-requests/{id} body: {"title": {...}, "description": {...}}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Mesma regra: validar `role == "admin"` e responder 403 caso contrário. `title`/`description` são mapas por idioma (`{"en": "...", "pt": "...", "es": "..."}`).
|
|
133
|
+
|
|
134
|
+
### Endpoints: AI Chat (histórico de conversas)
|
|
135
|
+
|
|
136
|
+
O assistente guarda várias conversas por usuário, cada uma com várias mensagens.
|
|
137
|
+
O usuário é identificado pelo token `Authorization: Bearer`.
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
GET /ai-conversations → lista as conversas do usuário, mais recente primeiro
|
|
141
|
+
POST /ai-conversations → cria uma conversa vazia e devolve o objeto criado
|
|
142
|
+
DELETE /ai-conversations/{id} → apaga a conversa e todas as mensagens dela
|
|
143
|
+
GET /ai-conversations/{id}/messages → mensagens da conversa, mais antiga primeiro
|
|
144
|
+
POST /ai-conversations/{id}/messages body: {"role": "...", "content": "...", "created_at": "..."}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Formato de uma conversa (o "última mensagem" é desnormalizado para a lista ficar barata):
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"id": "...",
|
|
152
|
+
"created_at": "2026-01-01T12:00:00Z",
|
|
153
|
+
"updated_at": "2026-01-01T12:05:00Z",
|
|
154
|
+
"last_message_role": "user" | "assistant" | null,
|
|
155
|
+
"last_message_content": "..." | null
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Ao salvar uma mensagem, o servidor deve atualizar `updated_at`, `last_message_role` e
|
|
160
|
+
`last_message_content` da conversa. O streaming da resposta da IA continua no endpoint
|
|
161
|
+
`AI_CHAT_ENDPOINT` (SSE) — ele só recebe `message` + `history`, não persiste nada.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
82
165
|
## Segurança
|
|
83
166
|
|
|
84
167
|
O `.gitignore` já exclui: `.env`, `.env.*`, `*.pem`, `*.keystore`.
|