Iceshrimp.NET/Iceshrimp.Frontend/Pages/Settings/Account.razor

254 lines
10 KiB
Text

@page "/settings/account"
@using Iceshrimp.Frontend.Core.Services
@using Iceshrimp.Frontend.Localization
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Extensions.Localization
@using Microsoft.AspNetCore.Components.Sections
@using Iceshrimp.Assets.PhosphorIcons
@using Iceshrimp.Frontend.Core.Miscellaneous
@using Iceshrimp.Shared.Schemas.Web
@using Iceshrimp.Frontend.Components
@attribute [Authorize]
@layout SettingsLayout
@inject ApiService Api;
@inject ILogger<Profile> Logger;
@inject IStringLocalizer<Localization> Loc;
@inject IJSRuntime Js;
<SectionContent SectionName="top-bar">
<Icon Name="Icons.UserCircleGear"></Icon>
@Loc["Account"]
</SectionContent>
@if (State is State.Loaded)
{
<div class="body">
<div class="section">
<h1>@Loc["Settings"]</h1>
<EditForm OnSubmit="Submit" Model="SettingsForm">
<label>
@Loc["Default note visibility"]
<InputSelect @bind-Value="SettingsForm.DefaultNoteVisibility">
<option value="@NoteVisibility.Public">@Loc["Public"]</option>
<option value="@NoteVisibility.Home">@Loc["Home"]</option>
<option value="@NoteVisibility.Followers">@Loc["Followers"]</option>
<option value="@NoteVisibility.Specified">@Loc["Direct"]</option>
</InputSelect>
</label>
<label>
@Loc["Default renote visibility"]
<InputSelect @bind-Value="SettingsForm.DefaultRenoteVisibility">
<option value="@NoteVisibility.Public">@Loc["Public"]</option>
<option value="@NoteVisibility.Home">@Loc["Home"]</option>
<option value="@NoteVisibility.Followers">@Loc["Followers"]</option>
</InputSelect>
</label>
<label>
@Loc["Private Mode"]
<InputCheckbox @bind-Value="SettingsForm.PrivateMode"/>
</label>
<label>
@Loc["Filter replies to inaccessible notes"]
<InputCheckbox @bind-Value="SettingsForm.FilterInaccessible"/>
</label>
<label>
@Loc["Auto accept follow requests from users you are following"]
<InputCheckbox @bind-Value="SettingsForm.AutoAcceptFollowed"/>
</label>
<label>
@Loc["Mark all posts as sensitive"]
<InputCheckbox @bind-Value="SettingsForm.AlwaysMarkSensitive"/>
</label>
<StateButton ExtraClasses="button" @ref="@SaveButton" OnClick="Submit">
<Initial>@Loc["Save changes"]</Initial>
<Success>@Loc["Saved"]</Success>
<Loading>@Loc["Loading"]<Icon Name="Icons.Spinner"/>
</Loading>
<Failed>@Loc["Error"]</Failed>
</StateButton>
</EditForm>
</div>
<h1>@Loc["Two-Factor Authentication"]</h1>
@if (SettingsForm.TwoFactorEnrolled == false)
{
<button class="button" @onclick="Enroll2FA">@Loc["Enable 2FA"]</button>
}
@if (SettingsForm.TwoFactorEnrolled)
{
<button class="button" @onclick="Disable2FAMenu">@Loc["Disable 2FA"]</button>
}
<dialog class="dialog" @ref="EnrollDialog">
@if (TwoFactorResponse is not null)
{
<div class="dialog-content">
<div class="header">
<h2 class="title">@Loc["Two-Factor Authentication"]</h2>
<button class="button close" @onclick="() => CloseDialog(EnrollDialog)">
<Icon Name="Icons.X"></Icon>
</button>
</div>
<img class="qr" src="@TwoFactorResponse.QrPng"/>
<div class="container">
<div class="name">@Loc["Two-Factor secret"]</div>
<div class="item">
<pre><code>@TwoFactorResponse.Secret</code></pre>
</div>
<div class="name">@Loc["Two-Factor URL"]</div>
<div class="item">
<pre><code>@TwoFactorResponse.Url</code></pre>
</div>
</div>
<input class="otp-input" @bind="OtpEnable" autocomplete="one-time-code"/>
<StateButton ExtraClasses="button" @ref="TwoFactorButton" OnClick="Confirm2FA">
<Loading>@Loc["Processing"]</Loading>
<Failed>@Loc["Failed to enroll"]</Failed>
<Success>@Loc["Two-Factor Authentication enabled!"]</Success>
<Initial>@Loc["Confirm 2FA Code"]</Initial>
</StateButton>
</div>
}
<div class="backdrop"></div>
</dialog>
<dialog class="dialog" @ref="DisableDialog">
<div class="dialog-content">
<div class="header">
<h2 class="title">@Loc["Two-Factor Authentication"]</h2>
<button class="button close" @onclick="() => CloseDialog(DisableDialog)">
<Icon Name="Icons.X"></Icon>
</button>
</div>
<input class="otp-input" @bind="OtpDisable" autocomplete="one-time-code"/>
<StateButton ExtraClasses="button" @ref="DisableButton" OnClick="Disable2FA">
<Loading>@Loc["Processing"]</Loading>
<Failed>@Loc["Failed to disable Two-Factor Authentication"]</Failed>
<Success>@Loc["Two-Factor Authentication disabled!"]</Success>
<Initial>
<Icon Name="Icons.Warning"></Icon>@Loc["Disable Two-Factor Authentication"]</Initial>
</StateButton>
</div>
<div class="backdrop"></div>
</dialog>
</div>
}
@code {
private UserSettingsResponse SettingsForm { get; set; } = null!;
private State State { get; set; } = State.Loading;
private StateButton SaveButton { get; set; } = null!;
private StateButton DisableButton { get; set; } = null!;
private StateButton TwoFactorButton { get; set; } = null!;
private ElementReference EnrollDialog { get; set; }
private ElementReference DisableDialog { get; set; }
private TwoFactorEnrollmentResponse? TwoFactorResponse { get; set; }
private string? OtpEnable { get; set; }
private string? OtpDisable { get; set; }
private readonly Lazy<Task<IJSObjectReference>> _moduleTask;
public Account()
{
_moduleTask = new Lazy<Task<IJSObjectReference>>(() =>
Js.InvokeAsync<IJSObjectReference>(
"import",
"./Pages/Settings/Account.razor.js")
.AsTask());
}
protected override async Task OnInitializedAsync()
{
SettingsForm = await Api.Settings.GetSettingsAsync();
State = State.Loaded;
}
private async Task Submit()
{
try
{
var res = await Api.Settings.UpdateSettingsAsync(SettingsForm);
SaveButton.State = StateButton.StateEnum.Loading;
StateHasChanged();
SaveButton.State = res ? StateButton.StateEnum.Success : StateButton.StateEnum.Failed;
}
catch (ApiException)
{
SaveButton.State = StateButton.StateEnum.Failed;
}
}
private async Task Enroll2FA()
{
var module = await _moduleTask.Value;
TwoFactorResponse = await Api.Settings.EnrollTwoFactorAsync();
await module.InvokeVoidAsync("openDialog", EnrollDialog);
}
private async Task Disable2FAMenu()
{
var module = await _moduleTask.Value;
await module.InvokeVoidAsync("openDialog", DisableDialog);
}
private async Task Disable2FA()
{
var module = await _moduleTask.Value;
if (OtpDisable is null)
{
return;
}
try
{
DisableButton.State = StateButton.StateEnum.Loading;
StateHasChanged();
await Api.Settings.DisableTwoFactorAsync(new TwoFactorRequest { Code = OtpDisable });
DisableButton.State = StateButton.StateEnum.Success;
StateHasChanged();
await Task.Delay(1000);
SettingsForm = await Api.Settings.GetSettingsAsync();
await module.InvokeVoidAsync("closeDialog", DisableDialog);
}
catch (ApiException e)
{
Logger.LogError(e, "2FA enrollment failed");
DisableButton.State = StateButton.StateEnum.Failed;
}
}
private async Task Confirm2FA()
{
var module = await _moduleTask.Value;
if (OtpEnable is null)
{
return;
}
try
{
TwoFactorButton.State = StateButton.StateEnum.Loading;
StateHasChanged();
await Api.Settings.ConfirmTwoFactorAsync(new TwoFactorRequest { Code = OtpEnable });
TwoFactorButton.State = StateButton.StateEnum.Success;
StateHasChanged();
await Task.Delay(1000);
SettingsForm = await Api.Settings.GetSettingsAsync();
await module.InvokeVoidAsync("closeDialog", EnrollDialog);
}
catch (ApiException e)
{
Logger.LogError(e, "2FA enrollment failed");
TwoFactorButton.State = StateButton.StateEnum.Failed;
}
}
private async Task CloseDialog(ElementReference dialog)
{
var module = await _moduleTask.Value;
await module.InvokeVoidAsync("closeDialog", dialog);
}
}