Initial code commit.

This commit is contained in:
2026-02-03 10:44:31 +08:00
parent 8927c5ae0e
commit d69fe2cc1f
99 changed files with 10839 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

View File

@@ -0,0 +1,210 @@
@page "/contacts"
@inject HttpClient Http
@inject IDialogService DialogService
@inject ISnackbar Snackbar
@inject BoomerangService Boomerang
<PageTitle>Contact List</PageTitle>
<MudGrid Class="py-4">
<MudItem xs="12">
<MudText Typo="Typo.h3">Contact List</MudText>
</MudItem>
<MudItem xs="12">
<MudText Typo="Typo.body1">Behold! Below is the list of all registered users in this application.</MudText>
<MudText Typo="Typo.body1" Color="Color.Error">(All data will be deleted for every 10 minutes)</MudText>
</MudItem>
<MudItem xs="12" Class="d-flex gap-4">
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" OnClick="OnButtonCreateClicked">Create</MudButton>
<MudSpacer />
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Refresh" OnClick="OnButtonRefreshClicked">Refresh</MudButton>
</MudItem>
<MudItem xs="12">
@if (contacts != null && contacts.Length > 0)
{
<MudTable Items="@contacts" Hover Breakpoint="Breakpoint.Sm" Loading="@loading" LoadingProgressColor="Color.Info" Class="py-3">
<HeaderContent>
<MudTh>Id</MudTh>
<MudTh>Username</MudTh>
<MudTh>Phone</MudTh>
<MudTh>Email</MudTh>
<MudTh>Skill Sets</MudTh>
<MudTh>Hobby</MudTh>
<MudTh>Action</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Id">@context.Id</MudTd>
<MudTd DataLabel="Username">@context.UserName</MudTd>
<MudTd DataLabel="Phone">@context.Phone</MudTd>
<MudTd DataLabel="Email">@context.Email</MudTd>
<MudTd DataLabel="Skill Sets">@context.SkillSets</MudTd>
<MudTd DataLabel="Hobby">@context.Hobby</MudTd>
<MudTd>
<MudTooltip Text="Edit" Placement="Placement.Top" Arrow>
<MudIconButton Icon="@Icons.Material.Filled.Edit" OnClick="() => OnEditContactClicked(context.Id)" Color="Color.Warning" Size="Size.Small" />
</MudTooltip>
<MudTooltip Text="Delete" Placement="Placement.Top" Arrow>
<MudIconButton Icon="@Icons.Material.Filled.Delete" OnClick="() => OnDeleteContactClicked(context.Id)" Color="Color.Error" Size="Size.Small" />
</MudTooltip>
</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
}
else
{
<MudText Typo="Typo.h5">No item to display. Click on the 'Create' button to add.</MudText>
}
</MudItem>
</MudGrid>
<MudOverlay Visible="loading" DarkBackground ZIndex="9999">
<MudProgressCircular Color="Color.Primary" Indeterminate />
</MudOverlay>
@code {
private GetAllContactsResponse[]? contacts;
private bool loading;
protected override async Task OnInitializedAsync()
{
await LoadTableAsync();
await Boomerang.AddVariableAsync("PageName", "Contacts");
await Boomerang.SendBeaconAsync();
}
private async Task LoadTableAsync()
{
loading = true;
var response = await Http.GetAsync("api/v1/contacts");
var result = await response.ToResult<List<GetAllContactsResponse>>();
if (result.Succeeded)
{
contacts = result.Data.ToArray();
}
else
{
contacts = null;
foreach (var message in result.Messages)
{
Snackbar.Add(message, Severity.Error);
}
}
loading = false;
}
private async Task OnButtonRefreshClicked()
{
await LoadTableAsync();
}
private async Task OnButtonCreateClicked()
{
var dialogOptions = new DialogOptions { BackdropClick = false, CloseButton = true };
var dialog = await DialogService.ShowAsync<CreateContactDialog>("Create New Contact", dialogOptions);
var dialogResult = await dialog.Result;
if (dialogResult!.Canceled)
{
return;
}
loading = true;
StateHasChanged();
var contactData = (AddEditContactCommand)dialogResult.Data!;
var response = await Http.PostAsJsonAsync("api/v1/contacts", contactData);
var result = await response.ToResult<int>();
if (result.Succeeded)
{
Snackbar.Add(result.Messages[0], Severity.Success);
await LoadTableAsync();
}
else
{
foreach (var message in result.Messages)
{
Snackbar.Add(message, Severity.Error);
}
loading = false;
}
}
async Task OnEditContactClicked(int id)
{
var dialogParams = new DialogParameters { ["Id"] = id };
var dialogOptions = new DialogOptions { BackdropClick = false, CloseButton = true };
var dialog = await DialogService.ShowAsync<EditContactDialog>("Edit Contact", dialogParams, dialogOptions);
var dialogResult = await dialog.Result;
if (dialogResult!.Canceled)
{
return;
}
loading = true;
StateHasChanged();
var contactData = (AddEditContactCommand)dialogResult.Data!;
var response = await Http.PostAsJsonAsync("api/v1/contacts", contactData);
var result = await response.ToResult<int>();
if (result.Succeeded)
{
Snackbar.Add(result.Messages[0], Severity.Success);
await LoadTableAsync();
}
else
{
foreach (var message in result.Messages)
{
Snackbar.Add(message, Severity.Error);
}
loading = false;
}
}
async Task OnDeleteContactClicked(int id)
{
var dialogResult = await DialogService.ShowMessageBox("Delete Contact", "Confirm to delete the item? This action cannot be undone.", yesText: "Delete!", cancelText: "No");
if (dialogResult == null)
{
return;
}
loading = true;
StateHasChanged();
var deleteResponse = await Http.DeleteAsync($"api/v1/contacts/{id}");
if (deleteResponse.IsSuccessStatusCode)
{
var result = await deleteResponse.ToResult();
if (result!.Succeeded)
{
Snackbar.Add("Contact deleted successfully.", Severity.Success);
await LoadTableAsync();
}
else
{
Snackbar.Add(result.Messages[0], Severity.Error);
}
}
else
{
Snackbar.Add("An error has occurred.", Severity.Error);
loading = false;
}
}
}

View File

@@ -0,0 +1,60 @@
<MudDialog>
<DialogContent>
<MudForm @ref="form" Model="request" @bind-IsValid="success" @bind-Errors="errors">
<MudTextField Label="Username" Required @bind-Value="request.UserName" />
<MudTextField Label="Email" Required @bind-Value="request.Email" />
<MudTextField Label="Phone" Required @bind-Value="request.Phone" MaxLength="20" />
<MudTextField Label="Skill Sets" Required @bind-Value="request.SkillSets" />
<MudTextField Label="Hobby" Required @bind-Value="request.Hobby" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton OnClick="Submit">Ok</MudButton>
</DialogActions>
</MudDialog>
@code {
private static readonly string[] SampleUserNames = new[]
{
"alex99",
"samwise",
"lunaStar",
"maverick",
"nova",
"pixelPro",
"kanu",
"tay",
"zorin",
"echo"
};
private static readonly Random _rnd = new();
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
private AddEditContactCommand request = new();
private MudForm? form;
private bool success;
private string[] errors = { };
private void Cancel() => MudDialog.Cancel();
private async Task Submit()
{
await form!.Validate();
if (!success)
{
return;
}
MudDialog.Close(request);
}
protected override Task OnInitializedAsync()
{
// Assign a random username from the in-memory list when the dialog opens
request.UserName = SampleUserNames[_rnd.Next(SampleUserNames.Length)];
return base.OnInitializedAsync();
}
}

View File

@@ -0,0 +1,74 @@
@using Sufi.Demo.PeopleDirectory.Application.Features.Contacts.Queries.GetById
@inject HttpClient Http
<MudDialog>
<DialogContent>
@if (showAlert)
{
<MudAlert Severity="Severity.Error">@alertMessage</MudAlert>
}
<MudForm @ref="form" Model="request" @bind-IsValid="success" @bind-Errors="errors">
<MudTextField Label="Username" Required="true" @bind-Value="request.UserName" />
<MudTextField Label="Email" Required="true" @bind-Value="request.Email" />
<MudTextField Label="Phone" Required="true" @bind-Value="request.Phone" />
<MudTextField Label="Skill Sets" Required="true" @bind-Value="request.SkillSets" />
<MudTextField Label="Hobby" Required="true" @bind-Value="request.Hobby" />
</MudForm>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton OnClick="Submit">Ok</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public int Id { get; set; }
private AddEditContactCommand request = new();
private MudForm? form;
private bool success;
private string[] errors = { };
private bool showAlert;
private string? alertMessage;
protected override async Task OnInitializedAsync()
{
var response = await Http.GetAsync($"api/v1/contacts/{Id}");
var result = await response.ToResult<GetContactByIdResponse>();
if (result.Succeeded)
{
var contact = result.Data;
request.Id = Id;
request.UserName = contact!.UserName;
request.Email = contact.Email;
request.Phone = contact.Phone;
request.SkillSets = contact.SkillSets;
request.Hobby = contact.Hobby;
showAlert = false;
alertMessage = "";
}
else
{
showAlert = true;
alertMessage = string.Join(',', result.Messages);
}
}
private void Cancel() => MudDialog.Cancel();
private async Task Submit()
{
await form!.Validate();
if (!success)
{
return;
}
MudDialog!.Close(request);
}
}

View File

@@ -0,0 +1,22 @@
@page "/"
@inject BoomerangService Boomerang
<PageTitle>Index - Demo App</PageTitle>
<MudGrid Class="py-4">
<MudItem xs="12">
<MudText Typo="Typo.h3">Welcome to this demo app.</MudText>
</MudItem>
<MudItem xs="12">
<MudText Typo="Typo.body1">Please view the contact list page.</MudText>
</MudItem>
</MudGrid>
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await Boomerang.AddVariableAsync("PageName", "Index");
await Boomerang.SendBeaconAsync();
}
}

View File

@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MudBlazor;
using MudBlazor.Services;
using Sufi.Demo.PeopleDirectory.UI.Client;
using Sufi.Demo.PeopleDirectory.UI.Client.Services;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// MudBlazor
builder.Services.AddMudServices(config =>
{
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.TopRight;
config.SnackbarConfiguration.ClearAfterNavigation = false;
config.SnackbarConfiguration.HideTransitionDuration = 200;
config.SnackbarConfiguration.ShowTransitionDuration = 200;
config.SnackbarConfiguration.NewestOnTop = true;
});
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<BoomerangService>();
await builder.Build().RunAsync();

View File

@@ -0,0 +1,30 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50212",
"sslPort": 44358
}
},
"profiles": {
"Sufi.Demo.PeopleDirectory.UI": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7024;http://localhost:5243",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.JSInterop;
namespace Sufi.Demo.PeopleDirectory.UI.Client.Services
{
public class BoomerangService(
IJSRuntime jsRuntime
)
{
public async Task<bool> InitializeAsync(object config)
{
return await jsRuntime.InvokeAsync<bool>("boomerangHelper.init", config);
}
public async Task AddVariableAsync(string key, string value)
{
await jsRuntime.InvokeVoidAsync("boomerangHelper.addVar", key, value);
}
public async Task SendBeaconAsync()
{
await jsRuntime.InvokeVoidAsync("boomerangHelper.sendBeacon");
}
}
}

View File

@@ -0,0 +1,30 @@
@inherits LayoutComponentBase
<MudThemeProvider />
<MudPopoverProvider />
<MudDialogProvider MaxWidth="MaxWidth.ExtraSmall" FullWidth="true" />
<MudSnackbarProvider />
<MudLayout>
<MudAppBar Color="Color.Info">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@(e => ToggleDrawer())" />
<MudSpacer />
</MudAppBar>
<MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Never">
<SideNavMenu />
</MudDrawer>
<MudMainContent>
<MudContainer MaxWidth="MaxWidth.False">
@Body
</MudContainer>
</MudMainContent>
</MudLayout>
@code {
bool _drawerOpen = true;
void ToggleDrawer()
{
_drawerOpen = !_drawerOpen;
}
}

View File

@@ -0,0 +1,81 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View File

@@ -0,0 +1,11 @@
<MudNavMenu Bordered>
<MudText Typo="Typo.h6" Class="px-4 pt-3">Sufi Demo App</MudText>
<MudText Typo="Typo.body2" Class="px-4 mud-text-secondary">For demo only</MudText>
<MudDivider Class="my-2" />
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard">Home</MudNavLink>
<MudNavLink Href="/contacts" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.List">Contact List</MudNavLink>
<MudDivider Class="my-2" />
</MudNavMenu>

View File

@@ -0,0 +1,68 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.21" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.21" PrivateAssets="all" />
<PackageReference Include="MudBlazor" Version="8.13.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Sufi.Demo.PeopleDirectory.Application\Sufi.Demo.PeopleDirectory.Application.csproj" />
<ProjectReference Include="..\..\..\Sufi.Demo.PeopleDirectory.Shared\Sufi.Demo.PeopleDirectory.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Pages\Auth\" />
</ItemGroup>
<UsingTask TaskName="ComputeHtmlTicks" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Ticks ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
Ticks = DateTime.UtcNow.Ticks.ToString();
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="ReplaceTokenInFile" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<TemplateFile ParameterType="System.String" Required="true" />
<OutputFile ParameterType="System.String" Required="true" />
<Token ParameterType="System.String" Required="true" />
<Replacement ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var content = File.ReadAllText(TemplateFile);
content = content.Replace(Token, Replacement);
File.WriteAllText(OutputFile, content);
]]>
</Code>
</Task>
</UsingTask>
<Target Name="InjectHtmlTicks" BeforeTargets="Build;Publish">
<ComputeHtmlTicks>
<Output TaskParameter="Ticks" PropertyName="HtmlTicks" />
</ComputeHtmlTicks>
<Message Text="Computed Ticks: $(HtmlTicks)" Importance="Normal" />
<!-- write wwwroot/index.html from template -->
<ReplaceTokenInFile TemplateFile="$(MSBuildProjectDirectory)\wwwroot\index.template.html" OutputFile="$(MSBuildProjectDirectory)\wwwroot\index.html" Token="__HTML_TICKS__" Replacement="$(HtmlTicks)" />
</Target>
</Project>

View File

@@ -0,0 +1,17 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using Sufi.Demo.PeopleDirectory.UI.Client
@using Sufi.Demo.PeopleDirectory.UI.Client.Shared
@using MudBlazor;
@using Sufi.Demo.PeopleDirectory.Application.Features.Contacts.Commands
@using Sufi.Demo.PeopleDirectory.Application.Features.Contacts.Queries.GetAll
@using Sufi.Demo.PeopleDirectory.Shared.Wrapper
@using Sufi.Demo.PeopleDirectory.UI.Client.Pages.Dialogs
@using Sufi.Demo.PeopleDirectory.Shared.Extensions
@using Sufi.Demo.PeopleDirectory.UI.Client.Services

View File

@@ -0,0 +1,62 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1:focus {
outline: none;
}
a, .btn-link {
color: #0071c1;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.content {
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Sufi Demo App</title>
<base href="/" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css?v=639044765520078561" rel="stylesheet" />
<link href="css/app.css?v=639044765520078561" rel="stylesheet" />
<link href="Sufi.Demo.PeopleDirectory.UI.Client.styles.css?v=639044765520078561" rel="stylesheet" />
</head>
<body>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js?v=639044765520078561"></script>
<script src="js/boomerang.js"></script>
<script src="js/boomerang-interop.js"></script>
<script>
BOOMR.init({
beacon_url: "api/v1/beacon/"
});
</script>
</body>
</html>

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Sufi Demo App</title>
<base href="/" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css?v=__HTML_TICKS__" rel="stylesheet" />
<link href="css/app.css?v=__HTML_TICKS__" rel="stylesheet" />
<link href="Sufi.Demo.PeopleDirectory.UI.Client.styles.css?v=__HTML_TICKS__" rel="stylesheet" />
</head>
<body>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js?v=__HTML_TICKS__"></script>
<script src="js/boomerang.js"></script>
<script src="js/boomerang-interop.js"></script>
<script>
BOOMR.init({
beacon_url: "api/v1/beacon/"
});
</script>
</body>
</html>

View File

@@ -0,0 +1,21 @@
window.boomerangHelper = {
init: function (config) {
if (window.BOOMR && window.BOOMR.init) {
BOOMR.init(config);
return true;
}
return false;
},
addVar: function (key, value) {
if (window.BOOMR) {
BOOMR.addVar(key, value);
}
},
sendBeacon: function () {
if (window.BOOMR) {
BOOMR.sendBeacon();
}
}
};

File diff suppressed because it is too large Load Diff