kasy-cli 1.31.13 → 1.32.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 (101) hide show
  1. package/lib/commands/new.js +15 -1
  2. package/lib/scaffold/CHANGELOG.json +9 -0
  3. package/lib/scaffold/backends/api/patch/README.md +87 -2
  4. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +34 -0
  5. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
  6. package/lib/scaffold/backends/firebase/setup-from-scratch.js +22 -0
  7. package/lib/scaffold/backends/supabase/deploy.js +5 -0
  8. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/README.md +26 -22
  9. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +8 -11
  10. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +3 -1
  11. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +60 -3
  12. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +69 -17
  13. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
  14. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +6 -0
  15. package/lib/scaffold/generate.js +1 -1
  16. package/lib/scaffold/shared/generator-utils.js +22 -3
  17. package/lib/utils/i18n/messages-en.js +2 -0
  18. package/lib/utils/i18n/messages-es.js +2 -0
  19. package/lib/utils/i18n/messages-pt.js +2 -0
  20. package/package.json +2 -2
  21. package/templates/firebase/docs/auth-setup.en.md +7 -1
  22. package/templates/firebase/docs/auth-setup.es.md +7 -1
  23. package/templates/firebase/docs/auth-setup.pt.md +7 -1
  24. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +79 -5
  25. package/templates/firebase/lib/components/kasy_accordion.dart +2 -2
  26. package/templates/firebase/lib/components/kasy_alert.dart +1 -1
  27. package/templates/firebase/lib/components/kasy_app_bar.dart +3 -3
  28. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +1 -1
  29. package/templates/firebase/lib/components/kasy_button.dart +8 -8
  30. package/templates/firebase/lib/components/kasy_chip.dart +1 -1
  31. package/templates/firebase/lib/components/kasy_date_picker.dart +26 -21
  32. package/templates/firebase/lib/components/kasy_dialog.dart +2 -2
  33. package/templates/firebase/lib/components/kasy_sidebar.dart +62 -11
  34. package/templates/firebase/lib/components/kasy_tabs.dart +2 -2
  35. package/templates/firebase/lib/components/kasy_text_area.dart +37 -5
  36. package/templates/firebase/lib/components/kasy_text_field.dart +77 -16
  37. package/templates/firebase/lib/components/kasy_toast.dart +1 -1
  38. package/templates/firebase/lib/components/kasy_web_header.dart +4 -3
  39. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +6 -0
  40. package/templates/firebase/lib/core/bottom_menu/notification_bottom_item.dart +16 -37
  41. package/templates/firebase/lib/core/config/features.dart +13 -0
  42. package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +21 -0
  43. package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +1 -1
  44. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +1 -1
  45. package/templates/firebase/lib/core/theme/icon_sizes.dart +47 -0
  46. package/templates/firebase/lib/core/theme/shadows.dart +13 -0
  47. package/templates/firebase/lib/core/theme/texts.dart +32 -0
  48. package/templates/firebase/lib/core/theme/theme.dart +2 -0
  49. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +3 -0
  50. package/templates/firebase/lib/core/web_viewport_scale.dart +23 -4
  51. package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +1 -1
  52. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -1
  53. package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +1 -1
  54. package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -1
  55. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +3 -1
  56. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +36 -14
  57. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +27 -11
  58. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +1 -1
  59. package/templates/firebase/lib/features/feedbacks/ui/feedback_page.dart +1 -1
  60. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +1 -1
  61. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -1
  62. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +22 -3
  63. package/templates/firebase/lib/features/home/home_image_grid.dart +1 -1
  64. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +2 -2
  65. package/templates/firebase/lib/features/notifications/providers/unread_notifications_count_provider.dart +17 -0
  66. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +6 -1
  67. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +35 -38
  68. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +1 -1
  69. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +1 -1
  70. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +3 -3
  71. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +1 -1
  72. package/templates/firebase/lib/features/settings/settings_page.dart +264 -307
  73. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
  74. package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +13 -6
  75. package/templates/firebase/lib/features/settings/ui/components/edit_name_sheet.dart +115 -0
  76. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +2 -2
  77. package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +1 -1
  78. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +13 -5
  79. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +12 -2
  80. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +7 -1
  81. package/templates/firebase/lib/features/subscriptions/providers/premium_page_provider.dart +11 -3
  82. package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -1
  83. package/templates/firebase/lib/features/subscriptions/ui/widgets/comparison_table.dart +1 -1
  84. package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +2 -2
  85. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_close_button.dart +1 -1
  86. package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +1 -1
  87. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +3 -3
  88. package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +1 -1
  89. package/templates/firebase/lib/i18n/en.i18n.json +10 -1
  90. package/templates/firebase/lib/i18n/es.i18n.json +10 -1
  91. package/templates/firebase/lib/i18n/pt.i18n.json +10 -1
  92. package/templates/firebase/pubspec.yaml +0 -1
  93. package/templates/firebase/web/stripe_success.html +64 -26
  94. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +0 -34
  95. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -67
  96. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -183
  97. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +0 -34
  98. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -67
  99. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -183
  100. package/templates/firebase/lib/features/authentication/ui/components/facebook_signin.dart +0 -19
  101. package/templates/firebase/login-redesign-preview.png +0 -0
@@ -2714,18 +2714,26 @@ Widget _buildTextFieldVariantsVariant(BuildContext context) {
2714
2714
  children: [
2715
2715
  KasyTextField(
2716
2716
  label: 'Primary Variant',
2717
- hint: 'Primary style input',
2717
+ hint: 'Primary style text field',
2718
2718
  description: 'Default variant with primary styling',
2719
2719
  contentType: KasyTextFieldContentType.email,
2720
2720
  ),
2721
2721
  SizedBox(height: KasySpacing.lg),
2722
2722
  KasyTextField(
2723
2723
  label: 'Secondary Variant',
2724
- hint: 'Secondary style input',
2724
+ hint: 'Secondary style text field',
2725
2725
  description: 'Secondary variant for surfaces',
2726
2726
  variant: KasyTextFieldVariant.secondary,
2727
2727
  contentType: KasyTextFieldContentType.email,
2728
2728
  ),
2729
+ SizedBox(height: KasySpacing.lg),
2730
+ KasyTextField(
2731
+ label: 'Flat Variant',
2732
+ hint: 'Flat style text field',
2733
+ description: 'Primary fill, hairline border, no shadow',
2734
+ variant: KasyTextFieldVariant.flat,
2735
+ contentType: KasyTextFieldContentType.email,
2736
+ ),
2729
2737
  ],
2730
2738
  );
2731
2739
  }
@@ -2738,7 +2746,7 @@ Widget _buildTextFieldStates(BuildContext context) {
2738
2746
  KasyTextField(
2739
2747
  label: 'Default State',
2740
2748
  hint: 'Enter your email',
2741
- description: 'Normal input state',
2749
+ description: 'Normal text field state',
2742
2750
  contentType: KasyTextFieldContentType.email,
2743
2751
  ),
2744
2752
  SizedBox(height: KasySpacing.lg),
@@ -9013,6 +9021,7 @@ class _DatePickerVariantsPreview extends StatefulWidget {
9013
9021
  class _DatePickerVariantsPreviewState
9014
9022
  extends State<_DatePickerVariantsPreview> {
9015
9023
  DateTime? _filledDate;
9024
+ DateTime? _flatDate;
9016
9025
  DateTime? _noBarrierDate;
9017
9026
  DateTime? _noSuffixDate;
9018
9027
  DateTime? _noFocusDate;
@@ -9035,6 +9044,16 @@ class _DatePickerVariantsPreviewState
9035
9044
  locale: locale,
9036
9045
  ),
9037
9046
  const SizedBox(height: KasySpacing.lg),
9047
+ // Flat — surface fill + hairline border, no shadow (KasyTextField.flat).
9048
+ KasyDatePicker(
9049
+ label: 'Flat (no shadow)',
9050
+ placeholder: 'Choose a date',
9051
+ value: _flatDate,
9052
+ onChanged: (d) => setState(() => _flatDate = d),
9053
+ variant: KasyTextFieldVariant.flat,
9054
+ locale: locale,
9055
+ ),
9056
+ const SizedBox(height: KasySpacing.lg),
9038
9057
  // Popover without the dimmed backdrop — feels lighter / tooltip-like.
9039
9058
  KasyDatePicker(
9040
9059
  label: 'No backdrop',
@@ -396,7 +396,7 @@ class _LikeButtonState extends State<_LikeButton>
396
396
  scale: _scale,
397
397
  child: Icon(
398
398
  widget.liked ? KasyIcons.favoriteFilled : KasyIcons.favorite,
399
- size: 18,
399
+ size: KasyIconSize.md,
400
400
  color: heart,
401
401
  ),
402
402
  ),
@@ -194,7 +194,7 @@ class _TimeTile extends StatelessWidget {
194
194
  const Spacer(),
195
195
  Icon(
196
196
  KasyIcons.arrowForwardIos,
197
- size: 14,
197
+ size: KasyIconSize.xs,
198
198
  color: context.colors.muted,
199
199
  ),
200
200
  ],
@@ -299,7 +299,7 @@ class _DateTile extends StatelessWidget {
299
299
  const Spacer(),
300
300
  Icon(
301
301
  KasyIcons.arrowForwardIos,
302
- size: 14,
302
+ size: KasyIconSize.xs,
303
303
  color: context.colors.muted,
304
304
  ),
305
305
  ],
@@ -0,0 +1,17 @@
1
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
2
+ import 'package:kasy_kit/core/states/user_state_notifier.dart';
3
+ import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
4
+
5
+ /// Live count of the signed-in user's unread notifications. Emits 0 when there
6
+ /// is no user. Shared source for badging the notifications item everywhere it
7
+ /// appears in the navigation (bottom bar, sidebar).
8
+ ///
9
+ /// Watches the user id, so it automatically re-subscribes to the right stream
10
+ /// when the account changes (avoids leaking the previous user's count).
11
+ final unreadNotificationsCountProvider = StreamProvider.autoDispose<int>((ref) {
12
+ final String? userId = ref.watch(userStateNotifierProvider).user.idOrNull;
13
+ if (userId == null) return Stream<int>.value(0);
14
+ return ref
15
+ .read(notificationRepositoryProvider)
16
+ .listenToUnreadNotificationsCount(userId);
17
+ });
@@ -1,3 +1,4 @@
1
+ import 'package:flutter/foundation.dart' show kIsWeb;
1
2
  import 'package:flutter/material.dart';
2
3
  import 'package:flutter_riverpod/flutter_riverpod.dart';
3
4
  import 'package:kasy_kit/components/components.dart';
@@ -52,7 +53,7 @@ class _EmptyNotificationsState extends ConsumerState<EmptyNotifications> {
52
53
  ),
53
54
  child: Icon(
54
55
  KasyIcons.notificationOff,
55
- size: 36,
56
+ size: KasyIconSize.display,
56
57
  color: context.colors.primary.withValues(alpha: 0.6),
57
58
  ),
58
59
  ),
@@ -79,6 +80,10 @@ class _EmptyNotificationsState extends ConsumerState<EmptyNotifications> {
79
80
  builder: (context, snapshot) {
80
81
  final permission = snapshot.data;
81
82
  if (permission == null) return const SizedBox.shrink();
83
+ // Web has no usable notification-permission prompt (askPermission is a
84
+ // no-op on web and permission_handler has no web support), so the CTA
85
+ // would be a dead button. Show the empty state without it.
86
+ if (kIsWeb) return const SizedBox.shrink();
82
87
  if (permission is NotificationPermissionGranted) {
83
88
  return const SizedBox.shrink();
84
89
  }
@@ -1,6 +1,6 @@
1
- import 'package:better_skeleton/better_skeleton.dart';
2
1
  import 'package:flutter/material.dart';
3
2
  import 'package:jiffy/jiffy.dart';
3
+ import 'package:kasy_kit/components/kasy_skeleton.dart';
4
4
  import 'package:kasy_kit/core/theme/theme.dart';
5
5
  import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
6
6
  import 'package:kasy_kit/features/notifications/providers/models/notification.dart'
@@ -307,54 +307,51 @@ class TileNotificationIcon extends StatelessWidget {
307
307
  ),
308
308
  child: Icon(
309
309
  active ? KasyIcons.notificationActive : KasyIcons.notification,
310
- size: 20,
310
+ size: KasyIconSize.lg,
311
311
  color: color,
312
312
  ),
313
313
  );
314
314
  }
315
315
  }
316
316
 
317
- /// A skeleton for the [NotificationTile] widget.
318
- class NotificationSkeletonTile extends StatefulWidget {
317
+ /// Skeleton placeholder for [NotificationTile], built from the design-system
318
+ /// [KasySkeleton] bones so the loading shimmer matches the kit everywhere and
319
+ /// mirrors the real tile's layout (leading badge + title / body / date lines).
320
+ class NotificationSkeletonTile extends StatelessWidget {
319
321
  const NotificationSkeletonTile({super.key});
320
322
 
321
- @override
322
- State<NotificationSkeletonTile> createState() =>
323
- _NotificationSkeletonTileState();
324
- }
325
-
326
- class _NotificationSkeletonTileState extends State<NotificationSkeletonTile>
327
- with SingleTickerProviderStateMixin {
328
- late final AnimationController animationController;
329
-
330
- @override
331
- void initState() {
332
- super.initState();
333
- animationController = AnimationController(
334
- vsync: this,
335
- duration: const Duration(milliseconds: 1000),
336
- )..repeat();
337
- }
338
-
339
- @override
340
- void deactivate() {
341
- animationController.dispose();
342
- super.deactivate();
343
- }
344
-
345
323
  @override
346
324
  Widget build(BuildContext context) {
347
- return Padding(
348
- padding: const EdgeInsets.only(top: KasySpacing.sm),
349
- child: AnimatedSkeleton(
350
- listenable: animationController,
351
- child: Container(
352
- height: 80,
353
- decoration: BoxDecoration(
354
- color: context.colors.primary.withValues(alpha: .1),
355
- borderRadius: KasyRadius.smBorderRadius,
325
+ return const Padding(
326
+ padding: EdgeInsets.only(top: KasySpacing.sm),
327
+ child: KasySkeletonGroup(
328
+ child: Padding(
329
+ padding: EdgeInsets.all(KasySpacing.md),
330
+ child: Row(
331
+ crossAxisAlignment: CrossAxisAlignment.start,
332
+ children: [
333
+ KasySkeleton(
334
+ width: 40,
335
+ height: 40,
336
+ borderRadius: BorderRadius.all(Radius.circular(10)),
337
+ ),
338
+ SizedBox(width: KasySpacing.smd),
339
+ Expanded(
340
+ child: Column(
341
+ crossAxisAlignment: CrossAxisAlignment.start,
342
+ children: [
343
+ KasySkeleton(width: 150, height: 13),
344
+ SizedBox(height: KasySpacing.sm),
345
+ KasySkeleton(width: double.infinity, height: 11),
346
+ SizedBox(height: KasySpacing.xs),
347
+ KasySkeleton(width: 210, height: 11),
348
+ SizedBox(height: KasySpacing.smd),
349
+ KasySkeleton(width: 70, height: 10),
350
+ ],
351
+ ),
352
+ ),
353
+ ],
356
354
  ),
357
- padding: const EdgeInsets.all(KasySpacing.md),
358
355
  ),
359
356
  ),
360
357
  );
@@ -76,7 +76,7 @@ class PermissionRequestView extends StatelessWidget {
76
76
  child: Center(
77
77
  child:
78
78
  illustration ??
79
- KasyBrandBadge(icon: icon, size: 76, glyphSize: 36),
79
+ KasyBrandBadge(icon: icon, size: 76, glyphSize: KasyIconSize.display),
80
80
  ),
81
81
  ),
82
82
  const SizedBox(height: KasySpacing.xl),
@@ -102,7 +102,7 @@ class _PulsingOrb extends StatelessWidget {
102
102
  ),
103
103
  ],
104
104
  ),
105
- child: Icon(KasyIcons.flash, color: colors.onPrimary, size: 34),
105
+ child: Icon(KasyIcons.flash, color: colors.onPrimary, size: KasyIconSize.display),
106
106
  )
107
107
  .animate(onPlay: (c) => c.repeat(reverse: true))
108
108
  .scaleXY(
@@ -283,7 +283,7 @@ class _PlanRow extends StatelessWidget {
283
283
  ),
284
284
  ),
285
285
  child: selected
286
- ? Icon(KasyIcons.check, size: 12, color: colors.onPrimary)
286
+ ? Icon(KasyIcons.check, size: KasyIconSize.xxs, color: colors.onPrimary)
287
287
  : null,
288
288
  ),
289
289
  const SizedBox(width: KasySpacing.smd),
@@ -421,7 +421,7 @@ class _FakeField extends StatelessWidget {
421
421
  ),
422
422
  child: Row(
423
423
  children: [
424
- Icon(icon, size: 16, color: colors.muted),
424
+ Icon(icon, size: KasyIconSize.sm, color: colors.muted),
425
425
  const SizedBox(width: KasySpacing.sm),
426
426
  Text(
427
427
  hint,
@@ -452,7 +452,7 @@ class _SocialChip extends StatelessWidget {
452
452
  borderRadius: KasyRadius.mdBorderRadius,
453
453
  border: Border.all(color: colors.outline),
454
454
  ),
455
- child: Icon(icon, size: 18, color: colors.onSurface),
455
+ child: Icon(icon, size: KasyIconSize.md, color: colors.onSurface),
456
456
  );
457
457
  }
458
458
  }
@@ -391,7 +391,7 @@ class RoundRadioBox extends StatelessWidget {
391
391
  opacity: iconOpacity,
392
392
  child: Transform.scale(
393
393
  scale: iconSize,
394
- child: Icon(icon, color: context.colors.onPrimary, size: 15),
394
+ child: Icon(icon, color: context.colors.onPrimary, size: KasyIconSize.sm),
395
395
  ),
396
396
  )
397
397
  : const SizedBox.shrink(),