onboardingState function

  1. @riverpod
Future<OnboardingStatus> onboardingState(
  1. Ref<Object?> ref
)

Resolves the current onboarding status for the signed-in user. The gate watches this and redirects (or shows the invitation modal) accordingly.

Implementation

@riverpod
Future<OnboardingStatus> onboardingState(Ref ref) async {
  final profile = await ref.watch(currentProfileProvider.future);

  // No profile row yet — the auto-profile trigger runs on signup so this is
  // transient. Treat as "needs profile completion" to force a refetch via
  // the profile screen; if the row is genuinely missing updateProfile will
  // upsert via RLS.
  if (profile == null) return OnboardingStatus.needsProfileCompletion;

  final displayName = profile.displayName?.trim();
  if (displayName == null || displayName.isEmpty) {
    return OnboardingStatus.needsProfileCompletion;
  }

  final persona = profile.persona;
  if (persona == null || persona.isEmpty) {
    return OnboardingStatus.needsPersona;
  }

  final invitations = await ref.watch(myInvitationsProvider.future);
  final dismissed = ref
      .watch(onboardingSessionStateProvider)
      .invitationsDismissed;
  if (invitations.isNotEmpty && !dismissed) {
    return OnboardingStatus.hasPendingInvitations;
  }

  // Learner onboarding applies to solo personas. Team leads skip the
  // pedagogical tour (they manage learners; they'll experience it only if
  // they also learn — reachable via Settings "Replay").
  if (persona == 'solo' && profile.onboardingCompletedAt == null) {
    return OnboardingStatus.needsLearnerOnboarding;
  }

  final isAdmin = await ref.watch(isOrgAdminProvider.future);
  if (isAdmin && profile.adminOnboardingCompletedAt == null) {
    return OnboardingStatus.needsAdminOnboarding;
  }

  return OnboardingStatus.complete;
}