Spec §2 (flow_customer.mermaid) routes post-OTP based on user-lookup + has_transacted, but the implementation previously dumped every OTP success on /home. Introduce `OnboardingIntent` provider: set to `onboarding` by routeForVerifChoice's verified branch (the "aku mau curhat" transaction journey), set to `recover` by SHome1st's masuk → banner. Router redirect on AuthAuthenticatedData+isAuthRoute consumes it: `onboarding` → /payment/entry (dispatches S6 paywall vs PickMethod via first_session_discount.eligible); `recover` → /home. Intent is reset in /payment/entry's initState so subsequent masuk → flows don't inherit it. auth_notifier.verifyOtp uses .copyWithPrevious on AsyncError so valueOrNull retains AuthOtpSentData/AuthAnonymousData through OTP failures — required for the OTP-blocked recovery path (/onboarding/anon/method → /payment/method-pick) to clear the global redirect without bouncing to /home. Router also extends the isAuthRoute/isOnboardingFlow carve-out to AuthOtpSentData. Maestro tests adopt `ts-<app>-<NN>-<MM>-<descriptor>.yaml` convention: NN = mermaid section, MM = sub-flow index. New ts-customer-02-01..05 cover the §2 branches (verified brand-new → S6, existing-no-tx → S6, existing-tx → method-pick, OTP-blocked → method-pick, anonymous first- timer → method-pick); deferred 02-06/07/08/09 documented in README_section_02.md. TS-07 → ts-customer-02-10 (masuk → recovery); TS-01..06 → ts-customer-04-01..06 (§4 returning-user). Shared onboarding_new_user_verified.yaml subflow extracted. Register screen's body Column now uses LayoutBuilder + SingleChildScrollView + ConstrainedBox + IntrinsicHeight so the keyboard-open layout no longer overflows by 1.3 px (verified visually). Spec prose updated at flow_customer.mermaid §2 to describe the intent-driven routing + login-vs-transaction divergence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
98 lines
3.3 KiB
Dart
98 lines
3.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../../../core/auth/onboarding_intent_provider.dart';
|
|
import '../../../core/theme/halo_tokens.dart';
|
|
import '../../../core/theme/widgets/widgets.dart';
|
|
import '../../onboarding/usp_seen_provider.dart';
|
|
|
|
/// Result of the post-name Verif Choice Sheet. Caller routes to the matching
|
|
/// onboarding sub-flow.
|
|
enum VerifChoice { verified, anonymous }
|
|
|
|
class VerifChoiceSheet extends StatelessWidget {
|
|
const VerifChoiceSheet({super.key});
|
|
|
|
/// Show the sheet and return the user's choice (`null` if dismissed).
|
|
static Future<VerifChoice?> show(BuildContext context) {
|
|
return HaloBottomSheet.show<VerifChoice>(
|
|
context,
|
|
child: const VerifChoiceSheet(),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const Text(
|
|
'Mau curhat sebagai siapa?',
|
|
style: TextStyle(
|
|
fontFamily: HaloTokens.fontDisplay,
|
|
fontSize: 22,
|
|
height: 28 / 22,
|
|
fontWeight: FontWeight.w700,
|
|
color: HaloTokens.ink,
|
|
),
|
|
),
|
|
const SizedBox(height: HaloSpacing.s8),
|
|
const Text(
|
|
'Verifikasi nomor HP biar bisa dapet diskon sesi pertama dan riwayat curhatmu kesimpan. Atau langsung curhat anonim, nggak perlu daftar.',
|
|
style: TextStyle(
|
|
fontFamily: HaloTokens.fontBody,
|
|
fontSize: 14,
|
|
height: 20 / 14,
|
|
color: HaloTokens.inkSoft,
|
|
),
|
|
),
|
|
const SizedBox(height: HaloSpacing.s24),
|
|
HaloButton(
|
|
label: 'verifikasi nomor HP',
|
|
fullWidth: true,
|
|
onPressed: () =>
|
|
Navigator.of(context).pop(VerifChoice.verified),
|
|
),
|
|
const SizedBox(height: HaloSpacing.s12),
|
|
HaloButton(
|
|
label: 'curhat anonim',
|
|
variant: HaloButtonVariant.secondary,
|
|
fullWidth: true,
|
|
onPressed: () =>
|
|
Navigator.of(context).pop(VerifChoice.anonymous),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Helper: route to the right onboarding sub-flow for a verif choice.
|
|
///
|
|
/// Phase 4 (2026-05-12): the S5 ESP screen is retired and S5b USP is now a
|
|
/// one-time gate. If the user has already seen USP (local SharedPreferences
|
|
/// flag, OR-merged with `customers.usp_seen` on login), we skip USP entirely
|
|
/// and jump to the per-branch next step.
|
|
Future<void> routeForVerifChoice(
|
|
BuildContext context,
|
|
WidgetRef ref,
|
|
VerifChoice choice,
|
|
) async {
|
|
final seen = await ref.read(uspSeenProvider.future);
|
|
if (!context.mounted) return;
|
|
switch (choice) {
|
|
case VerifChoice.verified:
|
|
// §2 transaction CTA path — router consumes this post-OTP and routes
|
|
// to /payment/entry (S6 paywall vs PickMethod via first_session_discount).
|
|
ref.read(onboardingIntentProvider.notifier).state =
|
|
OnboardingIntent.onboarding;
|
|
context.push(seen ? '/auth/register' : '/onboarding/verif/usp');
|
|
break;
|
|
case VerifChoice.anonymous:
|
|
// `/onboarding/anon/method` redirects to `/payment/method-pick`; use the
|
|
// canonical destination here.
|
|
context.push(seen ? '/payment/method-pick' : '/onboarding/anon/usp');
|
|
break;
|
|
}
|
|
}
|