Replace MudBlazor with Bootstrap
This commit is contained in:
parent
0816b9d464
commit
ca48d28aef
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -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};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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))" />
|
||||
|
||||
|
||||
@ -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" />
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
*@
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
@using BTCPayServer.Client
|
||||
@*
|
||||
@using BTCPayServer.Client
|
||||
@using BTCPayServer.Client.Models
|
||||
@implements IDisposable
|
||||
|
||||
@ -116,3 +117,4 @@
|
||||
}
|
||||
|
||||
}
|
||||
*@
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
8
BTCPayApp.UI/Shared/Icon.razor
Normal file
8
BTCPayApp.UI/Shared/Icon.razor
Normal 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 |
10
BTCPayApp.UI/Shared/Icon.razor.css
Normal file
10
BTCPayApp.UI/Shared/Icon.razor.css
Normal 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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
328
BTCPayApp.UI/Shared/MainLayout.razor.css
Normal file
328
BTCPayApp.UI/Shared/MainLayout.razor.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
15
BTCPayApp.UI/Shared/Onboarding.razor.css
Normal file
15
BTCPayApp.UI/Shared/Onboarding.razor.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
@inherits Fluxor.Blazor.Web.Components.FluxorLayout
|
||||
|
||||
<LayoutBase>
|
||||
<MudMainContent>
|
||||
<div class="min-vh-100">
|
||||
<main>
|
||||
@Body
|
||||
</MudMainContent>
|
||||
</LayoutBase>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
7
BTCPayApp.UI/wwwroot/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
7
BTCPayApp.UI/wwwroot/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
12886
BTCPayApp.UI/wwwroot/bootstrap/bootstrap.css
vendored
Normal file
12886
BTCPayApp.UI/wwwroot/bootstrap/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
89
BTCPayApp.UI/wwwroot/img/icon-sprite.svg
Normal file
89
BTCPayApp.UI/wwwroot/img/icon-sprite.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 76 KiB |
1
BTCPayApp.UI/wwwroot/img/logo-symbol.svg
Normal file
1
BTCPayApp.UI/wwwroot/img/logo-symbol.svg
Normal 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 |
@ -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 |
@ -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>
|
||||
Loading…
Reference in New Issue
Block a user