kasy-cli 1.32.0 → 1.35.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/README.md +1 -1
- package/bin/kasy.js +66 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/apple-web.js +222 -0
- package/lib/commands/configure.js +3 -91
- package/lib/commands/doctor.js +20 -0
- package/lib/commands/facebook.js +189 -0
- package/lib/commands/new.js +61 -11
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +199 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/deploy.js +92 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +92 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/scaffold/shared/generator-utils.js +18 -6
- package/lib/utils/apple-web.js +147 -0
- package/lib/utils/facebook.js +162 -0
- package/lib/utils/i18n/messages-en.js +85 -0
- package/lib/utils/i18n/messages-es.js +85 -0
- package/lib/utils/i18n/messages-pt.js +85 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +170 -0
- package/templates/firebase/CLAUDE.md +16 -0
- package/templates/firebase/DESIGN_SYSTEM.md +269 -0
- package/templates/firebase/docs/auth-setup.en.md +4 -2
- package/templates/firebase/docs/auth-setup.es.md +4 -2
- package/templates/firebase/docs/auth-setup.pt.md +4 -2
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +35 -17
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_screen.dart +114 -0
- package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +108 -73
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/chrome/chrome_visibility.dart +22 -0
- package/templates/firebase/lib/core/config/features.dart +5 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +48 -124
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +29 -126
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +11 -8
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +21 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +266 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +80 -15
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +20 -14
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -2
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +8 -1
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +186 -56
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +169 -208
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +2 -2
- package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +21 -8
- package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +2 -2
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -4
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +84 -128
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +3 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +2 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +2 -1
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +58 -21
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +54 -7
- package/templates/firebase/lib/i18n/es.i18n.json +54 -7
- package/templates/firebase/lib/i18n/pt.i18n.json +54 -7
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +94 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/tool/design_check.dart +152 -0
- package/templates/firebase/web/index.html +162 -14
- package/templates/firebase/assets/images/review.png +0 -0
- package/templates/firebase/assets/images/update.png +0 -0
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
- package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +0 -32
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# AGENTS.md — working in this codebase
|
|
2
|
+
|
|
3
|
+
This project was generated with the **Kasy** kit. This file orients any AI coding
|
|
4
|
+
assistant (Claude Code, Cursor, GitHub Copilot, …). Read it before generating
|
|
5
|
+
code. Claude Code users: `CLAUDE.md` points here.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
A production-ready Flutter app (web, iOS, Android) built on the **Kasy design
|
|
10
|
+
system** and a **feature-first** architecture. It was generated with **one**
|
|
11
|
+
backend — Firebase, Supabase, or a REST API. Only the **data layer** differs
|
|
12
|
+
between backends; the UI, state, i18n and design system are identical. So almost
|
|
13
|
+
everything below is backend-agnostic.
|
|
14
|
+
|
|
15
|
+
## Golden rules (every time)
|
|
16
|
+
|
|
17
|
+
1. **Use the design system, never raw values.** No hardcoded `fontSize`,
|
|
18
|
+
`Color(0x…)`, `Colors.*`, or literal padding / radius / icon sizes. Use
|
|
19
|
+
`context.textTheme.*` / `KasyTextTheme.*`, `context.colors.*`,
|
|
20
|
+
`KasySpacing.*`, `KasyRadius.*`, `KasyIconSize.*`. Full reference:
|
|
21
|
+
**`DESIGN_SYSTEM.md`**.
|
|
22
|
+
2. **Use kit components, not raw Material.** `KasyButton` (not
|
|
23
|
+
ElevatedButton / TextButton), `showKasyConfirmDialog` (not AlertDialog),
|
|
24
|
+
`showKasyToast` (not SnackBar), plus `KasyCard`, `KasyTextField`,
|
|
25
|
+
`KasyScreen`, etc.
|
|
26
|
+
3. **The Home screen is the visual reference** — clean, minimal, lots of
|
|
27
|
+
surface, one accent colour used sparingly. New screens should mirror it.
|
|
28
|
+
4. **English everywhere** in code, comments and identifiers. **No hardcoded
|
|
29
|
+
user-facing strings** — use slang i18n (`context.t.…`).
|
|
30
|
+
5. **Package imports only**: `import 'package:kasy_kit/…';`, never relative.
|
|
31
|
+
6. **Zero analyzer issues.** Run `flutter analyze` and fix everything before
|
|
32
|
+
considering a change done.
|
|
33
|
+
|
|
34
|
+
## Design system, components & responsiveness
|
|
35
|
+
|
|
36
|
+
This kit is an **organized, opinionated** design system — not a blank canvas.
|
|
37
|
+
Before building anything, browse the live **Design System** screen
|
|
38
|
+
(`lib/features/home/design_system_page.dart`) and the **Components** gallery:
|
|
39
|
+
colours, typography, spacing and dozens of ready widgets (`KasyButton`,
|
|
40
|
+
`KasyCard`, `KasyTextField`, `KasyScreen`, `showKasyToast`,
|
|
41
|
+
`showKasyConfirmDialog`, …). Reuse one before writing a new widget. Full token
|
|
42
|
+
reference: **`DESIGN_SYSTEM.md`**.
|
|
43
|
+
|
|
44
|
+
**Responsiveness is deliberate, not improvised.** There are exactly three
|
|
45
|
+
breakpoints and the type sizes are pinned per breakpoint, so the UI never
|
|
46
|
+
oscillates or guesses:
|
|
47
|
+
|
|
48
|
+
| Breakpoint | Width | Notes |
|
|
49
|
+
| ---------- | ---------- | ------------------------------------------------ |
|
|
50
|
+
| Mobile | `< 768` | native iOS/Android + phone-width web |
|
|
51
|
+
| Tablet | `768–1024` | |
|
|
52
|
+
| Desktop | `>= 1024` | authored baseline |
|
|
53
|
+
|
|
54
|
+
- **Typography** (`KasyTypeScale`): each role has an **explicit size per
|
|
55
|
+
breakpoint** — headings are largest on desktop and step down on smaller
|
|
56
|
+
screens; body/labels stay constant. It is applied globally by
|
|
57
|
+
`ResponsiveTextTheme` — **screens just use `context.textTheme.*` /
|
|
58
|
+
`context.kasyTextTheme.*` and the right size comes out**. Don't hardcode
|
|
59
|
+
`fontSize`; don't invent your own breakpoints.
|
|
60
|
+
- **Native mobile** is straightforward: it's the Mobile breakpoint and never
|
|
61
|
+
scales the viewport.
|
|
62
|
+
- **Web render scale** (`WebViewportScale`) is a *separate* concern: Flutter web
|
|
63
|
+
renders ~10% large, so **desktop only** scales the whole UI to `0.95`. Tablet
|
|
64
|
+
and mobile web render at natural size, like native. This is NOT the typography
|
|
65
|
+
scale — don't conflate the two.
|
|
66
|
+
|
|
67
|
+
To re-tune sizes, edit `KasyTypeScale` in `lib/core/theme/type_scale.dart` —
|
|
68
|
+
nothing else. The live ramp (tabs per breakpoint) is under **Design System →
|
|
69
|
+
Typography**.
|
|
70
|
+
|
|
71
|
+
## Architecture (feature-first, three layers)
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
lib/
|
|
75
|
+
├── components/ # Kasy design-system widgets (KasyButton, KasyCard, …)
|
|
76
|
+
├── core/ # cross-cutting: theme/, data/, states/, security/, widgets/, i18n
|
|
77
|
+
└── features/
|
|
78
|
+
└── <feature>/
|
|
79
|
+
├── api/ # data source (Firebase / Supabase / REST) → returns entities
|
|
80
|
+
├── repositories/ # entities → domain models (business logic)
|
|
81
|
+
├── providers/ # Riverpod notifiers → immutable page state
|
|
82
|
+
└── ui/ # pages, components (use provider + domain), widgets (dumb)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **Data flow:** `api → repository → provider (notifier) → ui`. Only repositories
|
|
86
|
+
use api classes. The api returns entities; repositories return domain models.
|
|
87
|
+
- **State:** a Riverpod notifier exposes an immutable (freezed) state; the view
|
|
88
|
+
`ref.watch`es it and triggers actions. Use `.when` / `.map` for
|
|
89
|
+
data / loading / error.
|
|
90
|
+
- **Backend note:** the `api` layer is the only backend-specific code. A new
|
|
91
|
+
data-backed feature follows the same `api → repo → provider` chain the
|
|
92
|
+
existing features use.
|
|
93
|
+
|
|
94
|
+
## Building a screen or component — checklist
|
|
95
|
+
|
|
96
|
+
1. Read `DESIGN_SYSTEM.md` (tokens, typography roles, components).
|
|
97
|
+
2. Reuse a kit component before building one. Wrap new pages in `KasyScreen`
|
|
98
|
+
(or mirror an existing page's scaffold).
|
|
99
|
+
3. Use semantic typography roles (`pageTitle`, `sectionTitle`, `rowTitle`, …)
|
|
100
|
+
and tokens for **every** size, colour and spacing.
|
|
101
|
+
4. Strings via `context.t.*` (add keys to the i18n JSON, never inline text).
|
|
102
|
+
5. `flutter analyze` clean. Optional: `dart run tool/design_check.dart` flags
|
|
103
|
+
any raw values that slipped into `lib/features`.
|
|
104
|
+
|
|
105
|
+
## Platform notes (native-only features)
|
|
106
|
+
|
|
107
|
+
Some features only work on a real iOS / Android build and are gated with
|
|
108
|
+
`kIsWeb`. They never run on web; where the user explicitly triggers them, a
|
|
109
|
+
"native app only" toast is shown instead of failing silently.
|
|
110
|
+
|
|
111
|
+
- **In-app review / store rating** — sends the user to the App Store / Play
|
|
112
|
+
Store. It is never shown on web. **iOS requires the numeric App Store ID**:
|
|
113
|
+
run `kasy configure` and fill `App Store ID (numeric)`. Android works
|
|
114
|
+
automatically from the package name. Until the ID is set, iOS store review
|
|
115
|
+
throws at runtime.
|
|
116
|
+
- **Push / FCM token, notification permission** — native APIs; on web the admin
|
|
117
|
+
actions surface the "native app only" toast.
|
|
118
|
+
- **App update prompts** — two distinct flows, both native-only and driven by
|
|
119
|
+
Firebase Remote Config (works on every backend): **"update available"** (shown
|
|
120
|
+
*before*, when the installed version is below `app_latest_version` / blocking
|
|
121
|
+
below `app_min_version`, sends the user to the store) and **"what's new"**
|
|
122
|
+
(shown *after* updating). See `lib/core/app_update/` and the README.
|
|
123
|
+
|
|
124
|
+
## Optional design guard-rail
|
|
125
|
+
|
|
126
|
+
`dart run tool/design_check.dart` scans `lib/features` for raw values
|
|
127
|
+
(hardcoded fonts / colours / Material widgets). It is **opt-in and
|
|
128
|
+
non-blocking** — relax it, wire it into CI, or delete it freely. Per-line
|
|
129
|
+
escape hatch: `// design-check: ignore`.
|
|
130
|
+
|
|
131
|
+
## Updating the kit (merging new versions with your changes)
|
|
132
|
+
|
|
133
|
+
The project is a scaffold: the kit code was copied into it. Improvements ship via
|
|
134
|
+
the CLI, which **overwrites** files (it does not auto-merge). The safety net is git.
|
|
135
|
+
|
|
136
|
+
Recommended flow when the user wants newer kit code:
|
|
137
|
+
|
|
138
|
+
1. **Commit first** so the working tree is clean (the CLI also warns about this).
|
|
139
|
+
2. Run the relevant command:
|
|
140
|
+
- `kasy update` — shows what's new for the features in use
|
|
141
|
+
- `kasy update core` / `kasy update components` — refreshes core files / UI components
|
|
142
|
+
- `kasy update <feature>` — e.g. `revenuecat`, `ai_chat`, `auth` (re-applies that feature)
|
|
143
|
+
3. The command overwrites the files and prints which ones changed.
|
|
144
|
+
4. **Merge:** if the user had customized any overwritten file, reconcile their version
|
|
145
|
+
(in the previous git commit) with the new kit version. As the in-project agent,
|
|
146
|
+
this is your job: read `git diff` (or `git show HEAD:<file>` for the user's prior
|
|
147
|
+
version) and produce a result that keeps BOTH the user's customizations and the new
|
|
148
|
+
kit improvements. Prefer additive merges; never silently drop the user's logic.
|
|
149
|
+
When unsure which side wins for a hunk, ask the user.
|
|
150
|
+
|
|
151
|
+
This is why committing before `kasy update` matters: it makes the user's prior version
|
|
152
|
+
recoverable so the merge is a normal 3-way reconcile, not a guess.
|
|
153
|
+
|
|
154
|
+
## Debugging when something doesn't work
|
|
155
|
+
|
|
156
|
+
Diagnose with real signals, not guesses:
|
|
157
|
+
|
|
158
|
+
- **App logs first.** Key steps (auth, service init) are logged via `Logger` — read
|
|
159
|
+
the `kasy run` terminal / browser console.
|
|
160
|
+
- **Then the backend logs.** Supabase: Dashboard → Logs (Auth / Edge Functions /
|
|
161
|
+
Postgres). Firebase: Console → Functions logs / Authentication.
|
|
162
|
+
- **Auth / token errors:** decode the JWT (base64url-decode the middle segment) and
|
|
163
|
+
inspect `sub` / `aud` / `iss`. A `missing sub claim` usually means the wrong
|
|
164
|
+
session/token was sent (e.g. the publishable `anon` key), not a bad provider token.
|
|
165
|
+
- **`kasy doctor`** checks common setup gaps (Google SHA-1, authorized domains,
|
|
166
|
+
provider config).
|
|
167
|
+
- **Web vs native differ:** web runs in `authRequired` mode (no anonymous user);
|
|
168
|
+
native is anonymous-first. Account-linking only applies when a real session exists.
|
|
169
|
+
|
|
170
|
+
Add a temporary `print(...)` to inspect a runtime value, then remove it.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This project shares one AI-guidance file across tools. **Read `AGENTS.md`** in
|
|
4
|
+
this directory first — it defines the architecture, the Kasy design system, and
|
|
5
|
+
the conventions to follow before generating any code.
|
|
6
|
+
|
|
7
|
+
Quick reminders:
|
|
8
|
+
|
|
9
|
+
- Use design-system tokens, never raw values — see `DESIGN_SYSTEM.md`
|
|
10
|
+
(`context.textTheme.*`, `context.colors.*`, `KasySpacing.*`, `KasyRadius.*`,
|
|
11
|
+
`KasyIconSize.*`).
|
|
12
|
+
- Use Kasy components (`KasyButton`, `KasyCard`, `showKasyToast`,
|
|
13
|
+
`showKasyConfirmDialog`, …), not raw Material.
|
|
14
|
+
- The Home screen is the visual reference: clean, minimal, one accent colour.
|
|
15
|
+
- English in code; user strings via `context.t.*` (slang i18n).
|
|
16
|
+
- Run `flutter analyze` and keep it clean.
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Kasy Design System
|
|
2
|
+
|
|
3
|
+
**Version 1.0.0** · last updated 2026-06-11
|
|
4
|
+
|
|
5
|
+
The single source of truth for how the app looks. Everything here lives in code
|
|
6
|
+
under `lib/core/theme/` and `lib/components/`, so changing a token in one place
|
|
7
|
+
updates the whole app (web, Android, iOS) at once. This document just writes the
|
|
8
|
+
rules down so anyone, including projects generated by the Kasy CLI, inherits the
|
|
9
|
+
same standard without guessing.
|
|
10
|
+
|
|
11
|
+
Design principle: **clean, modern, minimal**. Lots of surface, one accent colour
|
|
12
|
+
used sparingly, no loud weights. The Home screen (sidebar + header + elevated
|
|
13
|
+
`KasyCard`) is the reference for "what good looks like"; new screens should
|
|
14
|
+
mirror it.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## The golden rule
|
|
19
|
+
|
|
20
|
+
Use **tokens and roles**, never bespoke values.
|
|
21
|
+
|
|
22
|
+
| Instead of… | Use… |
|
|
23
|
+
| ----------------------------------- | -------------------------------------------- |
|
|
24
|
+
| `fontSize: 16, fontWeight: w600` | `context.textTheme.*` / `KasyTextTheme.*` |
|
|
25
|
+
| `Icon(size: 20)` | `Icon(size: KasyIconSize.lg)` |
|
|
26
|
+
| `EdgeInsets.all(16)` | `EdgeInsets.all(KasySpacing.md)` |
|
|
27
|
+
| `BorderRadius.circular(16)` | `BorderRadius.circular(KasyRadius.lg)` |
|
|
28
|
+
| `Color(0xFF…)` / `Colors.*` | `context.colors.*` |
|
|
29
|
+
| `ElevatedButton`, `TextButton` | `KasyButton` |
|
|
30
|
+
| `AlertDialog`, `SnackBar` | `showKasyConfirmDialog`, `showKasyToast` |
|
|
31
|
+
|
|
32
|
+
A bespoke number is only acceptable when a component is being *calibrated* (the
|
|
33
|
+
sidebar, the date-picker grid, badges) and there is no token that fits. When in
|
|
34
|
+
doubt, add or reuse a role rather than hardcoding.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Typography — `KasyTextTheme` (`lib/core/theme/texts.dart`)
|
|
39
|
+
|
|
40
|
+
### HeroUI scale (the raw roles, Inter)
|
|
41
|
+
|
|
42
|
+
| Role | Size / weight / line-height |
|
|
43
|
+
| --------------- | --------------------------- |
|
|
44
|
+
| `heading1` | 36 / w800 / 40 |
|
|
45
|
+
| `heading2` | 24 / w700 / 32 |
|
|
46
|
+
| `heading3` | 20 / w600 / 28 |
|
|
47
|
+
| `heading4` | 16 / w600 / 24 |
|
|
48
|
+
| `bodyBase` | 16 / w400 |
|
|
49
|
+
| `bodyBaseMedium`| 16 / w500 |
|
|
50
|
+
| `bodySm` | 14 / w400 |
|
|
51
|
+
| `bodySmMedium` | 14 / w500 |
|
|
52
|
+
| `bodyXs` | 12 / w400 |
|
|
53
|
+
| `bodyXsMedium` | 12 / w500 |
|
|
54
|
+
|
|
55
|
+
### Semantic app roles (prefer these on screens)
|
|
56
|
+
|
|
57
|
+
| Role | Maps to | Use for |
|
|
58
|
+
| -------------- | ------------------------------ | ----------------------------------------- |
|
|
59
|
+
| `pageTitle` | `heading2` (24/w700) | Screen / page titles, onboarding titles |
|
|
60
|
+
| `sectionTitle` | `heading4` (16/w600) | Detail / section headings |
|
|
61
|
+
| `sectionLabel` | 12 / w600 / letter-spacing 0.5 | Uppercase group eyebrows (lists, settings)|
|
|
62
|
+
| `rowTitle` | `bodySmMedium` (14/w500) | List / settings row titles |
|
|
63
|
+
| `rowValue` | `bodySm` (14/w400) | List / settings row values |
|
|
64
|
+
| `cardTitle` | `bodySmMedium` (14/w500) | Card titles |
|
|
65
|
+
| `cardSubtitle` | `bodyXs` (12/w400) | Card secondary text |
|
|
66
|
+
| `caption` | `bodyXs` (12/w400) | Captions, helper text |
|
|
67
|
+
|
|
68
|
+
Access the semantic roles via `context.kasyTextTheme.*` (colourless — apply
|
|
69
|
+
colour with `.copyWith`). Access Material slots via `context.textTheme.*`
|
|
70
|
+
(colour already applied). The raw scale roles are also exposed as
|
|
71
|
+
`KasyTextTheme.*` statics, but those are **fixed** (the desktop baseline, for
|
|
72
|
+
specs/docs) — use the `context.*` accessors in live UI so text scales.
|
|
73
|
+
|
|
74
|
+
Material → role mapping: `headlineMedium = heading2`, `headlineSmall =
|
|
75
|
+
titleLarge = heading3`, `titleMedium = heading4`.
|
|
76
|
+
|
|
77
|
+
The chrome/app-bar title is **w700** (not w900). Black weights read as heavy and
|
|
78
|
+
fight the rest of the UI.
|
|
79
|
+
|
|
80
|
+
### Responsive type — `KasyTypeScale` (`lib/core/theme/type_scale.dart`)
|
|
81
|
+
|
|
82
|
+
Not a blind multiplier: **every role declares an explicit size per breakpoint**
|
|
83
|
+
(the way Figma variables / modes do). The professional pattern — **headings are
|
|
84
|
+
largest on desktop and step down on smaller viewports** (a 36px title would feel
|
|
85
|
+
huge and wrap badly on a phone), while **body and labels stay constant** for
|
|
86
|
+
stable reading everywhere. Desktop is the authored reference.
|
|
87
|
+
|
|
88
|
+
| Role (example) | Mobile `< 768` | Tablet `768–1024` | Desktop `>= 1024` |
|
|
89
|
+
| -------------- | -------------- | ----------------- | ----------------- |
|
|
90
|
+
| `heading1` | 28 | 32 | 36 |
|
|
91
|
+
| `heading2` | 22 | 23 | 24 |
|
|
92
|
+
| `heading3` | 18 | 19 | 20 |
|
|
93
|
+
| `bodyBase` | 16 | 16 | 16 |
|
|
94
|
+
| `bodySm` | 14 | 14 | 14 |
|
|
95
|
+
|
|
96
|
+
`ResponsiveTextTheme` (innermost wrapper in `main.dart`) reads the viewport
|
|
97
|
+
width, picks the breakpoint and republishes a `Theme` whose Material `textTheme`
|
|
98
|
+
and `KasyTextTheme` extension use that breakpoint's sizes. Every
|
|
99
|
+
`context.textTheme.*` and `context.kasyTextTheme.*` inherits it — **no screen
|
|
100
|
+
does anything**; desktop is a pass-through (the authored baseline). Component
|
|
101
|
+
themes (buttons, inputs) keep their calibrated sizes on purpose. To re-tune the
|
|
102
|
+
whole app, edit the numbers in `KasyTypeScale` — nothing else. The live ramp is
|
|
103
|
+
visible under **Design System → Typography** (tabs per breakpoint).
|
|
104
|
+
|
|
105
|
+
**Web render scale (`WebViewportScale`, `lib/core/web_viewport_scale.dart`).** A
|
|
106
|
+
separate concern from type sizes: Flutter web renders ~10% larger than an
|
|
107
|
+
equivalent HTML app, so on **desktop only** (`>= 1024`) the whole UI is scaled to
|
|
108
|
+
`0.95` — a true viewport scale, so layout, scrolling and hit-testing stay
|
|
109
|
+
correct. Tablet and mobile web render at natural size, exactly like the native
|
|
110
|
+
build, which never scales.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Icon sizes — `KasyIconSize` (`lib/core/theme/icon_sizes.dart`)
|
|
115
|
+
|
|
116
|
+
| Token | px | Token alias | = |
|
|
117
|
+
| ----------- | -- | ------------ | -- |
|
|
118
|
+
| `xxs` | 12 | `rowLeading` | `lg` (20) |
|
|
119
|
+
| `xs` | 14 | `rowTrailing`| `sm` (16) |
|
|
120
|
+
| `sm` | 16 | `chrome` | `lg` (20) |
|
|
121
|
+
| `md` | 18 | `inline` | `md` (18) |
|
|
122
|
+
| `lg` | 20 | | |
|
|
123
|
+
| `xl` | 24 | | |
|
|
124
|
+
| `xxl` | 28 | | |
|
|
125
|
+
| `display` | 36 | | |
|
|
126
|
+
| `hero` | 72 | | |
|
|
127
|
+
|
|
128
|
+
Never pass a numeric `size:` to `Icon`; use a token so re-scaling is one edit.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Spacing — `KasySpacing` (`lib/core/theme/spacing.dart`)
|
|
133
|
+
|
|
134
|
+
`xs 4 · sm 8 · smd 12 · md 16 · lg 24 · xl 32 · xxl 48 · xxxl 64`
|
|
135
|
+
|
|
136
|
+
`pageHorizontalGutter = md (16)` · `pageVerticalGutter = sm (8)` ·
|
|
137
|
+
`belowChromeContentGap = 20`.
|
|
138
|
+
|
|
139
|
+
## Radius — `KasyRadius` (`lib/core/theme/radius.dart`)
|
|
140
|
+
|
|
141
|
+
Semantic: `xs 4 · sm 8 · md 12 · lg 16 · xl 24 · full 999`.
|
|
142
|
+
`KasyCard` defaults to `lg` (16).
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Colours — `KasyColors`, via `context.colors.*`
|
|
147
|
+
|
|
148
|
+
Always pull colours from `context.colors.*` so light/dark resolve automatically.
|
|
149
|
+
Key tokens: `primary`, `onPrimary`, `surface`, `onSurface`, `onBackground`,
|
|
150
|
+
`muted`, `fieldLabel`, `outline`, `error` / `onError`, `success`, `warning`,
|
|
151
|
+
`surfacePrimarySoft`, `surfaceNeutralSoft`, `surfaceErrorSoft`.
|
|
152
|
+
|
|
153
|
+
Brand: avocado green `#D2F51E` (accent), deep green `#011820` (dark ground).
|
|
154
|
+
Disabled state: blend the colour toward the surface (opaque), never raw opacity.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Components
|
|
159
|
+
|
|
160
|
+
### `KasyButton`
|
|
161
|
+
Sizes (height): `small 40` · `medium 45` (default) · `large 54`. Radius
|
|
162
|
+
`KasyRadius.md`. Variants: primary, secondary, tertiary, destructive,
|
|
163
|
+
destructiveSoft, inverse, link, soft, neutral, outline, ghost.
|
|
164
|
+
|
|
165
|
+
### `KasyTextField`
|
|
166
|
+
Canonical single-line height: **`singleLineHeight = 45`** (drives the field's
|
|
167
|
+
vertical padding so the box itself is exactly that tall on every platform — web
|
|
168
|
+
density is normalised to `standard`). Matches the medium button.
|
|
169
|
+
|
|
170
|
+
Variants:
|
|
171
|
+
- **primary** — surface fill + hairline border + soft shadow (floats; good on a
|
|
172
|
+
bare background).
|
|
173
|
+
- **secondary** — elevated fill (contrasts on a card) + border, no shadow.
|
|
174
|
+
- **flat** — primary's surface fill + a soft border, **no shadow**. For fields
|
|
175
|
+
sitting on a same-coloured card. Border comes from
|
|
176
|
+
`KasyShadows.inputFieldFlatBorder` (derived from `onSurface`, so it reads in
|
|
177
|
+
both light and dark; subtle by design).
|
|
178
|
+
- **embedded** — transparent, no border, no shadow.
|
|
179
|
+
|
|
180
|
+
Auth screens (sign in / sign up / recover) use **flat** on every breakpoint.
|
|
181
|
+
A custom `contentPadding` opts a field out of the 45 lock (e.g. the compact
|
|
182
|
+
header search). Use `forceFocusBorder` to show the "active" focused border from
|
|
183
|
+
state without taking real focus (composite triggers like the date picker).
|
|
184
|
+
|
|
185
|
+
### `KasyTextArea`
|
|
186
|
+
Multi-line sibling of `KasyTextField`; same variants (primary / secondary / flat
|
|
187
|
+
/ embedded). Height grows with content (not locked to 45).
|
|
188
|
+
|
|
189
|
+
### `KasyDatePicker`
|
|
190
|
+
Reuses `KasyTextField` as a read-only trigger, so it inherits the field look and
|
|
191
|
+
the 45 height. The "open" border is painted from state (`forceFocusBorder`), so
|
|
192
|
+
a mouse click never doubles the keyboard focus ring and the border never drops
|
|
193
|
+
while the calendar is open.
|
|
194
|
+
|
|
195
|
+
### `KasyCard`
|
|
196
|
+
Elevated surface panel, radius `KasyRadius.lg` (16). The base for screen content.
|
|
197
|
+
|
|
198
|
+
### `KasyScreen`
|
|
199
|
+
Opt-in screen scaffold. A new page wrapped in it gets the internal-screen
|
|
200
|
+
contract for free: page background, a centred content column capped at
|
|
201
|
+
`maxContentWidth` (~600), the page gutter, scroll handling and an optional
|
|
202
|
+
`KasyCard`. It is a thin convenience over `Scaffold` (same `appBar` /
|
|
203
|
+
`floatingActionButton` / `bottomNavigationBar` slots), so it never blocks a
|
|
204
|
+
screen that needs bespoke chrome.
|
|
205
|
+
|
|
206
|
+
```dart
|
|
207
|
+
KasyScreen(
|
|
208
|
+
appBar: KasyAppBar.root(title: 'Settings'),
|
|
209
|
+
card: true,
|
|
210
|
+
child: Column(children: [...]),
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Internal-screen contract
|
|
217
|
+
|
|
218
|
+
New internal screens follow the sidebar / Home ruler:
|
|
219
|
+
- Body text 14, icons 20 (`KasyIconSize.lg`), titles ~16-17 (`sectionTitle`).
|
|
220
|
+
- `KasyCard` radius 16, content width contained (~600), no oversized boxes.
|
|
221
|
+
- Use `KasyButton`, `showKasyConfirmDialog`, `showKasyToast` — not raw Material.
|
|
222
|
+
- Haptics only on native (`if (!kIsWeb)`); "hide chrome on scroll" only on the
|
|
223
|
+
phone breakpoint (`width < 768`).
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Keeping it consistent — the guard-rail (optional)
|
|
228
|
+
|
|
229
|
+
`tool/design_check.dart` is a plain Dart script that flags a **feature screen**
|
|
230
|
+
reaching around the design system (raw `fontSize`, raw `Color`, raw Material
|
|
231
|
+
widgets, literal icon sizes). The design system's own code (`lib/components`,
|
|
232
|
+
`lib/core`, `lib/main.dart`) is out of scope — that is where the primitives are
|
|
233
|
+
built.
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
dart run tool/design_check.dart
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
It is **opt-in and non-blocking by default**: nothing runs it automatically (not
|
|
240
|
+
wired into `flutter analyze`, builds or git hooks). Run it locally or add it to
|
|
241
|
+
CI to turn the convention into a gate. It has no extra dependency — read it, tune
|
|
242
|
+
the allowlists at the top, or delete it. For a deliberate one-off, put
|
|
243
|
+
`// design-check: ignore` on the line.
|
|
244
|
+
|
|
245
|
+
This is intentionally not a cage: a project built with the kit can relax or drop
|
|
246
|
+
it freely, and rebranding is still a token change (colours / type) in one place.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Changelog
|
|
251
|
+
|
|
252
|
+
### 1.1.0 — 2026-06-11
|
|
253
|
+
- `KasyScreen` scaffold added (opt-in internal-screen contract).
|
|
254
|
+
- Design guard-rail added (`tool/design_check.dart`), opt-in / non-blocking.
|
|
255
|
+
- Feature layer brought to a green baseline against the guard-rail (deliberate
|
|
256
|
+
exceptions marked with `// design-check: ignore`).
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
### 1.0.0 — 2026-06-11
|
|
260
|
+
- Icon sizes centralised into `KasyIconSize`; product migrated off numeric sizes.
|
|
261
|
+
- Semantic typography roles added (`pageTitle`, `sectionTitle`, `sectionLabel`,
|
|
262
|
+
`rowTitle`, …).
|
|
263
|
+
- `KasyTextField`: `flat` variant + `forceFocusBorder`; single-line height
|
|
264
|
+
locked to **45** via derived padding (platform-independent); flat border role
|
|
265
|
+
`inputFieldFlatBorder` (visible in both themes, subtle).
|
|
266
|
+
- `KasyButton` medium height aligned to **45**.
|
|
267
|
+
- `KasyTextArea` gained the variant set.
|
|
268
|
+
- App-bar title weight `w900 → w700`; onboarding titles `28/26 → 24` (page-title
|
|
269
|
+
scale); notifications group eyebrow → `sectionLabel`.
|
|
@@ -73,9 +73,9 @@ Requires an [Apple Developer](https://developer.apple.com) account (paid).
|
|
|
73
73
|
>
|
|
74
74
|
> **Android**: the Apple button is hidden by design (it needs the paid Services ID web flow and adds little on Android for a SaaS). Leave it hidden.
|
|
75
75
|
>
|
|
76
|
-
> **Web (Firebase)**:
|
|
76
|
+
> **Web (Firebase)**: after Steps 1-3, run `kasy apple-web` — it writes the Services ID + Team ID + Key ID + `.p8` into the Firebase Apple provider and turns on `withAppleWebSignin` (which ships `false`). The Services ID Return URL (`firebaseapp.com/__/auth/handler`) covers the popup flow. Firebase re-signs the secret itself (never expires).
|
|
77
77
|
>
|
|
78
|
-
> **Web (Supabase)**: the
|
|
78
|
+
> **Web (Supabase)**: Apple on the web for Supabase is **not wired in the app yet** (roadmap) — the button stays hidden on web. On Supabase, Apple works on **native iOS only** (already set up by `kasy new`). See `ROADMAP.md`.
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
@@ -83,6 +83,8 @@ Requires an [Apple Developer](https://developer.apple.com) account (paid).
|
|
|
83
83
|
|
|
84
84
|
Requires a [Meta for Developers](https://developers.facebook.com) account.
|
|
85
85
|
|
|
86
|
+
> **Shortcut:** the `kasy facebook` command automates writing the credentials (Info.plist, strings.xml, and the Firebase/Supabase provider) and opens the Meta dashboard. The steps below are what you do on Meta (manual, no API).
|
|
87
|
+
|
|
86
88
|
### Step 1 — Create an app on Meta
|
|
87
89
|
|
|
88
90
|
1. Open [Meta for Developers → My Apps](https://developers.facebook.com/apps)
|
|
@@ -73,9 +73,9 @@ Requiere cuenta de [Apple Developer](https://developer.apple.com) (de pago).
|
|
|
73
73
|
>
|
|
74
74
|
> **Android**: el botón Apple queda oculto por defecto (necesita el flujo del Services ID de pago y aporta poco en Android para un SaaS). Déjalo oculto.
|
|
75
75
|
>
|
|
76
|
-
> **Web (Firebase)**:
|
|
76
|
+
> **Web (Firebase)**: tras los Pasos 1 a 3, ejecuta `kasy apple-web` — escribe el Services ID + Team ID + Key ID + `.p8` en el proveedor Apple de Firebase y activa `withAppleWebSignin` (que viene `false`). La Return URL del Services ID (`firebaseapp.com/__/auth/handler`) cubre el flujo de popup. Firebase vuelve a firmar el secret solo (no expira).
|
|
77
77
|
>
|
|
78
|
-
> **Web (Supabase)**: la
|
|
78
|
+
> **Web (Supabase)**: Apple en la web para Supabase **aún no viene activado en la app** (roadmap) — el botón queda oculto en la web. En Supabase, Apple funciona solo en **iOS nativo** (ya configurado por `kasy new`). Consulta `ROADMAP.md`.
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
@@ -83,6 +83,8 @@ Requiere cuenta de [Apple Developer](https://developer.apple.com) (de pago).
|
|
|
83
83
|
|
|
84
84
|
Requiere cuenta en [Meta for Developers](https://developers.facebook.com).
|
|
85
85
|
|
|
86
|
+
> **Atajo:** el comando `kasy facebook` automatiza escribir las credenciales (Info.plist, strings.xml y el proveedor en Firebase/Supabase) y abre el panel de Meta. Los pasos de abajo son lo que haces en Meta (manual, sin API).
|
|
87
|
+
|
|
86
88
|
### Paso 1 — Crear una app en Meta
|
|
87
89
|
|
|
88
90
|
1. Abre [Meta for Developers → My Apps](https://developers.facebook.com/apps)
|
|
@@ -73,9 +73,9 @@ Requer conta [Apple Developer](https://developer.apple.com) (paga).
|
|
|
73
73
|
>
|
|
74
74
|
> **Android**: o botão Apple fica escondido por padrão (exige o fluxo do Services ID pago e agrega pouco no Android para um SaaS). Deixe escondido.
|
|
75
75
|
>
|
|
76
|
-
> **Web (Firebase)**:
|
|
76
|
+
> **Web (Firebase)**: depois dos Passos 1 a 3, rode `kasy apple-web` — ele grava o Services ID + Team ID + Key ID + `.p8` no provedor Apple do Firebase e liga `withAppleWebSignin` (que nasce `false`). A Return URL do Services ID (`firebaseapp.com/__/auth/handler`) cobre o fluxo de popup. O Firebase re-assina o secret sozinho (não expira).
|
|
77
77
|
>
|
|
78
|
-
> **Web (Supabase)**:
|
|
78
|
+
> **Web (Supabase)**: Apple na web no Supabase **não vem ligado no app ainda** (roadmap) — o botão fica escondido na web. No Supabase, o Apple funciona só no **iOS nativo** (já configurado pelo `kasy new`). Veja `ROADMAP.md`.
|
|
79
79
|
|
|
80
80
|
---
|
|
81
81
|
|
|
@@ -83,6 +83,8 @@ Requer conta [Apple Developer](https://developer.apple.com) (paga).
|
|
|
83
83
|
|
|
84
84
|
Requer conta no [Meta for Developers](https://developers.facebook.com).
|
|
85
85
|
|
|
86
|
+
> **Atalho:** o comando `kasy facebook` automatiza a parte de gravar as credenciais (Info.plist, strings.xml e o provedor no Firebase/Supabase) e abre o painel da Meta. Os passos abaixo são o que você faz na Meta (manual, sem API).
|
|
87
|
+
|
|
86
88
|
### Passo 1 — Criar app no Meta
|
|
87
89
|
|
|
88
90
|
1. Abra [Meta for Developers → My Apps](https://developers.facebook.com/apps)
|
|
@@ -1 +1,56 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"hosting": {
|
|
3
|
+
"public": "build/web",
|
|
4
|
+
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
|
|
5
|
+
"rewrites": [
|
|
6
|
+
{ "source": "**", "destination": "/index.html" }
|
|
7
|
+
],
|
|
8
|
+
"headers": [
|
|
9
|
+
{
|
|
10
|
+
"source": "**/@(index.html|flutter_bootstrap.js|flutter_service_worker.js|main.dart.js|main.dart.wasm|main.dart.mjs|manifest.json|version.json)",
|
|
11
|
+
"headers": [
|
|
12
|
+
{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" },
|
|
18
|
+
"storage": { "rules": "storage.rules" },
|
|
19
|
+
"functions": [
|
|
20
|
+
{
|
|
21
|
+
"source": "functions",
|
|
22
|
+
"codebase": "default",
|
|
23
|
+
"ignore": ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"],
|
|
24
|
+
"predeploy": ["npm --prefix \"$RESOURCE_DIR\" run build"]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"flutter": {
|
|
28
|
+
"platforms": {
|
|
29
|
+
"android": {
|
|
30
|
+
"default": {
|
|
31
|
+
"projectId": "fir-kit-8e56b",
|
|
32
|
+
"appId": "1:613770689965:android:f800aacb0091708b0f7524",
|
|
33
|
+
"fileOutput": "android/app/google-services.json"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"ios": {
|
|
37
|
+
"default": {
|
|
38
|
+
"projectId": "fir-kit-8e56b",
|
|
39
|
+
"appId": "1:613770689965:ios:7cc1f4579cf759fd0f7524",
|
|
40
|
+
"uploadDebugSymbols": false,
|
|
41
|
+
"fileOutput": "ios/Runner/GoogleService-Info.plist"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"dart": {
|
|
45
|
+
"lib/firebase_options.dart": {
|
|
46
|
+
"projectId": "fir-kit-8e56b",
|
|
47
|
+
"configurations": {
|
|
48
|
+
"android": "1:613770689965:android:f800aacb0091708b0f7524",
|
|
49
|
+
"ios": "1:613770689965:ios:7cc1f4579cf759fd0f7524",
|
|
50
|
+
"web": "1:613770689965:web:e9347bac3b3eae5d0f7524"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -5,7 +5,7 @@ import {notificationsApi} from "../notifications/notifications_api";
|
|
|
5
5
|
import {NotificationPayload, SystemNotificationParams} from "../notifications/models/notification";
|
|
6
6
|
import {NotificationTypes} from "../core/data/entities/notification_entity";
|
|
7
7
|
import {Timestamp} from "firebase-admin/firestore";
|
|
8
|
-
import {usersRepository
|
|
8
|
+
import {usersRepository} from "../core/data/repositories/repositories";
|
|
9
9
|
import * as functions from "firebase-functions/v1";
|
|
10
10
|
import {onDocumentCreated} from "firebase-functions/v2/firestore";
|
|
11
11
|
import {UserDeviceEntityData} from "../core/data/entities/user_device_entity";
|
|
@@ -52,9 +52,13 @@ export const onUserRegistration = functions
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Triggered when
|
|
56
|
-
* Sends a localized welcome notification (saved to DB only, no
|
|
57
|
-
* Fires on device creation so the device locale is available.
|
|
55
|
+
* Triggered when a device is registered.
|
|
56
|
+
* Sends a localized welcome notification ONCE per account (saved to DB only, no
|
|
57
|
+
* push). Fires on device creation so the device locale is available.
|
|
58
|
+
*
|
|
59
|
+
* The "once per account" guard is [UserRepository.claimWelcome], not a device
|
|
60
|
+
* count: a device re-registered after a logout/login cycle looks like a fresh
|
|
61
|
+
* first device, so counting devices re-sent the welcome on every re-login.
|
|
58
62
|
*/
|
|
59
63
|
export const onFirstDeviceRegistered = onDocumentCreated(
|
|
60
64
|
"users/{userId}/devices/{deviceId}",
|
|
@@ -62,8 +66,8 @@ export const onFirstDeviceRegistered = onDocumentCreated(
|
|
|
62
66
|
const logger = new Logger("onFirstDeviceRegistered");
|
|
63
67
|
const userId = event.params.userId;
|
|
64
68
|
try {
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
69
|
+
const shouldWelcome = await usersRepository.claimWelcome(userId);
|
|
70
|
+
if (!shouldWelcome) return;
|
|
67
71
|
|
|
68
72
|
const deviceData = event.data?.data() as UserDeviceEntityData | undefined;
|
|
69
73
|
// Flutter serializes as "extraData" (camelCase); Firestore schema uses "extra_data".
|
|
@@ -13,6 +13,7 @@ export interface SubscriptionEntityData {
|
|
|
13
13
|
creation_date: Timestamp;
|
|
14
14
|
last_activity: Timestamp;
|
|
15
15
|
expiration_date?: Timestamp;
|
|
16
|
+
trial_end?: Timestamp;
|
|
16
17
|
status: SubscriptionStatus;
|
|
17
18
|
store: Stores,
|
|
18
19
|
product_id: string;
|
|
@@ -30,6 +31,7 @@ export class SubscriptionEntity {
|
|
|
30
31
|
creation_date,
|
|
31
32
|
last_activity,
|
|
32
33
|
expiration_date,
|
|
34
|
+
trial_end,
|
|
33
35
|
status,
|
|
34
36
|
store,
|
|
35
37
|
product_id,
|
|
@@ -41,6 +43,7 @@ export class SubscriptionEntity {
|
|
|
41
43
|
this.creation_date = creation_date;
|
|
42
44
|
this.last_activity = last_activity;
|
|
43
45
|
this.expiration_date = expiration_date;
|
|
46
|
+
this.trial_end = trial_end;
|
|
44
47
|
this.status = status;
|
|
45
48
|
this.store = store;
|
|
46
49
|
this.product_id = product_id;
|
|
@@ -66,6 +69,7 @@ export class SubscriptionEntity {
|
|
|
66
69
|
creation_date: subscription.creationDate,
|
|
67
70
|
last_activity: subscription.lastUpdate,
|
|
68
71
|
expiration_date: subscription.expirationDate,
|
|
72
|
+
trial_end: subscription.trialEnd,
|
|
69
73
|
status: subscription.status,
|
|
70
74
|
store: subscription.store,
|
|
71
75
|
product_id: subscription.productId,
|
|
@@ -78,6 +82,7 @@ export class SubscriptionEntity {
|
|
|
78
82
|
creationDate: this.creation_date,
|
|
79
83
|
lastUpdate: this.last_activity,
|
|
80
84
|
expirationDate: this.expiration_date,
|
|
85
|
+
trialEnd: this.trial_end,
|
|
81
86
|
status: this.status,
|
|
82
87
|
store: this.store,
|
|
83
88
|
productId: this.product_id,
|