| | | 1 | | using Chronicis.Client.Abstractions; |
| | | 2 | | using Chronicis.Client.Services; |
| | | 3 | | using Chronicis.Shared.Extensions; |
| | | 4 | | |
| | | 5 | | namespace Chronicis.Client.ViewModels; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// ViewModel for the Getting Started / onboarding wizard. |
| | | 9 | | /// Owns step navigation state and the async completion flow. |
| | | 10 | | /// </summary> |
| | | 11 | | public sealed class GettingStartedViewModel : ViewModelBase |
| | | 12 | | { |
| | | 13 | | /// <summary>Total number of wizard steps.</summary> |
| | | 14 | | public const int TotalSteps = 4; |
| | | 15 | | |
| | | 16 | | private readonly IUserApiService _userApi; |
| | | 17 | | private readonly IAppNavigator _navigator; |
| | | 18 | | private readonly IUserNotifier _notifier; |
| | | 19 | | private readonly ILogger<GettingStartedViewModel> _logger; |
| | | 20 | | |
| | | 21 | | private int _currentStep = 0; |
| | | 22 | | private bool _isCompleting = false; |
| | | 23 | | private bool _isReturningUser = false; |
| | | 24 | | |
| | | 25 | | /// <summary>Zero-based index of the currently displayed step (0 – <see cref="TotalSteps"/> − 1).</summary> |
| | | 26 | | public int CurrentStep |
| | | 27 | | { |
| | 289 | 28 | | get => _currentStep; |
| | 15 | 29 | | private set => SetField(ref _currentStep, value); |
| | | 30 | | } |
| | | 31 | | |
| | | 32 | | /// <summary>Whether the completion async operation is in-flight.</summary> |
| | | 33 | | public bool IsCompleting |
| | | 34 | | { |
| | 25 | 35 | | get => _isCompleting; |
| | 10 | 36 | | private set => SetField(ref _isCompleting, value); |
| | | 37 | | } |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// Whether the user has previously completed onboarding. |
| | | 41 | | /// Affects button labels and skip-to-end behaviour. |
| | | 42 | | /// </summary> |
| | | 43 | | public bool IsReturningUser |
| | | 44 | | { |
| | 38 | 45 | | get => _isReturningUser; |
| | 14 | 46 | | private set => SetField(ref _isReturningUser, value); |
| | | 47 | | } |
| | | 48 | | |
| | 31 | 49 | | public GettingStartedViewModel( |
| | 31 | 50 | | IUserApiService userApi, |
| | 31 | 51 | | IAppNavigator navigator, |
| | 31 | 52 | | IUserNotifier notifier, |
| | 31 | 53 | | ILogger<GettingStartedViewModel> logger) |
| | | 54 | | { |
| | 31 | 55 | | _userApi = userApi; |
| | 31 | 56 | | _navigator = navigator; |
| | 31 | 57 | | _notifier = notifier; |
| | 31 | 58 | | _logger = logger; |
| | 31 | 59 | | } |
| | | 60 | | |
| | | 61 | | /// <summary> |
| | | 62 | | /// Loads the user profile to determine whether this is a returning user. |
| | | 63 | | /// Call from <c>OnInitializedAsync</c>. |
| | | 64 | | /// </summary> |
| | | 65 | | public async Task InitializeAsync() |
| | | 66 | | { |
| | | 67 | | var profile = await _userApi.GetUserProfileAsync(); |
| | | 68 | | IsReturningUser = profile?.HasCompletedOnboarding == true; |
| | | 69 | | } |
| | | 70 | | |
| | | 71 | | /// <summary>Advances to the next step, clamped at the last step.</summary> |
| | | 72 | | public void NextStep() |
| | | 73 | | { |
| | 5 | 74 | | if (CurrentStep < TotalSteps - 1) |
| | 4 | 75 | | CurrentStep++; |
| | 5 | 76 | | } |
| | | 77 | | |
| | | 78 | | /// <summary>Returns to the previous step, clamped at step 0.</summary> |
| | | 79 | | public void PreviousStep() |
| | | 80 | | { |
| | 2 | 81 | | if (CurrentStep > 0) |
| | 1 | 82 | | CurrentStep--; |
| | 2 | 83 | | } |
| | | 84 | | |
| | | 85 | | /// <summary>Jumps directly to <paramref name="step"/> if it is within bounds.</summary> |
| | | 86 | | public void GoToStep(int step) |
| | | 87 | | { |
| | 13 | 88 | | if (step >= 0 && step < TotalSteps) |
| | 10 | 89 | | CurrentStep = step; |
| | 13 | 90 | | } |
| | | 91 | | |
| | | 92 | | /// <summary> |
| | | 93 | | /// Completes onboarding (or, for returning users, simply navigates back to the dashboard). |
| | | 94 | | /// Marks the user's profile as having completed onboarding before navigating. |
| | | 95 | | /// </summary> |
| | | 96 | | public async Task CompleteOnboardingAsync() |
| | | 97 | | { |
| | | 98 | | if (IsReturningUser) |
| | | 99 | | { |
| | | 100 | | _navigator.NavigateTo("/dashboard"); |
| | | 101 | | return; |
| | | 102 | | } |
| | | 103 | | |
| | | 104 | | IsCompleting = true; |
| | | 105 | | try |
| | | 106 | | { |
| | | 107 | | var success = await _userApi.CompleteOnboardingAsync(); |
| | | 108 | | if (success) |
| | | 109 | | { |
| | | 110 | | _navigator.NavigateTo("/dashboard", replace: true); |
| | | 111 | | } |
| | | 112 | | else |
| | | 113 | | { |
| | | 114 | | _notifier.Error("Failed to complete setup. Please try again."); |
| | | 115 | | } |
| | | 116 | | } |
| | | 117 | | catch (Exception ex) |
| | | 118 | | { |
| | | 119 | | _logger.LogErrorSanitized(ex, "Error completing onboarding"); |
| | | 120 | | _notifier.Error("An error occurred. Please try again."); |
| | | 121 | | } |
| | | 122 | | finally |
| | | 123 | | { |
| | | 124 | | IsCompleting = false; |
| | | 125 | | } |
| | | 126 | | } |
| | | 127 | | } |