feat(ui): Online Inbox settings tab + auth-code/PKCE login
New Settings tab: enable toggle, config fields, sign-in/out + status. OnlineLoginService runs the PKCE loopback flow (Duende.IdentityModel.OidcClient 7.1.0), opens the system browser, captures the callback, hands the refresh token to the Worker. en/de localized. Fixes: loopback callback URL built from host:port base (avoids doubled redirect path); PollIntervalSeconds threaded through the state DTO so it loads instead of resetting to 60. Visual layout + the live sign-in round-trip need manual verification. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||
|
||||
public sealed partial class OnlineInboxSettingsViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IWorkerClient _worker;
|
||||
private readonly IOnlineLoginService _loginService;
|
||||
|
||||
[ObservableProperty] private bool _enabled;
|
||||
[ObservableProperty] private string _apiBaseUrl = "";
|
||||
[ObservableProperty] private string _authority = "";
|
||||
[ObservableProperty] private string _clientId = "";
|
||||
[ObservableProperty] private string _scopes = "openid offline_access";
|
||||
[ObservableProperty] private string _redirectUri = "http://localhost:8765/callback";
|
||||
[ObservableProperty] private int _pollIntervalSeconds = 60;
|
||||
[ObservableProperty] private bool _signedIn;
|
||||
[ObservableProperty] private bool _isBusy;
|
||||
[ObservableProperty] private string _statusMessage = "";
|
||||
|
||||
public OnlineInboxSettingsViewModel(IWorkerClient worker, IOnlineLoginService loginService)
|
||||
{
|
||||
_worker = worker;
|
||||
_loginService = loginService;
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
IsBusy = true;
|
||||
StatusMessage = "";
|
||||
try
|
||||
{
|
||||
var dto = await _worker.GetOnlineInboxStateAsync();
|
||||
if (dto is null)
|
||||
{
|
||||
StatusMessage = Loc.T("vm.onlineInbox.workerOffline");
|
||||
return;
|
||||
}
|
||||
|
||||
Enabled = dto.Enabled;
|
||||
ApiBaseUrl = dto.ApiBaseUrl;
|
||||
Authority = dto.Authority;
|
||||
ClientId = dto.ClientId;
|
||||
Scopes = dto.Scopes;
|
||||
RedirectUri = dto.RedirectUri;
|
||||
SignedIn = dto.SignedIn;
|
||||
PollIntervalSeconds = dto.PollIntervalSeconds;
|
||||
}
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Save()
|
||||
{
|
||||
IsBusy = true;
|
||||
StatusMessage = "";
|
||||
try
|
||||
{
|
||||
await _worker.SetOnlineInboxConfigAsync(new OnlineInboxConfigInputDto(
|
||||
Enabled,
|
||||
ApiBaseUrl,
|
||||
PollIntervalSeconds,
|
||||
Authority,
|
||||
ClientId,
|
||||
Scopes,
|
||||
RedirectUri));
|
||||
StatusMessage = Loc.T("vm.onlineInbox.saved");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = Loc.T("vm.onlineInbox.saveFailed", ex.Message);
|
||||
}
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SignIn()
|
||||
{
|
||||
IsBusy = true;
|
||||
StatusMessage = "";
|
||||
try
|
||||
{
|
||||
var result = await _loginService.LoginAsync(Authority, ClientId, Scopes, RedirectUri);
|
||||
if (!result.Success)
|
||||
{
|
||||
StatusMessage = Loc.T("vm.onlineInbox.signInFailed", result.Error ?? "Unknown error");
|
||||
return;
|
||||
}
|
||||
|
||||
await _worker.SetOnlineInboxAuthAsync(result.RefreshToken!);
|
||||
SignedIn = true;
|
||||
StatusMessage = Loc.T("vm.onlineInbox.signedIn");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = Loc.T("vm.onlineInbox.signInFailed", ex.Message);
|
||||
}
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SignOut()
|
||||
{
|
||||
IsBusy = true;
|
||||
StatusMessage = "";
|
||||
try
|
||||
{
|
||||
await _worker.ClearOnlineInboxAuthAsync();
|
||||
SignedIn = false;
|
||||
StatusMessage = Loc.T("vm.onlineInbox.signedOut");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = Loc.T("vm.onlineInbox.signOutFailed", ex.Message);
|
||||
}
|
||||
finally { IsBusy = false; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user