Replace MudBlazor with Bootstrap

This commit is contained in:
Dennis Reimann 2023-11-08 18:42:52 +01:00
parent 0816b9d464
commit ca48d28aef
No known key found for this signature in database
GPG Key ID: 5009E1797F03F8D0
41 changed files with 13949 additions and 944 deletions

View File

@ -3,14 +3,14 @@
<head>
<base href="/"/>
<meta charset="utf-8"/>
<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover" name="viewport"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover"/>
<title>BTCPay Server</title>
<link href="/_content/MudBlazor/MudBlazor.min.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/css/mudblazor-overrides.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/css/theme.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/css/fonts.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/css/site.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/favicon.png" rel="icon" type="image/png"/>
<link href="_content/BTCPayApp.UI/favicon.png" rel="icon" type="image/png"/>
<link href="_content/BTCPayApp.UI/bootstrap/bootstrap.css" rel="stylesheet"/>
<link href="_content/BTCPayApp.UI/css/fonts.css" rel="stylesheet"/>
<link href="_content/BTCPayApp.UI/css/site.css" rel="stylesheet"/>
<link href="_content/BTCPayApp.UI/css/theme.css" rel="stylesheet"/>
<link href="_content/BTCPayApp.UI/BTCPayApp.UI.bundle.scp.css" rel="stylesheet" />
</head>
<body>
<div class="status-bar-safe-area"></div>
@ -20,8 +20,8 @@
<a class="reload" href="">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="/_content/BTCPayApp.UI/js/site.js"></script>
<script src="/_content/MudBlazor/MudBlazor.min.js"></script>
<script src="/_framework/blazor.webview.js" autostart="false"></script>
<script src="_framework/blazor.webview.js" autostart="false"></script>
<script src="_content/BTCPayApp.UI/bootstrap/bootstrap.bundle.min.js"></script>
<script src="_content/BTCPayApp.UI/js/site.js"></script>
</body>
</html>

View File

@ -3,14 +3,14 @@
<head>
<base href="/"/>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover"/>
<title>BTCPay Server</title>
<link href="/_content/MudBlazor/MudBlazor.min.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/css/mudblazor-overrides.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/css/theme.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/css/fonts.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/css/site.css" rel="stylesheet"/>
<link href="/_content/BTCPayApp.UI/favicon.png" rel="icon" type="image/png"/>
<link href="_content/BTCPayApp.UI/favicon.png" rel="icon" type="image/png"/>
<link href="_content/BTCPayApp.UI/bootstrap/bootstrap.css" rel="stylesheet"/>
<link href="_content/BTCPayApp.UI/css/fonts.css" rel="stylesheet"/>
<link href="_content/BTCPayApp.UI/css/site.css" rel="stylesheet"/>
<link href="_content/BTCPayApp.UI/css/theme.css" rel="stylesheet"/>
<link href="_content/BTCPayApp.UI/BTCPayApp.UI.bundle.scp.css" rel="stylesheet" />
</head>
<body>
<div class="status-bar-safe-area"></div>
@ -20,8 +20,8 @@
<a class="reload" href="">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="/_content/BTCPayApp.UI/js/site.js"></script>
<script src="/_content/MudBlazor/MudBlazor.min.js"></script>
<script src="/_framework/blazor.webview.js"></script>
<script src="_framework/blazor.webview.js" autostart="false"></script>
<script src="_content/BTCPayApp.UI/bootstrap/bootstrap.bundle.min.js"></script>
<script src="_content/BTCPayApp.UI/js/site.js"></script>
</body>
</html>

View File

@ -9,14 +9,14 @@
<head>
<base href="~/"/>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover"/>
<title>BTCPay Server</title>
<link href="~/_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" asp-append-version="true"/>
<link href="~/_content/BTCPayApp.UI/css/mudblazor-overrides.css" rel="stylesheet" asp-append-version="true"/>
<link href="~/_content/BTCPayApp.UI/css/theme.css" rel="stylesheet" asp-append-version="true"/>
<link href="~/_content/BTCPayApp.UI/css/fonts.css" rel="stylesheet" asp-append-version="true"/>
<link href="~/_content/BTCPayApp.UI/css/site.css" rel="stylesheet" asp-append-version="true"/>
<link href="~/_content/BTCPayApp.UI/favicon.png" rel="icon" type="image/png" asp-append-version="true"/>
<link href="_content/BTCPayApp.UI/favicon.png" rel="icon" type="image/png" asp-append-version="true"/>
<link href="_content/BTCPayApp.UI/bootstrap/bootstrap.css" rel="stylesheet" asp-append-version="true"/>
<link href="_content/BTCPayApp.UI/css/fonts.css" rel="stylesheet" asp-append-version="true"/>
<link href="_content/BTCPayApp.UI/css/site.css" rel="stylesheet" asp-append-version="true"/>
<link href="_content/BTCPayApp.UI/css/theme.css" rel="stylesheet" asp-append-version="true"/>
<link href="_content/BTCPayApp.UI/BTCPayApp.UI.bundle.scp.css" rel="stylesheet" />
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
</head>
<body>
@ -31,8 +31,8 @@
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="~/_content/BTCPayApp.UI/js/site.js" asp-append-version="true"></script>
<script src="~/_content/MudBlazor/MudBlazor.min.js" asp-append-version="true"></script>
<script src="~/_framework/blazor.server.js" asp-append-version="true"></script>
<script src="_framework/blazor.server.js" asp-append-version="true"></script>
<script src="_content/BTCPayApp.UI/bootstrap/bootstrap.bundle.min.js" asp-append-version="true"></script>
<script src="_content/BTCPayApp.UI/js/site.js" asp-append-version="true"></script>
</body>
</html>

View File

@ -1,4 +1,12 @@
<Fluxor.Blazor.Web.StoreInitializer/>
@using BTCPayApp.UI.Features
@using BTCPayApp.Core.Contracts
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IStateSelection<RootState, HashSet<RootState.LoadingHandles>> LoadingStateSelection
@inject IDispatcher Dispatcher
@inject IConfigProvider ConfigProvider
<PageTitle>BTCPay Server</PageTitle>
<Fluxor.Blazor.Web.StoreInitializer/>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
@ -7,7 +15,23 @@
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<MudAlert Severity="Severity.Warning">This page does not exist.</MudAlert>
<div class="alert alert-warning">This page does not exist.</div>
</LayoutView>
</NotFound>
</Router>
@code {
protected override async Task OnInitializedAsync()
{
LoadingStateSelection.Select(state => state.Loading);
var state = await ConfigProvider.Get<UIState>(StateMiddleware.UiStateConfigKey);
if (state != null)
{
if (!string.IsNullOrEmpty(state.SelectedTheme))
{
Dispatcher.Dispatch(new UIState.ApplyUserTheme(state.SelectedTheme));
}
}
await base.OnInitializedAsync();
}
}

View File

@ -15,19 +15,6 @@
<ItemGroup>
<PackageReference Include="Fluxor.Blazor.Web" Version="5.8.0" />
<PackageReference Include="Fluxor.Blazor.Web.ReduxDevTools" Version="5.8.0" />
<PackageReference Include="MudBlazor" Version="6.8.0" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\FONT-LICENSE" />
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\css\open-iconic-bootstrap.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.eot" />
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.otf" />
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.svg" />
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.ttf" />
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.woff" />
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\ICON-LICENSE" />
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" />
</ItemGroup>
<ItemGroup>

View File

@ -1,249 +1,8 @@
using MudBlazor;
using MudBlazor.Utilities;
namespace BTCPayApp.UI;
namespace BTCPayApp.UI;
public static class Constants
{
public const string DarkTheme = "dark";
public const string LightTheme = "light";
public const string SystemTheme = "system";
public static class Colors
{
public static class Brand
{
public static MudColor Primary { get; } = "#51B13E";
public static MudColor Secondary { get; } = "#CEDC21";
public static string Tertiary { get; } = "#1E7A44";
public static MudColor Dark { get; } = "#0F3B21";
}
public static string Transparent { get; } = "transparent";
public static MudColor White { get; } = "#FFFFFF";
public static MudColor Black { get; } = "#000000";
public static MudColor Light100 { get; } = "#F8F9FA";
public static MudColor Light200 { get; } = "#E9ECEF";
public static MudColor Light300 { get; } = "#DEE2E6";
public static MudColor Light400 { get; } = "#CED4DA";
public static MudColor Light500 { get; } = "#8F979E";
public static MudColor Light600 { get; } = "#6C757D";
public static MudColor Light700 { get; } = "#495057";
public static MudColor Light800 { get; } = "#343A40";
public static MudColor Light900 { get; } = "#292929";
public static MudColor Dark100 { get; } = "#F0F6FC";
public static MudColor Dark200 { get; } = "#C9D1D9";
public static MudColor Dark300 { get; } = "#B1BAC4";
public static MudColor Dark400 { get; } = "#8B949E";
public static MudColor Dark500 { get; } = "#6E7681";
public static MudColor Dark600 { get; } = "#484F58";
public static MudColor Dark700 { get; } = "#30363D";
public static MudColor Dark800 { get; } = "#21262D";
public static MudColor Dark900 { get; } = "#161B22";
public static MudColor Dark950 { get; } = "#0D1117";
public static MudColor Primary100 { get; } = "#C7E6C1";
public static MudColor Primary200 { get; } = "#B5DEAD";
public static MudColor Primary300 { get; } = "#9DD392";
public static MudColor Primary400 { get; } = "#7CC46E";
public static MudColor Primary500 { get; } = "#44A431";
public static MudColor Primary600 { get; } = "#389725";
public static MudColor Primary700 { get; } = "#2E8A1B";
public static MudColor Primary800 { get; } = "#247D12";
public static MudColor Primary900 { get; } = "#1C710B";
public static MudColor Green100 { get; } = "#EEFAEB";
public static MudColor Green200 { get; } = "#C7E8C0";
public static MudColor Green300 { get; } = "#A0D695";
public static MudColor Green400 { get; } = "#78C369";
public static MudColor Green500 { get; } = "#51B13E";
public static MudColor Green600 { get; } = "#419437";
public static MudColor Green700 { get; } = "#307630";
public static MudColor Green800 { get; } = "#205928";
public static MudColor Green900 { get; } = "#0F3B21";
public static MudColor Yellow100 { get; } = "#FFFAF0";
public static MudColor Yellow200 { get; } = "#FFF2D9";
public static MudColor Yellow300 { get; } = "#FFE3AC";
public static MudColor Yellow400 { get; } = "#FFCF70";
public static MudColor Yellow500 { get; } = "#FFC043";
public static MudColor Yellow600 { get; } = "#BC8B2C";
public static MudColor Yellow700 { get; } = "#997328";
public static MudColor Yellow800 { get; } = "#674D1B";
public static MudColor Yellow900 { get; } = "#543D10";
public static MudColor Red100 { get; } = "#FFEFED";
public static MudColor Red200 { get; } = "#FED7D2";
public static MudColor Red300 { get; } = "#F1998E";
public static MudColor Red400 { get; } = "#E85C4A";
public static MudColor Red500 { get; } = "#E11900";
public static MudColor Red600 { get; } = "#AB1300";
public static MudColor Red700 { get; } = "#870F00";
public static MudColor Red800 { get; } = "#5A0A00";
public static MudColor Red900 { get; } = "#420105";
public static MudColor Blue100 { get; } = "#B5E1E8";
public static MudColor Blue200 { get; } = "#9DD7E1";
public static MudColor Blue300 { get; } = "#7CCAD7";
public static MudColor Blue400 { get; } = "#51B9C9";
public static MudColor Blue500 { get; } = "#17A2B8";
public static MudColor Blue600 { get; } = "#03899E";
public static MudColor Blue700 { get; } = "#007D91";
public static MudColor Blue800 { get; } = "#007284";
public static MudColor Blue900 { get; } = "#006778";
}
// https://mudblazor.com/customization/default-theme
// https://design.btcpayserver.org/styles/btcpayserver-variables.css
public static readonly MudTheme Theme = new ()
{
Typography = new Typography
{
Default = new Default
{
FontFamily = new[] { "Open Sans", "Helvetica Neue", "Arial", "sans-serif" },
FontSize = "16px",
LineHeight = 1.6,
LetterSpacing = "0"
},
H1 = new H1
{
FontSize = "calc(1.34375rem + 1.125vw)",
FontWeight = 700,
LineHeight = 1.2,
LetterSpacing = "0"
},
H2 = new H2
{
FontSize = "calc(1.3rem + 0.6vw)",
FontWeight = 700,
LineHeight = 1.2,
LetterSpacing = "0"
},
H3 = new H3
{
FontSize = "calc(1.27813rem + 0.3375vw)",
FontWeight = 700,
LineHeight = 1.2,
LetterSpacing = "0"
},
H4 = new H4
{
FontSize = "calc(1.25625rem + 0.075vw)",
FontWeight = 700,
LineHeight = 1.2,
LetterSpacing = "0"
},
H5 = new H5
{
FontSize = "1.09375rem",
FontWeight = 700,
LineHeight = 1.2,
LetterSpacing = "0"
},
H6 = new H6
{
FontSize = "0.875rem",
FontWeight = 700,
LineHeight = 1.2,
LetterSpacing = "0"
},
Button = new Button
{
FontSize = "1rem",
FontWeight = 600,
LineHeight = 1.6,
LetterSpacing = null,
TextTransform = "none"
}
},
Palette = new PaletteLight
{
Black = Colors.Black,
White = Colors.White,
Primary = Colors.Primary500,
PrimaryDarken = Colors.Brand.Tertiary,
PrimaryContrastText = Colors.White,
Secondary = Colors.Primary500,
SecondaryContrastText = Colors.Primary500,
Tertiary = Colors.Light500,
TertiaryContrastText = Colors.White,
Info = Colors.Blue500,
InfoContrastText = Colors.White,
Success = Colors.Green500,
SuccessContrastText = Colors.White,
Warning = Colors.Yellow500,
WarningContrastText = Colors.White,
Error = Colors.Red500,
ErrorContrastText = Colors.White,
Dark = Colors.Light800,
DarkContrastText = Colors.White,
TextPrimary = Colors.Light900,
TextSecondary = Colors.Light500,
TextDisabled = Colors.Light600,
ActionDefault = Colors.Light500,
ActionDisabled = Colors.Light600,
ActionDisabledBackground = Colors.Light200,
Background = Colors.Light100,
BackgroundGrey = Colors.White,
Surface = Colors.Light100,
DrawerBackground = Colors.White,
DrawerText = Colors.Light900,
DrawerIcon = Colors.Light900,
AppbarBackground = Colors.White,
AppbarText = Colors.Light900,
LinesDefault = Colors.Light200,
LinesInputs = Colors.Light300,
TableLines = Colors.Light200,
TableStriped = Colors.Light200,
TableHover = Colors.White,
Divider = Colors.Light300,
DividerLight = Colors.Light200,
},
PaletteDark = new PaletteDark
{
Black = Colors.Black,
White = Colors.White,
Primary = Colors.Primary500,
PrimaryDarken = Colors.Brand.Tertiary,
PrimaryContrastText = Colors.White,
Secondary = Colors.Dark900,
SecondaryContrastText = Colors.Primary500,
Tertiary = Colors.Dark500,
TertiaryContrastText = Colors.Black,
Info = Colors.Blue500,
InfoContrastText = Colors.White,
Success = Colors.Green500,
SuccessContrastText = Colors.White,
Warning = Colors.Yellow500,
WarningContrastText = Colors.White,
Error = Colors.Red500,
ErrorContrastText = Colors.White,
Dark = Colors.Dark200,
DarkContrastText = Colors.Black,
TextPrimary = Colors.White,
TextSecondary = Colors.Dark500,
TextDisabled = Colors.Dark400,
ActionDefault = Colors.Dark500,
ActionDisabled = Colors.Dark400,
ActionDisabledBackground = Colors.Dark700,
Background = Colors.Dark900,
BackgroundGrey = Colors.Dark950,
Surface = Colors.Dark900,
DrawerBackground = Colors.Dark950,
DrawerText = Colors.White,
DrawerIcon = Colors.White,
AppbarBackground = Colors.Dark950,
AppbarText = Colors.White,
LinesDefault = Colors.Dark800,
LinesInputs = Colors.Dark700,
TableLines = Colors.Dark800,
TableStriped = Colors.Dark800,
TableHover = Colors.Dark950,
Divider = Colors.Dark700,
DividerLight = Colors.Dark800,
}
};
}

View File

@ -38,12 +38,14 @@ public record OnChainTransactionsState(Dictionary<string, OnChainTransaction>? A
_state = state;
}
public override async Task HandleAsync(RootState.BTCPayConnectionUpdatedAction action, IDispatcher dispatcher)
public override Task HandleAsync(RootState.BTCPayConnectionUpdatedAction action, IDispatcher dispatcher)
{
if (action.ConnectionState is HubConnectionState.Connected)
{
dispatcher.Dispatch(new LoadTransactionsAction(true));
}
return Task.CompletedTask;
}
}
@ -83,7 +85,7 @@ public record OnChainTransactionsState(Dictionary<string, OnChainTransaction>? A
? OnChainTransactionStatus.Mempool
: OnChainTransactionStatus.Confirmed))));
}
catch (Exception e)
catch (Exception)
{
dispatcher.Dispatch(new SetError(true));
}
@ -113,4 +115,4 @@ public record OnChainTransactionsState(Dictionary<string, OnChainTransaction>? A
return state with {AllTransactions = allTransactions};
}
}
}
}

View File

@ -11,21 +11,8 @@ public class UIState
public bool IsDarkMode { get; set; }
public record ApplyUserTheme(string Theme);
public record SetSystemPreference(string Theme);
public record SetUserTheme(string Theme);
[ReducerMethod]
public static UIState Reduce(UIState state, SetSystemPreference action)
{
var effectiveTheme = state.SelectedTheme == Constants.SystemTheme ? action.Theme : state.SelectedTheme;
return new UIState
{
SystemTheme = action.Theme,
SelectedTheme = state.SelectedTheme,
IsDarkMode = effectiveTheme == Constants.DarkTheme
};
}
[ReducerMethod]
public static UIState Reduce(UIState state, SetUserTheme action)
{

View File

@ -1,33 +0,0 @@
using BTCPayApp.Core.Contracts;
using MudBlazor;
namespace BTCPayApp.UI;
public class MudBlazorThemeProvider : ISystemThemeProvider
{
private MudThemeProvider? _mudThemeProvider;
public MudThemeProvider? MudThemeProvider
{
get => _mudThemeProvider;
set
{
_mudThemeProvider = value;
if (_mudThemeProvider is not null)
{
_mudThemeProvider.WatchSystemPreference(b =>
{
SystemThemeChanged?.Invoke(this, b);
return Task.CompletedTask;
});
}
}
}
public Task<bool> IsDarkMode()
{
return Task.FromResult(MudThemeProvider?.IsDarkMode is true);
}
public event EventHandler<bool>? SystemThemeChanged;
}

View File

@ -7,8 +7,8 @@
<PageTitle>Pair with your BTCPay Server</PageTitle>
<MudText Typo="Typo.h1" Class="mb-4">Pair with your BTCPay Server</MudText>
<MudText>Find the pairing URL on the Lightning Node connection page of your BTCPay Server store.</MudText>
<h1 class="mb-3">Pair with your BTCPay Server</h1>
<p>Find the pairing URL on the Lightning Node connection page of your BTCPay Server store.</p>
<PairBTCPayServer OnPair="OnPair"></PairBTCPayServer>

View File

@ -4,4 +4,7 @@
<PageTitle>Point Of Sale</PageTitle>
<Keypad />
<div class="public-page-wrap">
<Keypad CurrencyDivisibility="2" CurrencySymbol="€" CurrencyCode="EUR"
IsDiscountEnabled="true" IsTipEnabled="true" CustomTipPercentages="new []{ 5, 10, 21 }" />
</div>

View File

@ -6,7 +6,7 @@
<PageTitle>Settings</PageTitle>
<MudText Typo="Typo.h1" Class="mb-8">Settings</MudText>
<h1 class="mb-3">Settings</h1>
<Settings Theme="@UiState.Value.SelectedTheme" OnThemeChange="theme => Dispatcher.Dispatch(new UIState.ApplyUserTheme(theme))" />

View File

@ -6,7 +6,7 @@
<PageTitle>Wallet Setup</PageTitle>
<MudText Typo="Typo.h1" Class="mb-4">Wallet Setup</MudText>
<h1 class="mb-3">Wallet Setup</h1>
<WalletSetup OnSeedChosen="SetSeed" />

View File

@ -1,22 +1,23 @@
@using System.ComponentModel.DataAnnotations
@*
@using System.ComponentModel.DataAnnotations
@using NBitcoin
@* if there is a configured wallet, and it is hot, ask if the user would like to import and use that within the app *@
@* if there is a configured wallet, and it is not hot, ask if the user would like to provide the seed to the app *@
@* if there is no configured wallet, ask if the user would like to create a new wallet *@
@* if the user would like to create a new wallet, ask if the user would like to import it into BTCPayServer store as a hot or cold wallet *@
$1$ if there is a configured wallet, and it is hot, ask if the user would like to import and use that within the app #1#
$1$ if there is a configured wallet, and it is not hot, ask if the user would like to provide the seed to the app #1#
$1$ if there is no configured wallet, ask if the user would like to create a new wallet #1#
$1$ if the user would like to create a new wallet, ask if the user would like to import it into BTCPayServer store as a hot or cold wallet #1#
@if (ConfiguredWallet is not null)
{
<MudAlert Severity="Severity.Warning" Class="mb-4">
<div class="alert alter-warning">
@if (IsConfiguredWalletHot)
{
<MudText>Your BTCPay Server store already has an onchain hot wallet configured.</MudText>
<div>Your BTCPay Server store already has an onchain hot wallet configured.</div>
}
else
{
<MudText>Your BTCPay Server store already has an onchain watch-only wallet configured.</MudText>
<div>Your BTCPay Server store already has an onchain watch-only wallet configured.</div>
}
</MudAlert>
</div>
}
<ValidationEditContext Model="this" @ref="_validationEditContext" Validate="Validate" OnValidSubmit="OnSubmit">
@ -26,7 +27,7 @@
<div>
<MudRadio T="string" Option="@option.Key" Class="align-start">
<div class="d-flex flex-wrap gap-3 pt-3">
<MudText>@option.Value.Item1</MudText>
<p>@option.Value.Item1</p>
@if (option.Value.Item2)
{
<MudChip Variant="Variant.Text" Color="Color.Warning" Size="Size.Small" Class="ma-0">Your existing wallet will be overwritten!</MudChip>
@ -38,18 +39,15 @@
</MudRadioGroup>
@if (IsMnemonicRequired)
{
<MudTextField
@bind-Value="Mnemonic"
HelperText="Mnemonic Seed"
Variant="Variant.Outlined"
AdornmentIcon="@Icons.Material.Filled.Refresh"
Adornment="Adornment.End"
OnAdornmentClick="GenerateSeed"
For="() => Mnemonic"/>
<div class="input-group mb-3">
<label for="Mnemonic" class="form-label">Mnemonic Seed</label>
<input id="Mnemonic" class="form-control" @bind-Value="Mnemonic">
<button class="btn btn-outline-secondary" type="button" @onclick="GenerateSeed">Generate</button>
</div>
}
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Disabled="_validationEditContext.Invalid" Class="mt-3">
<button type="submit" class="btn btn-primary mt-3" @bind-Disabled="_validationEditContext.Invalid">
Continue
</MudButton>
</button>
</ValidationEditContext>
@code {
@ -164,3 +162,4 @@
public record ChosenConfiguration(bool UseExisting, bool SetStore, string? Mnemonic);
}
*@

View File

@ -1,4 +1,5 @@
@using BTCPayServer.Client
@*
@using BTCPayServer.Client
@using BTCPayServer.Client.Models
@implements IDisposable
@ -116,3 +117,4 @@
}
}
*@

View File

@ -9,20 +9,21 @@
@if (Result is null)
{
<ValidationEditContext Model="this" @ref="_validationEditContext" OnValidSubmit="StartPair">
<MudTextField Label="Pairing URL" @bind-Value="PairingUrl" For="() => PairingUrl"/>
<MudErrorMessage For="() => PairingUrl"></MudErrorMessage>
<MudGrid Class="mt-4">
<MudItem>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Disabled="_validationEditContext.Invalid">
Submit
</MudButton>
</MudItem>
</MudGrid>
<ErrorMessage For="() => PairingUrl"></ErrorMessage>
<div class="form-group">
<label for="PairingURL" class="form-label">Pairing URL</label>
<InputText id="PairingURL" class="form-control" @bind-Value="PairingUrl" />
</div>
<button type="submit" class="btn btn-primary" disabled="@_validationEditContext.Invalid">
Submit
</button>
</ValidationEditContext>
}
else if (Result.StoreId is null)
{
@*
<BTCPayServerStoreSelector Key="@Result.Key" Url="@PairingUrl" bind-SelectedStoreId="SelectedStoreId" OnCreateStore="CreateStore" />
*@
}
@code {

View File

@ -1,13 +1,15 @@
@using BTCPayApp.UI.Pages
@using BTCPayApp.UI.Features
<MudGrid Justify="Justify.Center">
<MudSelect @bind-Value="@ThemeMode" Label="Theme" Variant="Variant.Outlined">
<MudSelectItem Value="@Constants.SystemTheme">System</MudSelectItem>
<MudSelectItem Value="@Constants.LightTheme">Light</MudSelectItem>
<MudSelectItem Value="@Constants.DarkTheme">Dark</MudSelectItem>
</MudSelect>
</MudGrid>
<div class="form-group">
<label class="form-label">Theme</label>
<InputSelect class="form-select w-auto" @bind-Value="ThemeMode">
<option value="@Constants.SystemTheme">System</option>
<option value="@Constants.LightTheme">Light</option>
<option value="@Constants.DarkTheme">Dark</option>
</InputSelect>
</div>
@code {
[Parameter]

View File

@ -2,18 +2,17 @@
@using NBitcoin
<ValidationEditContext Model="this" @ref="_validationEditContext" Validate="Validate" OnValidSubmit="OnSubmit">
<MudText>Please write down this seed and keep it safe. You will need it to recover your wallet.</MudText>
<MudTextField
@bind-Value="Mnemonic"
HelperText="Mnemonic Seed"
Variant="Variant.Outlined"
AdornmentIcon="@Icons.Material.Filled.Refresh"
Adornment="Adornment.End"
OnAdornmentClick="GenerateSeed"
For="() => Mnemonic"/>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Disabled="_validationEditContext.Invalid" Class="mt-3">
<p>Please write down this seed and keep it safe. You will need it to recover your wallet.</p>
<div class="form-group">
<label for="Mnemonic" class="form-label">Mnemonic Seed</label>
<div class="input-group">
<InputText id="Mnemonic" class="form-control" @bind-Value="Mnemonic" />
<button class="btn btn-outline-secondary" type="button" @onclick="GenerateSeed">Generate</button>
</div>
</div>
<button type="submit" class="btn btn-primary mt-3" disabled="@_validationEditContext.Invalid">
Continue
</MudButton>
</button>
</ValidationEditContext>
@code {

View File

@ -7,124 +7,120 @@
@using BTCPayApp.Core
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
<MudText Typo="Typo.h5" Class="mb-4">Dashboard</MudText>
<h1 class="mb-3">Dashboard</h1>
@if (State.Value.Loading.Contains(RootState.LoadingHandles.PairConfig))
{
<MudAlert Severity="Severity.Info">Pairing configuration loading.</MudAlert>
<div class="alert alert-info">Pairing configuration loading.</div>
}
else if (State.Value.PairConfig is not null)
{
<MudAlert Severity="@ConnectionSeverity">
<div class="alert alert-@ConnectionSeverity">
<div class="d-flex align-center justify-space-between gap-3">
Your BTCPay Server is paired. Connection status is @ConnectionStatus.
@if (State.Value.BTCPayServerConnectionState == HubConnectionState.Disconnected)
{
<MudButton Href="@Routes.Pair" Variant="Variant.Text" Color="Color.Error" Size="Size.Small">
<NavLink href="@Routes.Pair" class="btn btn-sm btn-danger">
Configure connection
</MudButton>
</NavLink>
}
</div>
</MudAlert>
</div>
}
else
{
<MudAlert Severity="Severity.Warning">
<div class="alert alert-warning">
<div class="d-flex flex-wrap align-center justify-space-between gap-3">
No BTCPay Server paired.
<MudButton Href="@Routes.Pair" Variant="Variant.Filled" Color="Color.Primary" Size="Size.Small">
<NavLink href="@Routes.Pair" class="btn btn-sm btn-primary">
Configure connection
</MudButton>
</NavLink>
</div>
</MudAlert>
</div>
}
@if (State.Value.Loading.Contains(RootState.LoadingHandles.WalletConfig))
{
<MudAlert Severity="Severity.Info">Wallet configuration loading.</MudAlert>
<div class="alert alert-info">Wallet configuration loading.</div>
}
else if (State.Value.WalletConfig is not null)
{
<MudAlert Severity="Severity.Success">Your wallet is configured.</MudAlert>
<div class="alert alert-success">Your wallet is configured.</div>
}
else
{
<MudAlert Severity="Severity.Warning">
<div class="alert alert-warning">
<div class="d-flex flex-wrap align-center justify-space-between gap-3">
Your wallet is not configured.
<MudButton Href="@Routes.WalletSetup" Variant="Variant.Filled" Color="Color.Primary" Size="Size.Small">
<NavLink href="@Routes.WalletSetup" class="btn btn-sm btn-primary">
Set up a wallet
</MudButton>
</NavLink>
</div>
</MudAlert>
</div>
}
@if (State.Value.Loading.Contains(RootState.LoadingHandles.LightningState))
{
<MudAlert Severity="Severity.Info">Lightning node loading.</MudAlert>
<div class="alert alert-info">Lightning node loading.</div>
}
else
{
<MudAlert Severity="@LightningSeverity">Your Lightning node is @LightningStatus</MudAlert>
<div class="alert alert-@LightningSeverity">Your Lightning node is @LightningStatus</div>
}
@if (State.Value.Loading.Contains(RootState.LoadingHandles.TransactionState))
{
<MudAlert Severity="Severity.Info">Transactions loading.</MudAlert>
<div class="alert alert-info">Transactions loading.</div>
}
else
{
<MudText Typo="Typo.h6" Class="mt-8" GutterBottom="true">Transactions</MudText>
<h6 class="mt-5">Transactions</h6>
@if (OnChainTransactionsState.Value.Error is not false)
{
<MudAlert Severity="Severity.Warning">
<div class="alert alert-warning">
<div class="d-flex flex-wrap align-center justify-space-between gap-3">
There was an error loading your transactions.
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Small" OnClick="() => Dispatcher.Dispatch(new OnChainTransactionsState.LoadTransactionsAction())">
<button class="btn btn-sm btn-primary" @onclick="() => Dispatcher.Dispatch(new OnChainTransactionsState.LoadTransactionsAction())">
Retry
</MudButton>
</button>
</div>
</MudAlert>
</div>
}
else if (OnChainTransactionsState.Value.AllTransactions?.Any() is true)
{
<MudList>
<ul>
@foreach (var tx in OnChainTransactionsState.Value.AllTransactions)
{
<MudListItem>
<MudListItemText>
<div class="d-flex align-center justify-space-between gap-3">
<div>
<MudText Typo="Typo.body2" Class="mb-1">@tx.Value.Id</MudText>
<MudText Typo="Typo.caption" Class="mb-1">@tx.Value.Timestamp</MudText>
</div>
<div>
<MudText Typo="Typo.body2" Class="mb-1">@tx.Value.Balance</MudText>
<MudText Typo="Typo.caption" Class="mb-1">@tx.Value.Status</MudText>
</div>
</div>
</MudListItemText>
</MudListItem>
<li class="d-flex align-center justify-space-between gap-3">
<div>
<div class="mb-1">@tx.Value.Id</div>
<div class="mb-1">@tx.Value.Timestamp</div>
</div>
<div>
<div class="mb-1">@tx.Value.Balance</div>
<div class="mb-1">@tx.Value.Status</div>
</div>
</li>
}
</MudList>
<MudButton Variant="Variant.Filled" Color="Color.Default" Size="Size.Small" OnClick="() => Dispatcher.Dispatch(new OnChainTransactionsState.LoadTransactionsAction())">
</ul>
<button class="btn btn-sm btn-secondary" @onclick="() => Dispatcher.Dispatch(new OnChainTransactionsState.LoadTransactionsAction())">
Refresh
</MudButton>
</button>
}
else
{
<MudText GutterBottom="true" Color="Color.Tertiary">There are no transactions, yet.</MudText>
<p class="text-muted">There are no transactions, yet.</p>
}
}
@code {
private string ConnectionStatus => State.Value.BTCPayServerConnectionState?.ToString().ToLowerInvariant() ?? "not connected";
private Severity ConnectionSeverity => State.Value.BTCPayServerConnectionState switch {
HubConnectionState.Disconnected => Severity.Warning,
HubConnectionState.Connected => Severity.Success,
HubConnectionState.Connecting => Severity.Info,
HubConnectionState.Reconnecting => Severity.Info,
_ => Severity.Warning
private string ConnectionSeverity => State.Value.BTCPayServerConnectionState switch {
HubConnectionState.Disconnected => "warning",
HubConnectionState.Connected => "success",
HubConnectionState.Connecting => "info",
HubConnectionState.Reconnecting => "info",
_ => "warning"
};
private string LightningStatus => State.Value.LightningNodeState switch {
@ -135,11 +131,11 @@ else
_ => State.Value.LightningNodeState.ToString().ToLowerInvariant()
};
private Severity LightningSeverity => State.Value.LightningNodeState switch {
LightningNodeState.Starting => Severity.Info,
LightningNodeState.Running => Severity.Success,
LightningNodeState.Error => Severity.Error,
_ => Severity.Warning
private string LightningSeverity => State.Value.LightningNodeState switch {
LightningNodeState.Starting => "info",
LightningNodeState.Running => "success",
LightningNodeState.Error => "danger",
_ => "warning"
};
protected override Task OnAfterRenderAsync(bool firstRender)

View File

@ -4,14 +4,13 @@
var messages = EditContext.GetValidationMessages(For);
foreach (var message in messages)
{
<MudText Color="Color.Error">@message</MudText>
<div class="text-danger">@message</div>
}
}
@code {
[CascadingParameter]
public EditContext EditContext{ get; set; }
public EditContext EditContext { get; set; }
[Parameter]
public Expression<Func<object>> For { get; set; }
@ -32,4 +31,4 @@
EditContext.OnValidationStateChanged -= EditContextOnOnValidationStateChanged;
}
}
}

View File

@ -0,0 +1,8 @@
<svg role="img" class="icon icon-@Symbol">
<use href="_content/BTCPayApp.UI/img/icon-sprite.svg#@Symbol"></use>
</svg>
@code {
[Parameter]
public string Symbol { get; set; }
}

After

Width:  |  Height:  |  Size: 183 B

View File

@ -0,0 +1,10 @@
svg.icon {
display: inline-block;
width: 1rem;
height: 1rem;
flex-shrink: 0 !important;
}
svg.icon-note {
color: var(--btcpay-neutral-500);
}

View File

@ -1,222 +1,302 @@
@using BTCPayApp.UI.Pages
@using BTCPayApp.UI.Shared
@using Microsoft.AspNetCore.Components
@using MudBlazor
@using System.Globalization
@using org.ldk.structs
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@layout SimpleLayout
<link href="./_content/BTCPayApp.UI/css/keypad.css" rel="stylesheet"/>
<MudContainer MaxWidth="MaxWidth.Small">
<ValidationEditContext Model="this" @ref="_validationEditContext" OnValidSubmit="HandleSubmit" Class="d-flex flex-column gap-4 py-5" Style="min-height:90vh">
<div class="d-flex flex-column align-center px-4 mb-auto">
<MudText Typo="Typo.overline" Color="Color.Tertiary" Class="fw-semibold">@Currency</MudText>
<MudText Typo="Typo.h1" Class="mb-4" Style="@AmountStyle">@FormatCurrency(Amount, false)</MudText>
@if (!string.IsNullOrEmpty(Calculation) && EnableDiscount || EnableTips)
@using Newtonsoft.Json.Linq
<form class="d-flex flex-column gap-4" @onsubmit="Submit">
<div ref="display" class="d-flex flex-column align-items-center px-4 mb-auto">
<div class="fw-semibold text-muted" id="Currency">@CurrencyCode</div>
<div class="fw-bold lh-sm" style="font-size:@(FontSize + "px")" id="Amount">@FormatCurrency(GetTotal(), false)</div>
<div class="text-muted text-center mt-2" id="Calculation">@Calculation(Model)</div>
</div>
@if (IsDiscountEnabled || IsTipEnabled)
{
<div id="ModeTabs" class="tab-content mb-n2">
@if (IsDiscountEnabled)
{
<MudText Typo="Typo.overline" Color="Color.Secondary" Class="text-muted text-center mt-2" id="Calculation">@Calculation</MudText>
<div class="tab-pane fade px-2 @(Mode == InputMode.Discount ? "show active" : "")" role="tabpanel" aria-labelledby="ModeTablist-Discount">
<div class="h4 fw-semibold text-muted text-center" id="Discount">
<span class="h3 text-body me-1">@Model.DiscountPercent%</span> discount
</div>
</div>
}
@if (IsTipEnabled)
{
var tip = GetTip();
<div class="tab-pane fade px-2 @(Mode == InputMode.Tip ? "show active" : "")" role="tabpanel" aria-labelledby="ModeTablist-Tip">
<div class="btcpay-pills d-flex flex-wrap align-items-center justify-content-center gap-2">
@if (CustomTipPercentages != null)
{
<button
id="Tip-Custom"
type="button"
class="btcpay-pill @(Model.TipPercent == null ? "active" : "")"
@onclick="() => Model.TipPercent = null">
@(Model.Tip is > 0 ? FormatCurrency(Model.Tip.Value, true) : "Custom")
</button>
@foreach(var percentage in CustomTipPercentages)
{
<button
type="button"
id="Tip-@percentage"
class="btcpay-pill @(Model.TipPercent == percentage ? "active" : "")"
@onclick="() => Model.TipPercent = percentage">
@percentage%
</button>
}
}
else
{
<div class="h5 fw-semibold text-muted text-center">
Amount@(tip > 0 ? $": {FormatCurrency(tip, true)}" : "")
</div>
}
</div>
</div>
}
</div>
@if (false && Modes.Count > 1)
<div id="ModeTablist" class="nav btcpay-pills align-items-center justify-content-center mb-n2 pb-1" role="tablist">
@foreach (var mode in GetModes())
{
<input id="ModeTablist-@mode" name="mode" value="@mode" type="radio" role="tab"
aria-controls="Mode-@mode" aria-selected="@(Mode == mode ? "true" : "false")"
data-bs-toggle="pill" data-bs-target="#Mode-@mode"
checked="@(Mode == mode)"
disabled="@(Mode != InputMode.Amount && GetAmount() is 0)"
@onclick="() => Mode = mode">
<label for="ModeTablist-@mode">@mode</label>
}
</div>
}
<div class="keypad">
@foreach (var key in Keys)
{
<div id="ModeTabs" class="tab-content mb-n2">
@if (EnableDiscount)
{
<div id="Mode-Discount" class="tab-pane fade px-2" :class="{ show: mode === 'discount', active: mode === 'discount' }" role="tabpanel" aria-labelledby="ModeTablist-Discount">
<div class="h4 fw-semibold text-muted text-center" id="Discount">
<span class="h3 text-body me-1">@DiscountPercent%</span> discount
</div>
</div>
}
@if (EnableTips)
{
<div id="Mode-Tip" class="tab-pane fade px-2" :class="{ show: mode === 'tip', active: mode === 'tip' }" role="tabpanel" aria-labelledby="ModeTablist-Tip">
<div class="btcpay-pills d-flex flex-wrap align-items-center justify-content-center gap-2">
@if (CustomTipPercentages.Count > 0)
{
<MudButton Variant="Variant.Filled" DisableElevation="true" Class="btcpay-pill" OnClick="() => { TipPercent = null; }">
@if (Tip > 0)
{
@FormatCurrency(Tip, true)
}
else
{
<span>Custom</span>
}
</MudButton>
@foreach (var percentage in CustomTipPercentages)
{
<MudButton Variant="Variant.Filled" DisableElevation="true" Class=@PillClass(percentage == TipPercent) OnClick="() => { TipPercent = percentage; }">
@percentage%
</MudButton>
}
}
else
{
<div class="h5 fw-semibold text-muted text-center">
Amount@(Tip > 0 ? $": {Tip}" : null)
</div>
}
</div>
</div>
}
</div>
<MudChipSet Mandatory="true" Class="d-flex align-center justify-center ">
@foreach (var mode in Modes)
{
var isActive = SelectedMode == mode;
<MudChip Variant="isActive ? Variant.Filled : Variant.Outlined" Color="Color.Secondary" Disabled="@(mode != "Amount" && Amount == 0)" OnClick="() => { SelectedMode = mode; }">@mode</MudChip>
}
</MudChipSet>
<button disabled="@(key == '+' && Mode != InputMode.Amount)" @onclick="@(e => KeyPress(key))" @onclick:preventDefault @ondblclick="@(e => DoublePress(key))" type="button" class="btn btn-secondary btn-lg" data-key="@key">@key</button>
}
<div class="keypad">
@foreach(var key in Keys)
{
<MudButton DisableElevation="true" @onclick="() => KeyPressed(key)" data-key="@key">
@if (key == "del")
{
<MudIcon Icon="@BTCPayIcons.CaretRight" aria-label="delete"/>
}
else
{
@key
}
</MudButton>
</div>
<button class="btn btn-lg btn-primary mx-3" type="submit" disabled="@IsSubmitting" id="pay-button">
@if (IsSubmitting)
{
<div class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">Loading...</span>
</div>
}
else
{
<span>Charge</span>
}
</button>
</form>
@code {
[Parameter]
public string? CurrencyCode { get; set; }
[Parameter]
public string? CurrencySymbol { get; set; }
[Parameter]
public int CurrencyDivisibility { get; set; }
[Parameter]
public bool IsDiscountEnabled { get; set; }
[Parameter]
public bool IsTipEnabled { get; set; }
[Parameter]
public int[]? CustomTipPercentages { get; set; }
private bool IsSubmitting { get; set; }
const int DefaultFontSize = 64;
static char[] Keys = { '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '0', '+' };
private int FontSize { get; set; } = DefaultFontSize;
public enum InputMode
{
Amount,
Discount,
Tip
}
private InputMode Mode { get; set; } = InputMode.Amount;
public class KeypadModel
{
public List<decimal> Amounts { get; set; } = new ();
public int? DiscountPercent { get; set; }
public int? TipPercent { get; set; }
public decimal? Tip { get; set; }
}
private KeypadModel Model { get; set; } = new ();
private void Submit()
{
IsSubmitting = true;
}
private void KeyPress(char key = '1')
{
if (Mode == InputMode.Amount) {
var lastIndex = Model.Amounts.Count - 1;
var lastAmount = Model.Amounts[lastIndex];
if (key == 'C') {
if (lastAmount == 0 && lastIndex == 0) {
// clear completely
Clear();
} else if (lastAmount == 0) {
// remove latest value
Model.Amounts.RemoveAt(lastIndex);
} else {
// clear latest value
Model.Amounts[lastIndex] = 0;
}
} else if (key == '+' && lastAmount != 0) {
Model.Amounts.Add(0);
} else { // Is a digit
Model.Amounts[lastIndex] = ApplyKeyToValue(key, lastAmount, CurrencyDivisibility);
}
</div>
<div class="actions px-4 gap-4">
<MudButton Size="Size.Large" DisableElevation="true" Color="Color.Secondary" @onclick="@Clear">Clear</MudButton>
<MudButton Size="Size.Large" DisableElevation="true" Color="Color.Primary" Disabled="Loading" data-testid="ChargeButton">
@if (Loading)
} else {
if (key == 'C') {
if (Mode == InputMode.Tip)
{
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true"/>
Model.Tip = null;
Model.TipPercent = null;
}
else
{
<span>Charge</span>
Model.DiscountPercent = null;
}
</MudButton>
</div>
</ValidationEditContext>
</MudContainer>
@code {
private ValidationEditContext _validationEditContext { get; set; }
public bool EnableDiscount { get; set; } = true;
public bool EnableTips { get; set; } = true;
public bool Loading { get; set; }
public string Currency { get; set; } = "USD";
private int Divisibility { get; set; } = 2;
private string DisplayAmount { get; set; } = string.Empty;
public double Amount { get; set; }
public double Tip { get; set; }
public double Discount { get; set; }
public double? TipPercent { get; set; }
public double DiscountPercent { get; set; }
public List<int> CustomTipPercentages { get; set; } = new () { 5, 10, 21 };
public static readonly List<string> Keys = new() { "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "0", "del" };
public string SelectedMode { get; set; } = "Amount";
public List<string> Modes
{
get
{
var modes = new List<string> { "Amount" };
if (EnableDiscount) modes.Add("Discount");
if (EnableTips) modes.Add("Tips");
return modes;
} else {
var divisibility = Mode == InputMode.Tip ? CurrencyDivisibility : 0;
if (Mode == InputMode.Tip)
{
Model.Tip = ApplyKeyToValue(key, Model.Tip ?? 0, divisibility);
Model.TipPercent = null;
}
else
{
Model.DiscountPercent = (int)ApplyKeyToValue(key, Model.DiscountPercent ?? 0, divisibility);
}
}
}
}
public string AmountStyle
public decimal ApplyKeyToValue(char key, decimal value, int divisibility)
{
get
{
var fs = 64; // TODO: Calculate based on width
return $"font-size:{fs}px";
var val = value is 0 ? "" : Formatted(value, divisibility);
val = (val + key)
.Replace(".", "")
.PadLeft(divisibility, '0');
//.Replace(new Regex("(\\d*)(\\d{{divisibility}})", "\\1.\\2");
return decimal.Parse(val);
}
public void DoublePress(char key)
{
if (key == 'C') {
Clear();
}
}
public string Calculation
public void Clear()
{
get
{
return "";
}
Model.Amounts.Clear();
Model.DiscountPercent = null;
Model.TipPercent = null;
Model.Tip = null;
Mode = InputMode.Amount;
}
private void KeyPressed(string key)
private JObject GetData()
{
switch (SelectedMode) {
case "Amount":
DisplayAmount = ApplyKeyToValue(key, DisplayAmount);
Amount = double.Parse(DisplayAmount, CultureInfo.InvariantCulture);
break;
case "Discount":
var discount = ApplyKeyToValue(key, DiscountPercent.ToString(CultureInfo.InvariantCulture));
DiscountPercent = double.Parse(discount, CultureInfo.InvariantCulture);
break;
case "Tips":
var tipPercent = ApplyKeyToValue(key, (TipPercent ?? 0).ToString(CultureInfo.InvariantCulture));
TipPercent = double.Parse(tipPercent, CultureInfo.InvariantCulture);
break;
default:
throw new Exception("Invalid mode");
var data = new JObject
{
["subTotal"] = GetAmount(),
["total"] = GetTotal(),
};
}
private string ApplyKeyToValue (string key, string value) {
if (string.IsNullOrEmpty(value) || value == "0") {
value = "";
var discount = GetDiscount();
if (discount > 0)
{
data["discount"] = discount;
}
switch (key) {
case "del":
if (string.IsNullOrEmpty(value)) return "0";
value = value[..^1];
value = string.IsNullOrEmpty(value) ? "0" : value;
break;
case ".":
if (!value.Contains(".")) { // Only add decimal point if it doesn't exist yet
value += ".";
}
break;
default:
value += key;
var decimalIndex = value.IndexOf(".", StringComparison.Ordinal);
if (decimalIndex != -1 && value.Length - decimalIndex - 1 > Divisibility) {
value = value.Replace(".", "");
value = $"{value[..^Divisibility]}.{value[^Divisibility..]}";
}
break;
if (Model.DiscountPercent is > 0)
{
data["discountPercentage"] = Model.DiscountPercent;
}
return value;
var tip = GetTip();
if (tip > 0)
{
data["tip"] = tip;
}
if (Model.TipPercent != null)
{
data["tipPercentage"] = Model.TipPercent;
}
return data;
}
private void HandleSubmit()
private List<InputMode> GetModes()
{
var modes = new List<InputMode> { InputMode.Amount };
if (IsDiscountEnabled) modes.Add(InputMode.Discount);
if (IsTipEnabled) modes.Add(InputMode.Tip);
return modes;
}
private void Clear()
private decimal GetAmount()
{
Amount = Tip = Discount = DiscountPercent = 0;
DisplayAmount = string.Empty;
TipPercent = null;
SelectedMode = "Amount";
return Model.Amounts.Count > 0 ? Model.Amounts.Sum() : Model.Amounts.FirstOrDefault();
}
private string FormatCurrency(double value, bool b)
private decimal GetDiscount()
{
var formatted = value.ToString("C", CultureInfo.InvariantCulture);
return formatted[1..] + (b ? $" {Currency}" : "");
var amount = GetAmount();
return amount > 0 && Model.DiscountPercent is > 0
? Math.Round(amount * (Model.DiscountPercent.Value / 100.0m), CurrencyDivisibility)
: 0;
}
private string PillClass(bool isActive)
private decimal GetTip()
{
return $"btcpay-pill{(isActive ? " active" : "")}";
if (Model.TipPercent is > 0) {
var amount = GetAmount() - GetDiscount();
return Math.Round(amount * (Model.TipPercent.Value / 100.0m), CurrencyDivisibility);
}
return Model.Tip is > 0 ? Math.Round(Model.Tip.Value, CurrencyDivisibility) : 0.0m;
}
private decimal GetTotal()
{
return GetAmount() - GetDiscount() + GetTip();
}
private string? Calculation(KeypadModel model)
{
if (model.Amounts.Count < 2 && model.DiscountPercent is not > 0 && !model.Tip.HasValue) return null;
var calc = string.Join(" + ", model.Amounts.Select(amt => FormatCurrency(amt, true)));
var discount = GetDiscount();
if (discount > 0) calc += $" - {FormatCurrency(discount, true)} (${model.DiscountPercent}%)";
var tip = GetTip();
if (model.Tip > 0) calc += $" + ${FormatCurrency(tip, true)}";
if (model.TipPercent > 0) calc += $" (${model.TipPercent}%)";
return calc;
}
private string FormatCurrency(decimal value, bool withSymbol)
{
if (CurrencyCode is "BTC" or "SATS") return FormatCrypto(value, withSymbol);
try {
var symbol = withSymbol ? $" {CurrencySymbol ?? CurrencyCode}" : "";
return $"{Formatted(value, CurrencyDivisibility)}{symbol}";
}
catch (Exception)
{
return FormatCrypto(value, withSymbol);
}
}
private string FormatCrypto(decimal value, bool withSymbol) {
var symbol = withSymbol ? $" {CurrencySymbol ?? CurrencyCode}" : "";
return $"{Formatted(value, CurrencyDivisibility)}{symbol}";
}
private string Formatted(decimal value, int divisibility) {
return string.Format($"{{0:0.{new string('0', divisibility)}}}", value);
}
}

View File

@ -1,8 +1,3 @@
.public-page-wrap {
max-width: 560px;
overflow: hidden;
}
/* modes */
#ModeTabs {
min-height: 2.75rem;
@ -13,25 +8,22 @@
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.keypad button {
.keypad .btn {
display: flex;
align-items: center;
justify-content: center;
padding: 0;
position: relative;
border-radius: 0;
font-weight: var(--btcpay-font-weight-semibold);
font-size: 24px;
min-height: 3.5rem;
height: 8vh;
max-height: 6rem;
padding: 0 !important;
border-radius: 0 !important;
color: var(--btcpay-body-text) !important;
color: var(--btcpay-body-text);
}
.keypad button[data-key="del"] svg.mud-svg-icon {
width: 2.25rem;
height: 2.25rem;
transform: rotate(180deg);
transform-origin: center;
.keypad .btn[data-key="+"] {
font-size: 2.25em;
}
.btcpay-pills label,
.btn-secondary.rounded-pill {
@ -41,55 +33,28 @@
/* make borders collapse by shifting rows and columns by 1px */
/* second column */
.keypad button:nth-child(3n-1) {
.keypad .btn:nth-child(3n-1) {
margin-left: -1px;
}
/* third column */
.keypad button:nth-child(3n) {
.keypad .btn:nth-child(3n) {
margin-left: -1px;
}
/* from second row downwards */
.keypad button:nth-child(n+4) {
.keypad .btn:nth-child(n+4) {
margin-top: -1px;
}
/* ensure highlighted button is topmost */
.keypad button:hover,
.keypad button:focus,
.keypad button:active {
.keypad .btn:hover,
.keypad .btn:focus,
.keypad .btn:active {
z-index: 1;
}
.actions {
display: flex;
align-items: center;
justify-content: center;
}
.actions button {
flex: 1 1 50%;
}
#Calculation {
min-height: 1.5rem;
}
@media (max-height: 700px) {
.store-header {
display: none !important;
}
}
@media (max-width: 575px) {
.public-page-wrap {
padding-right: 0;
padding-left: 0;
}
.keypad {
margin-left: -1px;
margin-right: -1px;
}
.store-footer {
display: none !important;
}
}
/* fix sticky hover effect on mobile browsers */
@media (hover: none) {
.keypad .btn-secondary:hover,

View File

@ -1,64 +0,0 @@
@using BTCPayApp.UI.Features
@using BTCPayApp.Core.Contracts
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IStateSelection<RootState, HashSet<RootState.LoadingHandles>> LoadingStateSelection
@inject IStateSelection<UIState, bool> DarkModeStateSelection
@inject IDispatcher Dispatcher
@inject IConfigProvider ConfigProvider
@inject ISystemThemeProvider SystemThemeProvider
@inject IStore Store
<PageTitle>BTCPay Server</PageTitle>
<MudThemeProvider @ref="@_mudThemeProvider" IsDarkMode="@(_darkMode ?? DarkModeStateSelection.Value)" Theme="Constants.Theme"/>
<MudDialogProvider/>
<MudSnackbarProvider/>
<MudLayout>
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Size="Size.Small" Class="@(LoadingStateSelection.Value.Any() ? null : "invisible")"/>
@ChildContent
</MudLayout>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
protected MudThemeProvider _mudThemeProvider;
private bool? _darkMode;
protected override async Task OnInitializedAsync()
{
LoadingStateSelection.Select(state => state.Loading);
DarkModeStateSelection.Select(state => state.IsDarkMode);
var state = await ConfigProvider.Get<UIState>(StateMiddleware.UiStateConfigKey);
if (state != null)
{
if (_darkMode is null && !Store.Initialized.IsCompleted)
{
_darkMode = state.IsDarkMode;
_ = Store.Initialized.ContinueWith(_ => _darkMode = null);
}
if (!string.IsNullOrEmpty(state.SelectedTheme))
{
Dispatcher.Dispatch(new UIState.ApplyUserTheme(state.SelectedTheme));
}
}
await base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (SystemThemeProvider is MudBlazorThemeProvider mudThemeProvider)
mudThemeProvider.MudThemeProvider = _mudThemeProvider;
ApplySystemPreference(this, await _mudThemeProvider.GetSystemPreference());
SystemThemeProvider.SystemThemeChanged += ApplySystemPreference;
}
}
private void ApplySystemPreference(object? sender, bool isDarkMode)
{
var systemTheme = isDarkMode ? Constants.DarkTheme : Constants.LightTheme;
Dispatcher.Dispatch(new UIState.SetSystemPreference(systemTheme));
}
}

View File

@ -1,34 +1,57 @@
@using BTCPayApp.UI.Pages
@inherits Fluxor.Blazor.Web.Components.FluxorLayout
<LayoutBase>
<MudAppBar Elevation="1">
<MudIconButton Icon="@BTCPayIcons.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@DrawerToggle"/>
<MudLink Href="@Routes.Home" Underline="Underline.None" Color="Color.Inherit" Class="d-flex align-center ml-2">
<MudImage Src="_content/BTCPayApp.UI/img/logo.svg" Height="24" Alt="BTCPay Server" />
<MudText Class="ml-3">BTCPay Server</MudText>
</MudLink>
</MudAppBar>
<MudDrawer @bind-Open="_isDrawerOpen" ClipMode="DrawerClipMode.Always" Class="py-5">
<MudNavMenu Color="Color.Success" Bordered="true">
<MudNavLink Href="@Routes.Home" Icon="@BTCPayIcons.Home" Match="NavLinkMatch.All">Dashboard</MudNavLink>
<MudNavLink Href="@Routes.WalletSetup" Icon="@Icons.Material.Filled.AccountBalanceWallet" Match="NavLinkMatch.All">Wallet</MudNavLink>
<MudNavLink Href="@Routes.PointOfSale" Icon="@BTCPayIcons.Pointofsale" Match="NavLinkMatch.All">Point Of Sale</MudNavLink>
<MudNavLink Href="@Routes.Settings" Icon="@BTCPayIcons.Settings" Match="NavLinkMatch.All">Settings</MudNavLink>
</MudNavMenu>
</MudDrawer>
<MudMainContent>
<MudContainer Fixed="true" Class="py-5">
<div id="MainLayout">
<header id="MainMenu" class="btcpay-header d-flex flex-column d-print-none">
<div id="MainMenuHead">
<button id="MainMenuToggle" class="mainMenuButton" type="button" data-bs-toggle="offcanvas" data-bs-target="#MainNav" aria-controls="MainNav" aria-expanded="false" aria-label="Toggle navigation">
<span>Menu</span>
</button>
<NavLink class="navbar-brand py-2" href="@Routes.Home" Match="NavLinkMatch.All">
<svg xmlns="http://www.w3.org/2000/svg" role="img" alt="BTCPay Server" class="main-logo main-logo-btcpay">
<use href="_content/BTCPayApp.UI/img/logo.svg#small" class="main-logo-btcpay--small"/>
<use href="_content/BTCPayApp.UI/img/logo.svg#large" class="main-logo-btcpay--large"/>
</svg>
</NavLink>
</div>
<nav id="MainNav" class="d-flex flex-column justify-content-between">
<div class="accordion px-3 px-lg-4">
<div class="accordion-item">
<div class="accordion-body">
<ul class="navbar-nav">
<li class="nav-item">
<NavLink class="nav-link" href="@Routes.Home" Match="NavLinkMatch.All">
<Icon symbol="home"/>
<span>Dashboard</span>
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link" href="@Routes.WalletSetup" Match="NavLinkMatch.All">
<Icon symbol="existing-wallet"/>
<span>Wallet</span>
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link" href="@Routes.PointOfSale" Match="NavLinkMatch.All">
<Icon symbol="pointofsale"/>
<span>Point Of Sale</span>
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link" href="@Routes.Settings" Match="NavLinkMatch.All">
<Icon symbol="settings"/>
<span>Settings</span>
</NavLink>
</li>
</ul>
</div>
</div>
</div>
</nav>
</header>
<main id="MainContent">
<section>
@Body
</MudContainer>
</MudMainContent>
</LayoutBase>
@code {
private bool _isDrawerOpen;
void DrawerToggle()
{
_isDrawerOpen = !_isDrawerOpen;
}
}
</section>
</main>
</div>

View File

@ -0,0 +1,328 @@
/* Breakpoints:
XS <576px
SM 576px
MD 768px
LG 992px
XL 1200px */
#MainLayout {
--mobile-header-height: 4rem;
--desktop-header-height: 8rem;
--sidebar-width: 280px;
--sticky-header-height: 0; /* gets dynamically set via JavaScript */
min-height: 100vh;
scroll-padding-top: calc(var(--sticky-header-height) + var(--btcpay-space-m));
}
/* Main Menu */
#MainMenu {
--button-width: 40px;
--button-height: 40px;
--button-padding: 7px;
--icon-size: 1.5rem;
--menu-border: 1px solid var(--btcpay-body-border-light);
height: var(--header-height);
z-index: 1031; /* needs a value between fixed and the offcanvas backdrop, see https://getbootstrap.com/docs/5.1/layout/z-index/ */
}
#MainMenuHead {
display: flex;
align-items: center;
}
#MainMenuHead .mainMenuButton {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--button-width);
height: var(--button-height);
padding: var(--button-padding);
background: transparent;
border: none;
cursor: pointer;
outline: none;
color: var(--btcpay-body-text-muted);
}
#MainNav {
--btcpay-scrollbar-color: var(--btcpay-neutral-400);
height: calc(100vh - var(--mobile-header-height));
overflow-y: auto;
padding-top: var(--btcpay-space-m);
}
::deep #MainNav .nav-item svg.icon {
width: var(--icon-size);
height: var(--icon-size);
margin-right: var(--btcpay-space-xs);
}
#MainNav .accordion-button {
padding: var(--btcpay-space-s) 0;
text-transform: uppercase;
color: var(--btcpay-body-text-muted);
font-weight: var(--btcpay-font-weight-semibold);
}
#MainNav .accordion-item {
border: none !important;
}
#MainNav .navbar-nav > li.nav-item .nav-link {
display: inline-flex;
align-items: center;
font-weight: var(--btcpay-font-weight-semibold);
color: var(--btcpay-header-link);
transition-property: color;
transition-duration: var(--btcpay-transition-duration-fast);
}
#MainNav .navbar-nav > li.nav-item .nav-link .icon {
flex-shrink: 0;
}
#MainNav .navbar-nav > li.nav-item-sub {
padding-left:calc(1.5rem + var(--btcpay-space-xs))
}
#MainNav .navbar-nav > li.nav-item .nav-link span {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#MainNav .navbar-nav > li.nav-item .nav-link:focus,
#MainNav .navbar-nav > li.nav-item .nav-link:hover {
color: var(--btcpay-header-link-accent);
}
#MainNav .navbar-nav > li.nav-item .nav-link.active,
#MainNav .navbar-nav > li.nav-item .nav-link.active:focus,
#MainNav .navbar-nav > li.nav-item .nav-link.active:hover {
color: var(--btcpay-header-link-active);
}
#MainNavSettings {
margin-top: auto;
}
.navbar-brand,
.navbar-brand:hover,
.navbar-brand:focus {
color: inherit;
}
.btcpay-header {
color: var(--btcpay-header-text);
background: var(--btcpay-header-bg);
}
#MainContent {
flex: 1;
display: flex;
flex-direction: column;
}
#MainContent > section {
flex: 1;
padding: var(--content-padding-top) var(--content-padding-horizontal) var(--content-padding-bottom);
}
/* Logo */
#MainMenuHead .main-logo {
display: inline-block;
height: 2rem;
}
@media (max-width: 575px) {
#MainMenuHead .main-logo-custom {
max-width: 25vw;
}
#MainMenuHead .main-logo-btcpay {
width: 1.125rem;
}
#MainMenuHead .main-logo-btcpay .main-logo-btcpay--large {
display: none;
}
}
@media (min-width: 576px) {
#MainMenuHead .main-logo-custom {
max-width: 10.5rem;
}
#MainMenuHead .main-logo-btcpay {
width: 4.625rem;
}
#MainMenuHead .main-logo-btcpay .main-logo-btcpay--small {
display: none;
}
}
@media (max-width: 991px) {
#MainLayout {
--header-height: var(--mobile-header-height);
--content-padding-top: calc(var(--header-height) + var(--btcpay-space-m));
--content-padding-bottom: var(--btcpay-space-xl);
--content-padding-horizontal: var(--btcpay-space-m);
/* Prevent anchors from disappearing underneath the fixed header */
scroll-padding: var(--content-padding-top);
}
#MainMenu {
position: fixed;
top: 0;
left: 0;
right: 0;
border-bottom: var(--menu-border);
}
#MainMenuHead {
padding: var(--btcpay-space-s) var(--btcpay-space-m);
}
#MainNav {
position: fixed;
top: var(--mobile-header-height);
bottom: 0;
left: 0;
width: var(--sidebar-width);
z-index: 1045;
border-right: var(--menu-border);
color: var(--btcpay-body-text);
background-color: inherit;
background-clip: padding-box;
outline: 0;
transform: translateX(-100%);
transition: transform var(--btcpay-transition-duration-fast) ease-in-out;
/* Fixes https://github.com/btcpayserver/btcpayserver/issues/3807 */
height: calc(100vh - var(--mobile-header-height)); /* This line is a fallback for browsers which don't support "fill-available" */
height: -moz-available;
height: -webkit-fill-available;
height: fill-available;
/* Since we can't do "calc(fill-available - var(--mobile-header-height));" I'm using "padding-bottom" instead */
padding-bottom: var(--mobile-header-height);
/* END FIX */
}
#MainNav.show {
transform: none;
}
::deep #MainMenu .offcanvas-backdrop {
top: var(--mobile-header-height);
transition-duration: var(--btcpay-transition-duration-fast);
}
#MainMenuToggle {
--line-thickness: 2px;
--transition-easing: ease-in-out;
--transition-duration: var(--btcpay-transition-duration-fast);
flex: 0 0 var(--button-width);
margin-right: var(--btcpay-space-s);
margin-left: calc(var(--button-padding) * -1);
}
#MainMenuToggle span {
position: relative;
display: inline-block;
width: calc(var(--button-width) - var(--button-padding) * 2);
height: calc(var(--button-height) - (var(--button-padding) * 2) - (var(--line-thickness) * 4));
border-top: var(--line-thickness) solid;
border-bottom: var(--line-thickness) solid;
color: var(--btcpay-body-text-muted);
font-size: 0;
transition: all var(--transition-duration) var(--transition-easing);
}
#MainMenuToggle span:before,
#MainMenuToggle span:after {
position: absolute;
display: block;
content: '';
width: 100%;
height: var(--line-thickness);
top: 50%;
left: 50%;
background: currentColor;
transform: translate(-50%, -50%);
transition: transform var(--transition-duration) var(--transition-easing);
}
#MainMenuToggle:hover span {
color: var(--btcpay-header-text);
}
#MainMenuToggle[aria-expanded="true"] span {
border-color: transparent;
}
#MainMenuToggle[aria-expanded="true"] span:before {
transform: translate(-50%, -50%) rotate(45deg);
}
#MainMenuToggle[aria-expanded="true"] span:after {
transform: translate(-50%, -50%) rotate(-45deg);
}
}
@media (min-width: 992px) {
#MainLayout {
--header-height: var(--desktop-header-height);
--content-padding-top: 5rem;
--content-padding-bottom: 5rem;
--content-padding-horizontal: 5rem;
}
#MainMenu {
position: fixed;
top: 0;
bottom: 0;
left: 0;
width: var(--sidebar-width);
height: 100vh;
border-right: var(--menu-border);
}
#MainMenuHead {
flex-wrap: wrap;
padding: var(--btcpay-space-m) 1.5rem;
}
#MainMenuToggle,
::deep #MainMenu .offcanvas-backdrop {
display: none !important;
}
#MainContent {
margin-left: var(--sidebar-width);
}
::deep #MainContent pre {
max-width: calc(100vw - var(--sidebar-width) - (2 * var(--btcpay-space-xl)) - 1rem); /* 1rem for scrollbar */
}
#MainContent > section {
margin: 0;
max-width: none;
}
::deep #MainContent > section .w-100-fixed {
/* constrains the content to respect the maximum width and enable responsive tables */
width: calc(100vw - var(--sidebar-width) - var(--content-padding-horizontal) * 2);
}
}
@media (min-width: 992px) and (max-height: 799px) {
#MainLayout {
--content-padding-top: 1.25rem;
}
}

View File

@ -1,40 +1,27 @@
@using BTCPayApp.UI.Pages
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
<MudCarousel Class="mx-auto" ShowArrows="@false" AutoCycle="@false" @bind-SelectedIndex="@Step" TData="object" id="OnboardingCarousel">
<MudCarouselItem Transition="@Transition.Slide">
<MudContainer MaxWidth="MaxWidth.ExtraSmall" Class="d-flex flex-column gap-4" Style="height:calc(100% - 4rem)">
<MudIcon Class="mx-auto my-auto" Icon="@BTCPayIcons.LogoBtcpay" Color="@Color.Primary" Size="@Size.Large"/>
<MudText Typo="Typo.h4" Align="Align.Center">Welcome To BTCPay Server</MudText>
<MudText Typo="Typo.subtitle1" Align="Align.Center" Class="flex-grow-1">
No fees, no third-party.
All payments go through your node, running on your phone!
</MudText>
<MudButton Size="Size.Large" DisableElevation="true" Color="Color.Primary" OnClick="@(() => GoToStep(1))">Next</MudButton>
</MudContainer>
</MudCarouselItem>
<MudCarouselItem Transition="@Transition.Slide">
<MudContainer MaxWidth="MaxWidth.ExtraSmall" Class="d-flex flex-column gap-4" Style="height:calc(100% - 4rem)">
<MudIcon Class="mx-auto my-auto" Icon="@BTCPayIcons.Key" Color="@Color.Primary" Size="@Size.Large" />
<MudText Typo="Typo.h4" Align="Align.Center">Keep your wallet and bitcoin secure</MudText>
<MudText Typo="Typo.subtitle1" Align="Align.Center" Class="flex-grow-1">
Cloud backups, enhanced accounting functionality and ability to easily refund your customers
</MudText>
<MudButton Size="Size.Large" DisableElevation="true" Color="Color.Primary" OnClick="@(() => GoToStep(2))">Next</MudButton>
</MudContainer>
</MudCarouselItem>
<MudCarouselItem Transition="@Transition.Slide">
<MudContainer MaxWidth="MaxWidth.ExtraSmall" Class="d-flex flex-column gap-4" Style="height:calc(100% - 4rem)">
<MudIcon Class="mx-auto my-auto" Icon="@BTCPayIcons.Pointofsale" Color="@Color.Primary" Size="@Size.Large" />
<MudText Typo="Typo.h4" Align="Align.Center">Start Accepting Bitcoin Payments</MudText>
<MudText Typo="Typo.subtitle1" Align="Align.Center" Class="flex-grow-1">
Free and open-source bitcoin point of sale. With 0% Fees & No Third-party.
</MudText>
<MudButton Size="Size.Large" DisableElevation="true" Color="Color.Primary" Href="@Routes.WalletSetup" data-testid="StandaloneButton">Get Started</MudButton>
<MudButton Size="Size.Large" DisableElevation="true" Color="Color.Secondary" Href="@Routes.Pair" data-testid="PairButton">Pair with BTCPay Server</MudButton>
</MudContainer>
</MudCarouselItem>
</MudCarousel>
<div id="OnboardingCarousel" class="carousel slide">
<div class="carousel-inner">
<div class="carousel-item active">
<Icon Symbol="logo-btcpay" />
<h4>Welcome To BTCPay Server</h4>
<p>No fees, no third-party. All payments go through your node, running on your phone!</p>
</div>
<div class="carousel-item">
<Icon Symbol="key" />
<h4>Keep your wallet and bitcoin secure</h4>
<p>Cloud backups, enhanced accounting functionality and ability to easily refund your customers</p>
</div>
<div class="carousel-item">
<Icon Symbol="pointofsale" />
<h4>Start Accepting Bitcoin Payments</h4>
<p>Free and open-source bitcoin point of sale. With 0% Fees & No Third-party.</p>
<NavLink class="btn btn-primary" href="@Routes.WalletSetup" data-testid="StandaloneButton">Get Started</NavLink>
<NavLink class="btn btn-secondary" href="@Routes.Pair" data-testid="PairButton">Pair with BTCPay Server</NavLink>
</div>
</div>
</div>
@code {
private int Step { get; set; } = 0;

View File

@ -0,0 +1,15 @@
@media (min-width: 0px) {
#OnboardingCarousel {
min-height: 430px;
}
}
@media (min-width: 320px) {
#OnboardingCarousel {
min-height: 410px;
}
}
@media (min-width: 480px) {
#OnboardingCarousel {
min-height: 380px;
}
}

View File

@ -1,7 +1,7 @@
@inherits Fluxor.Blazor.Web.Components.FluxorLayout
<LayoutBase>
<MudMainContent>
<div class="min-vh-100">
<main>
@Body
</MudMainContent>
</LayoutBase>
</main>
</div>

View File

@ -1,16 +1,11 @@
using BTCPayApp.Core.Contracts;
using Fluxor;
using Fluxor;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using MudBlazor.Services;
namespace BTCPayApp.UI;
public static class StartupExtensions
{
public static IServiceCollection AddBTCPayAppUIServices(this IServiceCollection serviceCollection)
{
serviceCollection.AddMudServices();
serviceCollection.AddFluxor(options =>
{
options.UseRouting();
@ -20,7 +15,6 @@ public static class StartupExtensions
#endif
options.AddMiddleware<StateMiddleware>();
});
serviceCollection.TryAddScoped<ISystemThemeProvider, MudBlazorThemeProvider>();
return serviceCollection;
}
}
}

View File

@ -5,7 +5,6 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using MudBlazor
@using Fluxor
@using BTCPayApp.UI
@using BTCPayApp.UI.Shared

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,82 +0,0 @@
:root {
}
.mud-button-text {
--btcpay-btn-disabled-opacity: 0.65;
color: var(--btcpay-btn-color) !important;
border: 1px solid var(--btcpay-btn-border-color) !important;
border-radius: var(--btcpay-btn-border-radius) !important;
background-color: var(--btcpay-btn-bg) !important;
padding: var(--btcpay-btn-padding-y) var(--btcpay-btn-padding-x) !important;
}
.mud-button-text:hover,
.mud-button-text:focus-visible {
color: var(--btcpay-btn-hover-color) !important;
background-color: var(--btcpay-btn-hover-bg) !important;
border-color: var(--btcpay-btn-hover-border-color) !important;
}
.mud-button-text:disabled {
color: var(--btcpay-btn-disabled-color) !important;
background-color: var(--btcpay-btn-disabled-bg) !important;
border-color: var(--btcpay-btn-disabled-border-color) !important;
opacity: var(--btcpay-btn-disabled-opacity) !important;
}
.mud-button-text-primary,
.mud-button-filled-primary {
--btcpay-btn-color: var(--btcpay-primary-text);
--btcpay-btn-bg: var(--btcpay-primary);
--btcpay-btn-border-color: var(--btcpay-primary-border);
--btcpay-btn-hover-color: var(--btcpay-primary-text-hover);
--btcpay-btn-hover-bg: var(--btcpay-primary-bg-hover);
--btcpay-btn-hover-border-color: var(--btcpay-primary-border-hover);
--btcpay-btn-focus-shadow-rgb: var(--btcpay-primary-shadow);
--btcpay-btn-active-color: var(--btcpay-primary-text-active);
--btcpay-btn-active-bg: var(--btcpay-primary-bg-active);
--btcpay-btn-active-border-color: var(--btcpay-primary-border-active);
--btcpay-btn-disabled-color: var(--btcpay-primary-text);
--btcpay-btn-disabled-bg: var(--btcpay-primary);
--btcpay-btn-disabled-border-color: var(--btcpay-primary-border);
}
.mud-button-text-default,
.mud-button-text-secondary,
.mud-button-filled-secondary {
--btcpay-btn-color: var(--btcpay-secondary-text);
--btcpay-btn-bg: var(--btcpay-secondary);
--btcpay-btn-border-color: var(--btcpay-secondary-border);
--btcpay-btn-hover-color: var(--btcpay-secondary-text-hover);
--btcpay-btn-hover-bg: var(--btcpay-secondary-bg-hover);
--btcpay-btn-hover-border-color: var(--btcpay-secondary-border-hover);
--btcpay-btn-focus-shadow-rgb: var(--btcpay-secondary-shadow);
--btcpay-btn-active-color: var(--btcpay-secondary-text-active);
--btcpay-btn-active-bg: var(--btcpay-secondary-bg-active);
--btcpay-btn-active-border-color: var(--btcpay-secondary-border-active);
--btcpay-btn-disabled-color: var(--btcpay-secondary-text);
--btcpay-btn-disabled-bg: var(--btcpay-secondary);
--btcpay-btn-disabled-border-color: var(--btcpay-secondary-border);
}
.mud-button-text-size-large {
--btcpay-btn-padding-y: 1rem;
--btcpay-btn-padding-x: 2rem;
--btcpay-btn-font-size: 1rem;
--btcpay-btn-border-radius: var(--btcpay-border-radius-l);
}
.mud-button-text-size-medium {
--btcpay-btn-padding-x: 1.5rem;
--btcpay-btn-padding-y: 0.5rem;
--btcpay-btn-font-size: 0.875rem;
--btcpay-btn-border-radius: var(--btcpay-border-radius);
}
.mud-button-text-size-small {
--btcpay-btn-padding-y: 0.5rem;
--btcpay-btn-padding-x: 1.5rem;
--btcpay-btn-font-size: 0.75rem;
--btcpay-btn-border-radius: var(--btcpay-border-radius);
}

View File

@ -1,24 +1,66 @@
@media (min-width: 0px) {
#OnboardingCarousel {
min-height: 430px;
}
}
@media (min-width: 320px) {
#OnboardingCarousel {
min-height: 410px;
}
}
@media (min-width: 480px) {
#OnboardingCarousel {
min-height: 380px;
}
}

::-moz-focus-inner {
padding: 0;
border-style: none;
}
.fw-semibold {
font-weight: var(--btcpay-font-weight-semibold);
[aria-expanded] > svg.icon-caret-down {
flex-shrink: 0;
width: 24px;
height: 24px;
margin-left: auto;
transition: transform 0.2s ease-in-out;
}
[aria-expanded="true"] > svg.icon-caret-down {
transform: rotate(-180deg);
}
/* Badges */
.badge-new,
.badge-pending {
background: #d4edda;
color: #000;
}
.badge-expired {
background: #eee;
color: #000;
}
.badge-invalid {
background: var(--btcpay-danger);
color: var(--btcpay-danger-text);
}
.badge-unusual,
.badge-processing {
background: var(--btcpay-warning);
color: var(--btcpay-warning-text);
}
.badge-settled {
background: var(--btcpay-success);
color: var(--btcpay-success-text);
}
/* Blazor error */
#blazor-error-ui {
display: none;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
background-color: var(--btcpay-danger);
color: var(--btcpay-danger-text);
padding: .6rem 1.75rem .7rem 1.25rem;
}
#blazor-error-ui .reload {
color: inherit;
text-decoration: underline;
}
#blazor-error-ui .dismiss{
position: absolute;
right: .75rem;
top: .5rem;
color: inherit;
cursor: pointer;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -0,0 +1 @@
<svg height="600" width="335.746" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="a" clipPathUnits="userSpaceOnUse"><path d="M0 800h1280V0H0Z"/></clipPath></defs><g transform="matrix(.89032 0 0 -.89032 -658.541 610.276)" clip-path="url(#a)"><path style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0a8.28 8.28 0 0 0-8.281 8.28v124.622a8.282 8.282 0 0 0 16.562 0V8.28A8.28 8.28 0 0 0 0 0" transform="translate(779.205 11.551) scale(4.7732)"/><path style="fill:#51b13e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0a8.282 8.282 0 0 0-3.556 15.764l49.934 23.698-51.295 37.79a8.281 8.281 0 0 0-1.754 11.58 8.28 8.28 0 0 0 11.577 1.754l62.441-46.003a8.278 8.278 0 0 0-1.362-14.147L3.545.802A8.25 8.25 0 0 0 0 0" transform="translate(779.23 11.542) scale(4.7732)"/><path style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0a8.28 8.28 0 0 0-4.918 14.948l51.294 37.791-49.933 23.699a8.28 8.28 0 0 0-3.931 11.031c1.962 4.132 6.902 5.893 11.031 3.93l62.441-29.633a8.281 8.281 0 0 0 1.361-14.148L4.905 1.614A8.248 8.248 0 0 0 0 0" transform="translate(779.237 245.36) scale(4.7732)"/><path style="fill:#1e7a44;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0v-35.022l23.757 17.503z" transform="translate(818.73 432.08) scale(4.7732)"/><path style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.77321" d="M818.732 309.48h-79.054v192.69h79.054z"/><path style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0a8.282 8.282 0 0 1-8.281-8.281v-104.215H8.281V-8.281A8.282 8.282 0 0 1 0 0" transform="translate(779.205 685.445) scale(4.7732)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1 +1,17 @@
<svg height="600" width="335.746" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="a" clipPathUnits="userSpaceOnUse"><path d="M0 800h1280V0H0Z"/></clipPath></defs><g transform="matrix(.89032 0 0 -.89032 -658.541 610.276)" clip-path="url(#a)"><path style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0a8.28 8.28 0 0 0-8.281 8.28v124.622a8.282 8.282 0 0 0 16.562 0V8.28A8.28 8.28 0 0 0 0 0" transform="translate(779.205 11.551) scale(4.7732)"/><path style="fill:#51b13e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0a8.282 8.282 0 0 0-3.556 15.764l49.934 23.698-51.295 37.79a8.281 8.281 0 0 0-1.754 11.58 8.28 8.28 0 0 0 11.577 1.754l62.441-46.003a8.278 8.278 0 0 0-1.362-14.147L3.545.802A8.25 8.25 0 0 0 0 0" transform="translate(779.23 11.542) scale(4.7732)"/><path style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0a8.28 8.28 0 0 0-4.918 14.948l51.294 37.791-49.933 23.699a8.28 8.28 0 0 0-3.931 11.031c1.962 4.132 6.902 5.893 11.031 3.93l62.441-29.633a8.281 8.281 0 0 0 1.361-14.148L4.905 1.614A8.248 8.248 0 0 0 0 0" transform="translate(779.237 245.36) scale(4.7732)"/><path style="fill:#1e7a44;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0v-35.022l23.757 17.503z" transform="translate(818.73 432.08) scale(4.7732)"/><path style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.77321" d="M818.732 309.48h-79.054v192.69h79.054z"/><path style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M0 0a8.282 8.282 0 0 1-8.281-8.281v-104.215H8.281V-8.281A8.282 8.282 0 0 1 0 0" transform="translate(779.205 685.445) scale(4.7732)"/></g></svg>
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
<symbol id="small" viewBox="0 0 46 84">
<path d="M5.206 83.433a4.86 4.86 0 01-4.859-4.861V5.431a4.86 4.86 0 119.719 0v73.141a4.861 4.861 0 01-4.86 4.861" fill="#CEDC21" class="logo-brand-light"/>
<path d="M5.209 83.433a4.862 4.862 0 01-2.086-9.253L32.43 60.274 2.323 38.093a4.861 4.861 0 015.766-7.826l36.647 26.999a4.864 4.864 0 01-.799 8.306L7.289 82.964a4.866 4.866 0 01-2.08.469" fill="#51B13E" class="logo-brand-medium"/>
<path d="M5.211 54.684a4.86 4.86 0 01-2.887-8.774L32.43 23.73 3.123 9.821a4.861 4.861 0 014.166-8.784l36.648 17.394a4.86 4.86 0 01.799 8.305l-36.647 27a4.844 4.844 0 01-2.878.948" fill="#CEDC21" class="logo-brand-light"/>
<path d="M10.066 31.725v20.553L24.01 42.006z" fill="#1E7A44" class="logo-brand-dark"/>
<path d="M10.066 5.431A4.861 4.861 0 005.206.57 4.86 4.86 0 00.347 5.431v61.165h9.72V5.431h-.001z" fill="#CEDC21" class="logo-brand-light"/>
</symbol>
<symbol id="large" viewBox="0 0 192 84">
<path d="M5.206 83.433a4.86 4.86 0 01-4.859-4.861V5.431a4.86 4.86 0 119.719 0v73.141a4.861 4.861 0 01-4.86 4.861" fill="#CEDC21" class="logo-brand-light"/>
<path d="M5.209 83.433a4.862 4.862 0 01-2.086-9.253L32.43 60.274 2.323 38.093a4.861 4.861 0 015.766-7.826l36.647 26.999a4.864 4.864 0 01-.799 8.306L7.289 82.964a4.866 4.866 0 01-2.08.469" fill="#51B13E" class="logo-brand-medium"/>
<path d="M5.211 54.684a4.86 4.86 0 01-2.887-8.774L32.43 23.73 3.123 9.821a4.861 4.861 0 014.166-8.784l36.648 17.394a4.86 4.86 0 01.799 8.305l-36.647 27a4.844 4.844 0 01-2.878.948" fill="#CEDC21" class="logo-brand-light"/>
<path d="M10.066 31.725v20.553L24.01 42.006z" fill="#1E7A44" class="logo-brand-dark"/>
<path d="M10.066 5.431A4.861 4.861 0 005.206.57 4.86 4.86 0 00.347 5.431v61.165h9.72V5.431h-.001z" fill="#CEDC21" class="logo-brand-light"/>
<path d="M74.355 41.412c3.114.884 4.84 3.704 4.84 7.238 0 5.513-3.368 8.082-7.955 8.082H60.761V27.271h9.259c4.504 0 7.997 2.146 7.997 7.743 0 2.821-1.179 5.43-3.662 6.398m-4.293-.716c3.324 0 6.018-1.179 6.018-5.724 0-4.586-2.776-5.808-6.145-5.808h-7.197v11.531h7.324v.001zm1.052 14.099c3.366 0 6.06-1.768 6.06-6.145 0-4.713-3.072-6.144-6.901-6.144h-7.534v12.288h8.375v.001zM98.893 27.271v1.81h-8.122v27.651h-1.979V29.081h-8.123v-1.81zM112.738 26.85c5.01 0 9.554 2.524 10.987 8.543h-1.895c-1.348-4.923-5.303-6.732-9.134-6.732-6.944 0-10.605 5.681-10.605 13.341 0 8.08 3.661 13.256 10.646 13.256 4.125 0 7.828-1.85 9.26-7.279h1.895c-1.264 6.271-6.229 9.174-11.154 9.174-7.87 0-12.583-5.808-12.583-15.15 0-8.966 4.969-15.153 12.583-15.153M138.709 27.271c5.091 0 8.795 3.326 8.795 9.764 0 6.06-3.704 9.722-8.795 9.722h-7.746v9.976h-1.935V27.271h9.681zm0 17.549c3.745 0 6.816-2.397 6.816-7.827 0-5.429-2.947-7.869-6.816-7.869h-7.746V44.82h7.746zM147.841 56.732v-.255l11.741-29.29h.885l11.615 29.29v.255h-2.062l-3.322-8.501H153.27l-3.324 8.501h-2.105zm12.164-26.052l-6.059 15.697h12.078l-6.019-15.697zM189.551 27.271h2.104v.293l-9.176 16.92v12.248h-2.02V44.484l-9.216-16.961v-.252h2.147l3.997 7.492 4.043 7.786h.04l4.081-7.786z" fill="currentColor" class="logo-brand-text"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,27 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/"/>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover" />
<title>BTCPay Server</title>
<link href="./_content/MudBlazor/MudBlazor.min.css" rel="stylesheet"/>
<link href="./_content/BTCPayApp.UI/css/mudblazor-overrides.css" rel="stylesheet"/>
<link href="./_content/BTCPayApp.UI/css/theme.css" rel="stylesheet"/>
<link href="./_content/BTCPayApp.UI/css/fonts.css" rel="stylesheet"/>
<link href="./_content/BTCPayApp.UI/css/site.css" rel="stylesheet"/>
<link href="./_content/BTCPayApp.UI/favicon.png" rel="icon" type="image/png"/>
</head>
<body>
<div class="status-bar-safe-area"></div>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a class="reload" href="">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="./_content/BTCPayApp.UI/js/site.js"></script>
<script src="./_content/MudBlazor/MudBlazor.min.js"></script>
<script src="./_framework/blazor.webview.js"></script>
</body>
</html>