mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-19 23:28:23 -04:00
Add email claim enable/disable toggle to admin (#711)
This commit is contained in:
committed by
Leendert de Borst
parent
8c1e5a7bf8
commit
2071a7c4fe
@@ -32,6 +32,11 @@ public class UserEmailClaimWithCount
|
||||
/// </summary>
|
||||
public string AddressDomain { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the email claim is disabled.
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the created at timestamp.
|
||||
/// </summary>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@if (ShowChart && !IsLoading)
|
||||
{
|
||||
<div class="mt-6">
|
||||
@@ -65,6 +65,7 @@
|
||||
/// </summary>
|
||||
private bool ShowChart { get; set; } = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshData();
|
||||
@@ -128,12 +129,12 @@
|
||||
Date = g.Key,
|
||||
Count = g.Count()
|
||||
}).ToListAsync();
|
||||
|
||||
|
||||
// Fill in any missing days with zero counts
|
||||
var allDates = Enumerable.Range(0, DaysToShow)
|
||||
.Select(offset => DateTime.UtcNow.Date.AddDays(-offset))
|
||||
.Reverse();
|
||||
|
||||
|
||||
DailyEmailClaimCounts = allDates
|
||||
.GroupJoin(
|
||||
DailyEmailClaimCounts,
|
||||
@@ -148,7 +149,7 @@
|
||||
private void ToggleChart()
|
||||
{
|
||||
ShowChart = !ShowChart;
|
||||
|
||||
|
||||
// If we're showing the chart but haven't loaded the data yet
|
||||
if (ShowChart && DailyEmailClaimCounts.Count == 0)
|
||||
{
|
||||
@@ -167,7 +168,7 @@
|
||||
public int Days7 { get; set; }
|
||||
public int Days14 { get; set; }
|
||||
}
|
||||
|
||||
|
||||
private sealed class DailyEmailClaimCount
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
@@ -59,6 +59,13 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The search term from the query parameter.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[SupplyParameterFromQuery(Name = "search")]
|
||||
public string? SearchTermFromQuery { get; set; }
|
||||
|
||||
private readonly List<TableColumn> _tableColumns = [
|
||||
new TableColumn { Title = "ID", PropertyName = "Id" },
|
||||
new TableColumn { Title = "Time", PropertyName = "DateSystem" },
|
||||
@@ -70,9 +77,7 @@ else
|
||||
|
||||
private List<EmailViewModel> EmailViewModelList { get; set; } = [];
|
||||
private bool IsInitialized { get; set; } = false;
|
||||
|
||||
private bool IsLoading { get; set; } = true;
|
||||
|
||||
private int CurrentPage { get; set; } = 1;
|
||||
private int PageSize { get; set; } = 50;
|
||||
private int TotalRecords { get; set; }
|
||||
@@ -110,6 +115,12 @@ else
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// Set the search term from the query parameter if it exists
|
||||
if (!string.IsNullOrEmpty(SearchTermFromQuery))
|
||||
{
|
||||
_searchTerm = SearchTermFromQuery;
|
||||
}
|
||||
|
||||
await RefreshData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,125 @@
|
||||
@using AliasVault.RazorComponents.Tables
|
||||
|
||||
<SortableTable Columns="@_emailClaimTableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
|
||||
@foreach (var entry in SortedEmailClaimList)
|
||||
{
|
||||
<SortableTableRow>
|
||||
<SortableTableColumn IsPrimary="true">@entry.Id</SortableTableColumn>
|
||||
<SortableTableColumn>@entry.CreatedAt.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
|
||||
<SortableTableColumn>@entry.Address</SortableTableColumn>
|
||||
<SortableTableColumn>@entry.EmailCount</SortableTableColumn>
|
||||
</SortableTableRow>
|
||||
}
|
||||
</SortableTable>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button Color="secondary" OnClick="ToggleShowDisabled">
|
||||
@(ShowDisabled ? "Hide Disabled Claims" : "Show Disabled Claims")
|
||||
</Button>
|
||||
@if (EmailClaimList.Any(e => !e.Disabled))
|
||||
{
|
||||
<Button Color="danger" OnClick="DisableAllEmailClaims">Disable All</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (IsLoading)
|
||||
{
|
||||
<LoadingIndicator />
|
||||
}
|
||||
else
|
||||
{
|
||||
<SortableTable Columns="@_emailClaimTableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
|
||||
@foreach (var entry in SortedEmailClaimList)
|
||||
{
|
||||
<SortableTableRow Class="@(entry.Disabled ? "bg-secondary" : "")">
|
||||
<SortableTableColumn IsPrimary="true">@entry.Id</SortableTableColumn>
|
||||
<SortableTableColumn>@entry.CreatedAt.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
|
||||
<SortableTableColumn><a href="/emails?search=@entry.Address">@entry.Address</a></SortableTableColumn>
|
||||
<SortableTableColumn>@entry.EmailCount</SortableTableColumn>
|
||||
<SortableTableColumn>@(entry.Disabled ? "Disabled" : "Enabled")</SortableTableColumn>
|
||||
<SortableTableColumn>
|
||||
@if (entry.Disabled)
|
||||
{
|
||||
<Button Color="success" OnClick="() => ToggleEmailClaimStatus(entry)">Enable</Button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Button Color="danger" OnClick="() => ToggleEmailClaimStatus(entry)">Disable</Button>
|
||||
}
|
||||
</SortableTableColumn>
|
||||
</SortableTableRow>
|
||||
}
|
||||
</SortableTable>
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Gets or sets the list of email claims to display.
|
||||
/// Gets or sets the user.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public List<UserEmailClaimWithCount> EmailClaimList { get; set; } = [];
|
||||
public AliasVaultUser User { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the callback for when an email claim is enabled or disabled.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<(Guid id, bool disabled)> OnEmailClaimStatusChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of email claims to display.
|
||||
/// </summary>
|
||||
private List<UserEmailClaimWithCount> EmailClaimList { get; set; } = [];
|
||||
|
||||
private bool IsLoading { get; set; } = true;
|
||||
|
||||
private string SortColumn { get; set; } = "CreatedAt";
|
||||
private SortDirection SortDirection { get; set; } = SortDirection.Descending;
|
||||
private bool ShowDisabled { get; set; } = false;
|
||||
|
||||
private readonly List<TableColumn> _emailClaimTableColumns = [
|
||||
new TableColumn { Title = "ID", PropertyName = "Id" },
|
||||
new TableColumn { Title = "Created", PropertyName = "CreatedAt" },
|
||||
new TableColumn { Title = "Email", PropertyName = "Address" },
|
||||
new TableColumn { Title = "Email Count", PropertyName = "EmailCount" },
|
||||
new TableColumn { Title = "Status", PropertyName = "Disabled" },
|
||||
new TableColumn { Title = "Actions", PropertyName = "" },
|
||||
];
|
||||
|
||||
private IEnumerable<UserEmailClaimWithCount> SortedEmailClaimList => SortList(EmailClaimList, SortColumn, SortDirection);
|
||||
private IEnumerable<UserEmailClaimWithCount> SortedEmailClaimList =>
|
||||
SortList(ShowDisabled ? EmailClaimList : EmailClaimList.Where(e => !e.Disabled).ToList(), SortColumn, SortDirection);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
await RefreshData();
|
||||
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method will refresh the email claim list.
|
||||
/// </summary>
|
||||
private async Task RefreshData()
|
||||
{
|
||||
await using var dbContext = await DbContextFactory.CreateDbContextAsync();
|
||||
|
||||
if (string.IsNullOrEmpty(User.Id))
|
||||
{
|
||||
EmailClaimList = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Load all email claims for this user.
|
||||
EmailClaimList = await dbContext.UserEmailClaims
|
||||
.Where(x => x.UserId == User.Id)
|
||||
.Select(x => new UserEmailClaimWithCount
|
||||
{
|
||||
Id = x.Id,
|
||||
Address = x.Address,
|
||||
AddressLocal = x.AddressLocal,
|
||||
AddressDomain = x.AddressDomain,
|
||||
CreatedAt = x.CreatedAt,
|
||||
UpdatedAt = x.UpdatedAt,
|
||||
EmailCount = dbContext.Emails.Count(e => e.To == x.Address),
|
||||
Disabled = x.Disabled
|
||||
})
|
||||
.OrderBy(x => x.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private void HandleSortChanged((string column, SortDirection direction) sort)
|
||||
{
|
||||
@@ -38,6 +128,87 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ToggleShowDisabled()
|
||||
{
|
||||
ShowDisabled = !ShowDisabled;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method will toggle the disabled status of an email claim.
|
||||
/// </summary>
|
||||
private async Task ToggleEmailClaimStatus(UserEmailClaimWithCount entry)
|
||||
{
|
||||
await using var dbContext = await DbContextFactory.CreateDbContextAsync();
|
||||
|
||||
if (entry.Disabled)
|
||||
{
|
||||
// Enable email claim without confirmation.
|
||||
var emailClaim = await dbContext.UserEmailClaims.FindAsync(entry.Id);
|
||||
if (emailClaim != null)
|
||||
{
|
||||
// Re-enable the email claim.
|
||||
emailClaim.Disabled = false;
|
||||
emailClaim.UpdatedAt = DateTime.UtcNow;
|
||||
await dbContext.SaveChangesAsync();
|
||||
await RefreshData();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await ConfirmModalService.ShowConfirmation(
|
||||
title: "Confirm Email Claim Disable",
|
||||
message: @"Are you sure you want to disable this email claim?
|
||||
|
||||
Important notes:
|
||||
• Disabling an email claim means that emails will no longer be received for this address and will be rejected by the server.
|
||||
• The user can re-enable this at will by re-saving their vault which will claim it again.
|
||||
|
||||
Do you want to proceed with disabling this claim?"))
|
||||
{
|
||||
// Load email claim
|
||||
var emailClaim = await dbContext.UserEmailClaims.FindAsync(entry.Id);
|
||||
if (emailClaim != null)
|
||||
{
|
||||
// Set the disabled status to true.
|
||||
emailClaim.Disabled = true;
|
||||
emailClaim.UpdatedAt = DateTime.UtcNow;
|
||||
await dbContext.SaveChangesAsync();
|
||||
await RefreshData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisableAllEmailClaims()
|
||||
{
|
||||
if (await ConfirmModalService.ShowConfirmation(
|
||||
title: "Confirm Email Claim Disable",
|
||||
message: @"Are you sure you want to disable all email claims?
|
||||
|
||||
Important notes:
|
||||
• Disabling an email claim means that emails will no longer be received for this address and will be rejected by the server.
|
||||
• The user can re-enable this at will by re-saving their vault which will claim it again.
|
||||
|
||||
Do you want to proceed with disabling all email claims?"))
|
||||
{
|
||||
await using var dbContext = await DbContextFactory.CreateDbContextAsync();
|
||||
|
||||
// Load email claims
|
||||
var emailClaims = await dbContext.UserEmailClaims.Where(x => x.UserId == User.Id).ToListAsync();
|
||||
|
||||
// Disable all email claims.
|
||||
foreach (var emailClaim in emailClaims)
|
||||
{
|
||||
emailClaim.Disabled = true;
|
||||
emailClaim.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync();
|
||||
await RefreshData();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<UserEmailClaimWithCount> SortList(List<UserEmailClaimWithCount> emailClaims, string sortColumn, SortDirection sortDirection)
|
||||
{
|
||||
return sortColumn switch
|
||||
@@ -46,6 +217,7 @@
|
||||
"CreatedAt" => SortableTable.SortListByProperty(emailClaims, e => e.CreatedAt, sortDirection),
|
||||
"Address" => SortableTable.SortListByProperty(emailClaims, e => e.Address, sortDirection),
|
||||
"EmailCount" => SortableTable.SortListByProperty(emailClaims, e => e.EmailCount, sortDirection),
|
||||
"Disabled" => SortableTable.SortListByProperty(emailClaims, e => e.Disabled, sortDirection),
|
||||
_ => emailClaims
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,8 +105,12 @@ else
|
||||
<div class="items-center">
|
||||
<div>
|
||||
<h3 class="mb-1 text-xl font-bold text-gray-900 dark:text-white">Email claims</h3>
|
||||
|
||||
<EmailClaimTable EmailClaimList="@EmailClaimList" />
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Email claims represent the email addresses that the user has (historically) used. Whenever a user deletes an email alias
|
||||
the claim gets disabled and the server will reject all emails sent to that alias. A user can always re-enable
|
||||
the claim by using it again. Email claims are permanently tied to a user and cannot be transferred to another user.
|
||||
</p>
|
||||
<EmailClaimTable User="@User" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,7 +130,6 @@ else
|
||||
private int TwoFactorKeysCount { get; set; }
|
||||
private List<AliasVaultUserRefreshToken> RefreshTokenList { get; set; } = [];
|
||||
private List<Vault> VaultList { get; set; } = [];
|
||||
private List<UserEmailClaimWithCount> EmailClaimList { get; set; } = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -201,22 +204,6 @@ else
|
||||
.OrderBy(x => x.UpdatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
// Load all email claims for this user.
|
||||
EmailClaimList = await dbContext.UserEmailClaims
|
||||
.Where(x => x.UserId == User.Id)
|
||||
.Select(x => new UserEmailClaimWithCount
|
||||
{
|
||||
Id = x.Id,
|
||||
Address = x.Address,
|
||||
AddressLocal = x.AddressLocal,
|
||||
AddressDomain = x.AddressDomain,
|
||||
CreatedAt = x.CreatedAt,
|
||||
UpdatedAt = x.UpdatedAt,
|
||||
EmailCount = dbContext.Emails.Count(e => e.To == x.Address)
|
||||
})
|
||||
.OrderBy(x => x.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -603,6 +603,10 @@ video {
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.right-0 {
|
||||
right: 0px;
|
||||
}
|
||||
@@ -611,10 +615,6 @@ video {
|
||||
top: 38px;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -663,10 +663,18 @@ video {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
@@ -715,6 +723,10 @@ video {
|
||||
margin-inline-start: 0.25rem;
|
||||
}
|
||||
|
||||
.ms-2 {
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-0 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
@@ -743,18 +755,6 @@ video {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.ms-4 {
|
||||
margin-inline-start: 1rem;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.line-clamp-1 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
@@ -914,10 +914,6 @@ video {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -968,10 +964,6 @@ video {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grid-cols-\[150px_1fr\] {
|
||||
grid-template-columns: 150px 1fr;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -984,10 +976,6 @@ video {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1020,10 +1008,6 @@ video {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.space-x-1 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.25rem * var(--tw-space-x-reverse));
|
||||
@@ -1365,11 +1349,6 @@ video {
|
||||
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
@@ -1483,14 +1462,26 @@ video {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pl-2 {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.pl-3 {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.ps-10 {
|
||||
padding-inline-start: 2.5rem;
|
||||
}
|
||||
|
||||
.ps-2 {
|
||||
padding-inline-start: 0.5rem;
|
||||
}
|
||||
@@ -1507,30 +1498,6 @@ video {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pl-10 {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
|
||||
.pl-3 {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.ps-8 {
|
||||
padding-inline-start: 2rem;
|
||||
}
|
||||
|
||||
.ps-12 {
|
||||
padding-inline-start: 3rem;
|
||||
}
|
||||
|
||||
.ps-10 {
|
||||
padding-inline-start: 2.5rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1807,11 +1774,6 @@ video {
|
||||
background-color: rgb(153 27 27 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-blue-800:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-gray-700:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||
@@ -1908,11 +1870,6 @@ video {
|
||||
--tw-ring-color: rgb(252 165 165 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-blue-300:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:divide-gray-600:is(.dark *) > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-opacity: 1;
|
||||
border-color: rgb(75 85 99 / var(--tw-divide-opacity));
|
||||
@@ -2053,11 +2010,6 @@ video {
|
||||
background-color: rgb(113 63 18 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-blue-600:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-opacity-80:is(.dark *) {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
@@ -2142,11 +2094,6 @@ video {
|
||||
color: rgb(254 240 138 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-blue-500:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:placeholder-gray-400:is(.dark *)::-moz-placeholder {
|
||||
--tw-placeholder-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
|
||||
@@ -2186,11 +2133,6 @@ video {
|
||||
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:hover\:bg-blue-700:hover:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:hover\:text-gray-300:hover:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(209 213 219 / var(--tw-text-opacity));
|
||||
@@ -2266,11 +2208,6 @@ video {
|
||||
--tw-ring-color: rgb(153 27 27 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-blue-800:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(30 64 175 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sm\:mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
|
||||
Reference in New Issue
Block a user