Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample PR with tailwind #50

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"tailwindcss.cli": {
"version": "3.2.4",
"commands": [
"tailwindcss"
]
}
}
}
164 changes: 103 additions & 61 deletions Todo.Web/Client/Components/LogInForm.razor
Original file line number Diff line number Diff line change
@@ -1,78 +1,120 @@
@inject TodoClient Client

<EditForm Model="@this" class="form-horizontal py-5" OnValidSubmit="@Login">
<DataAnnotationsValidator />
<div class="mb-3">
<label for="username" class="form-label">User name</label>
<InputText id="username" class="form-control" @bind-Value="Username" />
<ValidationMessage For="@(() => Username)" />
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<InputText id="password" type="password" class="form-control" @bind-Value="Password" />
<ValidationMessage For="@(() => Password)" />
</div>
<div>
<button class="btn btn-primary">Login</button>
<button type="button" class="btn btn-secondary" @onclick="@Create">Create User</button>
</div>
</EditForm>
<div class="items-center px-5 py-12 lg:px-20">
<div class="flex flex-col w-full max-w-md p-10 mx-auto my-6 transition duration-500 ease-in-out transform bg-white rounded-lg md:mt-0">
<div class="mt-8">
<DataAnnotationsValidator/>
<div class="mt-6">
<div class="space-y-6">
<div>
<label for="username" class="block text-sm font-medium text-neutral-600">User name</label>
<div class="mt-1">
<InputText id="username" class="block w-full px-5 py-3 text-base text-neutral-600 placeholder-gray-300 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-50 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300" @bind-Value="Username" />
</div>
<div class="text-red-600">
<ValidationMessage For="@(() => Username)" />
</div>
</div>

<div class="space-y-1">
<label for="password" class="block text-sm font-medium text-neutral-600">Password</label>
<div class="mt-1">
<InputText type="password" id="password" class="block w-full px-5 py-3 text-base text-neutral-600 placeholder-gray-300 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-50 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300" @bind-Value="Password"/>
</div>
<div class="text-red-600">
<ValidationMessage For="@(() => Password)" />
</div>
</div>

<div class="form-horizontal">
@foreach (var provider in SocialProviders)
{
<a class="btn btn-secondary m-1" role="button" href="auth/login/@provider">@provider</a>
}
</div>
<div class="flex items-center justify-between">
<div class="text-sm">
<button type="button" class="font-medium text-blue-600 hover:text-blue-500" @onclick="@Create">Create User</button>
</div>
</div>

<div>
<button type="submit" class="flex items-center justify-center w-full px-10 py-4 text-base font-medium text-center text-white transition duration-500 ease-in-out transform bg-blue-600 rounded-xl hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Login</button>
</div>
</div>
@if (SocialProviders.Length > 0)
{
<div class="relative my-4">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 text-neutral-600 bg-white"> Or continue with </span>
</div>
</div>
<div>
@foreach (var provider in SocialProviders)
{
<a class="w-full items-center block px-10 py-3.5 text-base font-medium text-center text-blue-600 transition duration-500 ease-in-out transform border-2 border-white shadow-md rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 cursor-pointer" role="button" href="auth/login/@provider">
@provider
</a>
}
</div>
}
</div>
</div>
</div>
</div>
</EditForm>

@if (!string.IsNullOrEmpty(alertMessage))
{
<div class="alert alert-danger">@alertMessage</div>
<div class="flex bg-red-100 rounded-lg p-4 mb-4 text-sm text-red-700" role="alert">
<svg class="w-5 h-5 inline mr-3" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<div class="font-medium">
@alertMessage
</div>
</div>
}

@code {
string? alertMessage;
string? alertMessage;

[Required]
[StringLength(15)]
public string? Username { get; set; }
[Required]
[StringLength(15)]
public string? Username { get; set; }

[Required]
[StringLength(32, MinimumLength = 6, ErrorMessage = "The password must be between 6 and 32 characters long.")]
[RegularExpression("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z])(?=.*[^a-zA-Z\\d]).*$",
MatchTimeoutInMilliseconds = 1000,
ErrorMessage = "The password must contain a lower-case letter, an upper-case letter, a digit and a special character.")]
public string? Password { get; set; }
[Required]
[StringLength(32, MinimumLength = 6, ErrorMessage = "The password must be between 6 and 32 characters long.")]
[RegularExpression("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z])(?=.*[^a-zA-Z\\d]).*$",
MatchTimeoutInMilliseconds = 1000,
ErrorMessage = "The password must contain a lower-case letter, an upper-case letter, a digit and a special character.")]
public string? Password { get; set; }

[Parameter]
public EventCallback<string> OnLoggedIn { get; set; }
[Parameter]
public EventCallback<string> OnLoggedIn { get; set; }

[Parameter]
public string[] SocialProviders { get; set; } = Array.Empty<string>();
[Parameter]
public string[] SocialProviders { get; set; } = Array.Empty<string>();

async Task Login()
{
alertMessage = null;
if (await Client.LoginAsync(Username, Password))
{
await OnLoggedIn.InvokeAsync(Username);
}
else
{
alertMessage = "Login failed";
}
}
async Task Login()
{
alertMessage = null;
if (await Client.LoginAsync(Username, Password))
{
await OnLoggedIn.InvokeAsync(Username);
}
else
{
alertMessage = "Login failed";
}
}

async Task Create()
{
alertMessage = null;
if (await Client.CreateUserAsync(Username, Password))
{
await OnLoggedIn.InvokeAsync(Username);
}
else
{
alertMessage = "Failed to create user";
}
}
async Task Create()
{
alertMessage = null;
if (await Client.CreateUserAsync(Username, Password))
{
await OnLoggedIn.InvokeAsync(Username);
}
else
{
alertMessage = "Failed to create user";
}
}
}
56 changes: 32 additions & 24 deletions Todo.Web/Client/Components/TodoList.razor
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
@inject TodoClient Client


@if (todos is null)
{
<div class="d-flex justify-content-center align-items-center vh-100">
<div class="spinner-border" role="status"></div>
</div>
// https://tailwindcomponents.com/component/centered-spinner
<div class="absolute right-1/2 bottom-1/2 transform translate-x-1/2 translate-y-1/2 ">
<div class="border-t-transparent border-solid animate-spin rounded-full border-blue-400 border-2 h-12 w-12"></div>
</div>
}
else
{
<h6 class="mb-3">Todo List</h6>
<div class="rounded-3xl text-white py-10 px-6 mx-auto mt-8 mb-6 max-w-xs sm:max-w-md bg-gradient-to-b bg-blue-600 from-indigo-500 to-blue-600">
<h6 class="mb-4 text-lg">Todo List</h6>

<EditForm @ref="@form" Model="@this" OnValidSubmit="@AddTodo" class="d-flex justify-content-center align-items-center mb-4">
<DataAnnotationsValidator />
<div class="form-outline flex-fill">
<InputText autofocus class="form-control form-control-lg" @bind-Value="@NewTodo" placeholder="Type a new todo item" />
<ValidationMessage For="@(() => NewTodo)" />
</div>
</EditForm>
<ul class="mt-8">
@foreach (var todo in todos)
{
<li class="bg-black/10 rounded-lg p-4 flex mb-3 last:mb-0 hover:scale-105 transition-all duration-150 ease-in-out" @key="@todo.Id">
<div class="flex-grow">
<input class="mr-2" type="checkbox" value="" aria-label="..."/>
@todo.Title
</div>
<a class="cursor-pointer hover:text-red-500 hover:scale-105 transition-all duration-150 ease-in-out" title="Remove item" @onclick="@(() => DeleteTodo(todo))">🗙</a>
</li>
}
</ul>

<ul class="list-group mb-0">
@foreach (var todo in todos)
{
<li class="list-group-item d-flex justify-content-between align-items-center border-start-0 border-top-0 border-end-0 border-bottom rounded-0 mb-2" @key="@todo.Id">
<div class="d-flex align-items-center">
<input class="form-check-input me-2" type="checkbox" value="" aria-label="..." />
@todo.Title
</div>
<a data-mdb-toggle="tooltip" title="Remove item" style="text-decoration:none" @onclick="@(() => DeleteTodo(todo))">🗙</a>
</li>
}
</ul>
<EditForm @ref="@form" Model="@this" OnValidSubmit="@AddTodo" class="mb-4 mt-8">
<DataAnnotationsValidator/>
<div class="">
<InputText autofocus class="w-full rounded-xl bg-white/10 text-white outline-0 p-4 shadow focus:shadow-lg mt-4 transition-all duration-150 ease-in-out placeholder:text-blue-200" @bind-Value="@NewTodo" placeholder="Type a new todo item"/>
<ValidationMessage For="@(() => NewTodo)"/>
</div>
<input type="submit" class="my-5 p-5 bg-black text-white rounded-xl w-full hover:bg-opacity-60 transition border-0 capitalize flex items-center justify-center" value="Add new">
</EditForm>


</div>
}

@code {
Expand All @@ -39,7 +46,7 @@ else
[Required]
public string? NewTodo { get; set; }

[Parameter]
[Parameter]
public EventCallback OnForbidden { get; set; }

protected override async Task OnInitializedAsync()
Expand All @@ -49,6 +56,7 @@ else

async Task LoadTodos()
{
await System.Threading.Tasks.Task.Delay(1000);
(var statusCode, todos) = await Client.GetTodosAsync();

if (statusCode == HttpStatusCode.Forbidden || statusCode == HttpStatusCode.Unauthorized)
Expand Down
11 changes: 11 additions & 0 deletions Todo.Web/Client/Todo.Web.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,21 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="tailwindcss.msbuild" Version="0.0.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Shared\Todo.Web.Shared.csproj" />
</ItemGroup>

<!-- https://github.com/dotnet/sdk/issues/11253 -->
<Target Name="EnsureRestoreCli" BeforeTargets="BeforeBuild">
<Exec Command="dotnet tool restore" />
</Target>

<PropertyGroup>
<TailwindCSSReleaseBuildArguments>-i main.css -o ./wwwroot/css/tailwind.css --minify</TailwindCSSReleaseBuildArguments>
<TailwindCSSDebugBuildArguments>-i main.css -o ./wwwroot/css/tailwind.css</TailwindCSSDebugBuildArguments>
</PropertyGroup>

</Project>
58 changes: 35 additions & 23 deletions Todo.Web/Client/TodoApp.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,50 @@

@if (!string.IsNullOrEmpty(CurrentUser))
{
<ul class="nav justify-content-center">
<li class="nav-item">
Logged in as <strong>@CurrentUser</strong>
<a class="btn btn-primary" role="button" @onclick="@Logout">Logout</a>
</li>
</ul>
<header>
<nav class="bg-white border-gray-200 bg-gray-50 px-4 lg:px-6 py-2.5">
<div class="flex flex-wrap justify-between items-center mx-auto max-w-screen-xl">
<a href="#" class="flex items-center">
<div class="w-6 h-6">
<svg fill="none" viewBox="-10.12021875 -53.60951036 339.95397529 343.02235093" xmlns="http://www.w3.org/2000/svg"><path d="m303.935 88.479c-6.598 41.362-27.653 79.041-59.42 106.335s-72.185 42.433-114.064 42.723c-8.483.326-16.977-.19-25.358-1.539a77.723 77.723 0 0 1 -64.63-73.266 75.479 75.479 0 0 1 22.14-52.762 75.46 75.46 0 0 1 105.885-.748 75.478 75.478 0 0 1 22.884 52.443c.317 12.51-5.102 23.483-16.239 23.8-11.899 0-17.477-8.491-17.477-19.934v-31.797a19.478 19.478 0 0 0 -19.323-19.494h-26.653a46.386 46.386 0 0 0 -39.119 20.961 46.399 46.399 0 0 0 31.587 71.268 46.392 46.392 0 0 0 41.8-14.911l.932-1.39.933 1.543a32.82 32.82 0 0 0 27.986 13.328 36.992 36.992 0 0 0 34.268-38.671 100.64 100.64 0 0 0 -2.761-24.577c-4.943-22.734-18.126-42.834-37.008-56.423a94.153 94.153 0 0 0 -125.236 13.718 94.175 94.175 0 0 0 -23.92 63.097 95.352 95.352 0 0 0 27.473 65.824 95.331 95.331 0 0 0 65.448 28.344s6.98.635 14.849.454a200.94 200.94 0 0 0 107.769-32.155c.457-.318.914.317.61.78a158.177 158.177 0 0 1 -123.225 50.396 111.185 111.185 0 0 1 -80.961-32.871 111.215 111.215 0 0 1 -32.215-81.232 115.099 115.099 0 0 1 46.223-92.17 112.704 112.704 0 0 1 66.497-21.953h35.772a100.637 100.637 0 0 0 74.247-32.784 1.39 1.39 0 0 1 .755-.431 1.418 1.418 0 0 1 1.52.663c.153.257.222.555.197.854a100.93 100.93 0 0 1 -15.608 45.14 1.386 1.386 0 0 0 .115 1.511 1.387 1.387 0 0 0 1.424.507 108.158 108.158 0 0 0 75.198-62.013c.173-.277.411-.507.695-.67a1.902 1.902 0 0 1 1.869 0c.284.162.523.392.694.67a137.098 137.098 0 0 1 13.447 87.432zm-189.964 44.858a27.823 27.823 0 0 0 -27.293 33.255 27.83 27.83 0 0 0 21.862 21.865 27.824 27.824 0 0 0 33.251-27.296v-25.977a2.007 2.007 0 0 0 -1.904-1.904z" fill="#702af7" /></svg>
</div>
<span class="self-center text-xl font-semibold whitespace-nowrap pl-2">AspNet + Blazor</span>
</a>
<div class="flex items-center lg:order-2">
<span class="pr-4">Logged in as <strong>@CurrentUser</strong></span>
<a href="#" @onclick="@Logout" class="text-red-600 hover:text-red-900 bg-gray-100 hover:bg-red-200 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 focus:outline-none transition-all duration-150 ease-in-out" role="button">
Logout
</a>
</div>
</div>
</nav>
</header>

<TodoList OnForbidden="@Logout" />
<TodoList OnForbidden="@Logout" />
}
else
{
<LogInForm OnLoggedIn="@HandleLogin" SocialProviders="@SocialProviders" />
<LogInForm OnLoggedIn="@HandleLogin" SocialProviders="@SocialProviders" />
}

@code {
[Parameter]
public string? CurrentUser { get; set; }
[Parameter]
public string? CurrentUser { get; set; }

[Parameter]
public string[] SocialProviders { get; set; } = Array.Empty<string>();
[Parameter]
public string[] SocialProviders { get; set; } = Array.Empty<string>();


void HandleLogin(string newUsername)
{
CurrentUser = newUsername;
}
void HandleLogin(string newUsername)
{
CurrentUser = newUsername;
}

async Task Logout()
{
if (await Client.LogoutAsync())
{
CurrentUser = null;
}
}
async Task Logout()
{
if (await Client.LogoutAsync())
{
CurrentUser = null;
}
}
}
Loading