Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/AdminUI/AdminPortalMudTheme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ static AdminPortalMudTheme()
Black = "#27272f",
Background = "#181818",
BackgroundGrey = "#27272f",
Surface = "#373740",
Surface = "#333333",
DrawerBackground = "#333333",
DrawerText = "#ffffffff",
DrawerIcon = "rgba(255,255,255, 0.50)",
Expand Down
2 changes: 1 addition & 1 deletion src/AdminUI/Helpers/DateTimeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ public static class DateTimeFormatter
public static string FormatLongDate(DateTime? date)
=> date.HasValue
// Format based on https://www.ssw.com.au/rules/weekdays-on-date-selectors/
? $"{date.Value:ddd}, {date.Value:d} {date.Value:t}"
? $"{date.Value:ddd} {date.Value:d} {date.Value:t}"
: "-";
}
84 changes: 45 additions & 39 deletions src/AdminUI/Pages/SendNotification.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@page "/send-notification"
@page "/send-notification"

@using Microsoft.AspNetCore.Authorization
@using MudBlazor
Expand All @@ -16,7 +16,7 @@
<div class="send-notification-layout">
<!-- Form Column -->
<div class="send-notification-form">
<MudPaper Class="pa-4 mt-4 warm-grey-bg send-notification-paper" aria-labelledby="page-title">
<MudPaper Class="pa-4 mt-4 send-notification-paper" aria-labelledby="page-title">
<EditForm Model="@_model" OnValidSubmit="@HandleValidSubmit" onkeypress="return event.keyCode!=13">
<DataAnnotationsValidator />
<MudGrid Spacing="3">
Expand All @@ -37,52 +37,53 @@
</MudItem>

<!-- Always visible Date/Time/Timezone; disabled when not scheduling -->
<MudItem xs="12" sm="6" md="4">
<MudDatePicker Label="Date" Date="@_model.ScheduleDate" DateChanged="@OnDateChanged"
For="@(() => _model.ScheduleDate)"
Required="@(_model.DeliveryOption == Delivery.Schedule)"
Disabled="@(_model.DeliveryOption != Delivery.Schedule)" MinDate="@DateTime.Today"
data-testid="schedule-date" aria-label="Schedule date for notification"
Class="schedule-field-large" />
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudTimePicker Label="Time" Time="@_model.ScheduleTime" TimeChanged="@OnTimeChanged"
For="@(() => _model.ScheduleTime)"
Required="@(_model.DeliveryOption == Delivery.Schedule)"
Disabled="@(_model.DeliveryOption != Delivery.Schedule)" data-testid="schedule-time"
aria-label="Schedule time for notification" Class="schedule-field-large" />
</MudItem>
<MudItem xs="12" sm="6" md="4">
<MudSelect @bind-Value="@_model.SelectedTimeZone" Label="Timezone"
For="@(() => _model.SelectedTimeZone)"
Required="@(_model.DeliveryOption == Delivery.Schedule)"
Disabled="@(_model.DeliveryOption != Delivery.Schedule)"
AnchorOrigin="Origin.BottomCenter" data-testid="schedule-timezone"
aria-label="Select timezone for scheduled notification" Class="schedule-field-large">
@foreach (var tz in _timezones)
{
<MudSelectItem Value="@tz.Key">@tz.Value</MudSelectItem>
}
</MudSelect>
<MudItem xs="12">
<div class="schedule-controls-container">
<MudDatePicker Label="Date" Date="@_model.ScheduleDate" DateChanged="@OnDateChanged"
For="@(() => _model.ScheduleDate)"
Required="@(_model.DeliveryOption == Delivery.Schedule)"
Disabled="@(_model.DeliveryOption != Delivery.Schedule)" MinDate="@DateTime.Today"
data-testid="schedule-date" aria-label="Schedule date for notification"
Class="schedule-field-large schedule-date-picker" />
<MudTimePicker Label="Time" Time="@_model.ScheduleTime" TimeChanged="@OnTimeChanged"
For="@(() => _model.ScheduleTime)"
Required="@(_model.DeliveryOption == Delivery.Schedule)"
Disabled="@(_model.DeliveryOption != Delivery.Schedule)" data-testid="schedule-time"
aria-label="Schedule time for notification"
Class="schedule-field-large schedule-time-picker" />
<MudSelect @bind-Value="@_model.SelectedTimeZone" Label="Timezone"
For="@(() => _model.SelectedTimeZone)"
Required="@(_model.DeliveryOption == Delivery.Schedule)"
Disabled="@(_model.DeliveryOption != Delivery.Schedule)"
AnchorOrigin="Origin.BottomCenter" data-testid="schedule-timezone"
aria-label="Select timezone for scheduled notification"
Class="schedule-field-large schedule-timezone-select">
@foreach (var tz in _timezones)
{
<MudSelectItem Value="@tz.Key">@tz.Value</MudSelectItem>
}
</MudSelect>
</div>
</MudItem>

<MudItem xs="12">
<MudItem xs="12" Class="target-item-first">
<MudText Typo="Typo.h5" Class="section-header" id="target-section-label">
Target<span class="required-indicator">*</span></MudText>
<MudRadioGroup @bind-Value="@_model.TargetingOption" aria-labelledby="target-section-label"
data-testid="target-radio-group" Class="radio-group-large">
data-testid="target-radio-group" Class="radio-group-inline">
<MudRadio Option="@Targeting.Everyone" Color="Color.Primary"
data-testid="target-everyone" aria-label="Send to everyone">Everyone</MudRadio>
</MudRadioGroup>
</MudItem>

<!-- Achievement selector -->
<MudItem xs="12" Class="target-item">
<MudRadioGroup @bind-Value="@_model.TargetingOption" aria-labelledby="target-section-label"
data-testid="target-radio-group-achievement" Class="radio-group-inline">
<MudRadio Option="@Targeting.Achievement" Color="Color.Primary"
data-testid="target-achievement"
aria-label="Send to users with specific achievement">Requires Achievement</MudRadio>
<MudRadio Option="@Targeting.Role" Color="Color.Primary" data-testid="target-role"
aria-label="Send to users with specific role">Requires Role</MudRadio>
</MudRadioGroup>
</MudItem>

<!-- Always visible achievement selector -->
<MudItem xs="12">
<MudAutocomplete T="AchievementDto" Label="Required Achievement"
@bind-Value="@_model.SelectedAchievement" SearchFunc="@SearchAchievements"
ToStringFunc="@(a => a?.Name)" ShowProgressIndicator="true" ResetValueOnEmptyText="true"
Expand All @@ -92,8 +93,13 @@
aria-label="Search and select required achievement" Variant="Variant.Outlined" />
</MudItem>

<!-- Always visible role selector -->
<MudItem xs="12">
<!-- Role selector -->
<MudItem xs="12" Class="target-item">
<MudRadioGroup @bind-Value="@_model.TargetingOption" aria-labelledby="target-section-label"
data-testid="target-radio-group-role" Class="radio-group-inline">
<MudRadio Option="@Targeting.Role" Color="Color.Primary" data-testid="target-role"
aria-label="Send to users with specific role">Requires Role</MudRadio>
</MudRadioGroup>
<MudAutocomplete T="RoleDto" @bind-Value="@_model.SelectedRole" Label="Role"
SearchFuncWithCancel="@SearchRoles" ToStringFunc="@(role => role?.Name ?? string.Empty)"
ShowProgressIndicator="true" ResetValueOnEmptyText="true"
Expand Down
137 changes: 137 additions & 0 deletions src/AdminUI/Pages/SendNotification.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,79 @@
margin-top: 0 !important;
}

/* Container for Date/Time/Timezone fields to allow flex layout and offset */
.schedule-controls-container {
display: flex;
flex-direction: row;
gap: 24px;
margin-left: 108px; /* Offset to align with 'Schedule' radio button on desktop */
}

.schedule-date-picker,
.schedule-time-picker {
flex: 1;
}

.schedule-timezone-select {
flex: 1.5; /* Timezone takes more width space than Date and Time */
}

/* Remove offset and stack vertically on mobile */
@media (max-width: 1400px) {
.schedule-controls-container {
margin-left: 0;
flex-direction: column;
gap: 16px;
}

.schedule-date-picker,
.schedule-time-picker,
.schedule-timezone-select {
flex: none;
width: 100%;
}
}

/* Mobile-specific layout adjustments */
@media (max-width: 599px) {
.schedule-controls-container {
gap: 12px;
}

/* Stack radio buttons vertically on mobile */
::deep .radio-group-large {
flex-direction: column;
gap: 4px;
}
}

/* Larger font for radio groups */
.radio-group-large label {
font-size: 1.25rem !important;
}

/* Inline radio buttons above their respective dropdowns */
::deep .radio-group-inline {
display: flex;
flex-wrap: wrap;
gap: 0 16px;
margin-bottom: 0px;
}

::deep .radio-group-inline label {
font-size: 1rem !important;
}

/* Reduce spacing for target section items */
::deep .target-item {
padding-top: 4px !important;
padding-bottom: 4px !important;
}

::deep .target-item-first {
padding-bottom: 0 !important;
}

/* Section headers */
.section-header {
font-weight: 500;
Expand All @@ -39,10 +107,51 @@
margin-right: auto;
}

/* Mobile page adjustments */
@media (max-width: 599px) {
.send-notification-page {
padding: 0;
}

.send-notification-page h3 {
font-size: 1.5rem;
margin-bottom: 8px;
}
}

.send-notification-paper {
overflow-x: hidden;
}

/* Mobile paper adjustments */
@media (max-width: 599px) {
.send-notification-paper {
padding: 16px !important;
margin-top: 8px !important;
}
}

/* Ensure labels and inputs have consistent margins/padding */
::deep .send-notification-paper .mud-input-label {
margin-left: 12px;
margin-right: 12px;
}

::deep .send-notification-paper .mud-input-slot {
padding-left: 12px !important;
padding-right: 12px !important;
}

/* Ensure the input underline in error state matches normal state style but stays red */
::deep .send-notification-paper .mud-input.mud-input-underline.mud-input-error::after {
border-bottom-width: 1px !important;
}

::deep .send-notification-paper .mud-input.mud-input-underline.mud-input-error::before {
border-bottom-width: 1px !important;
border-bottom-color: var(--mud-palette-error) !important;
}

/* Two-column layout: form + preview */
.send-notification-layout {
display: flex;
Expand Down Expand Up @@ -216,4 +325,32 @@
.preview-placeholder {
color: #8e8e93;
font-style: italic;
}

/* Mobile form adjustments */
@media (max-width: 599px) {
/* Reduce section header size on mobile */
.section-header {
font-size: 1.1rem !important;
}

/* Full-width button on mobile */
::deep .send-notification-paper .d-flex.justify-end {
justify-content: stretch !important;
}

::deep .send-notification-paper .d-flex.justify-end button {
width: 100%;
margin-left: 0 !important;
}

/* Reduce MudGrid spacing on mobile */
::deep .send-notification-paper .mud-grid {
gap: 8px;
}

/* Reduce MudItem padding on mobile */
::deep .send-notification-paper .mud-grid > .mud-item {
padding: 8px 0 !important;
}
}
61 changes: 61 additions & 0 deletions src/AdminUI/wwwroot/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,23 @@ html, body {
/* Smokey white background for active (focused) inputs */
:root {
--smokey-white: #F7F7F7;
--ssw-red: #cc4141;
--validation-pink: #ff8a8a;
--label-grey: rgba(255, 255, 255, 0.5);
}

/* Generic inputs */
input:focus, textarea:focus, select:focus {
background-color: var(--smokey-white);
}

/* Ensure all inputs have consistent padding */
input.mud-input-slot,
textarea.mud-input-slot {
padding-left: 12px !important;
padding-right: 12px !important;
}

/* MudTextField - black text on smokey white when focused */
/* Target the input element directly as it has the mud-input-slot class */
input.mud-input-slot:focus,
Expand Down Expand Up @@ -102,6 +112,57 @@ textarea.mud-input-slot:not(:focus),
background-color: transparent;
}

/* ======================
Label Styling - Keep grey for focused inputs (no red)
====================== */
/* Override MudBlazor's default red label on focus */
.mud-input-label,
.mud-input-label-filled,
.mud-input-label-outlined,
.mud-input-control .mud-input-label,
.mud-input-control.mud-input-control-focused .mud-input-label,
.mud-input-control:focus-within .mud-input-label {
color: var(--label-grey) !important;
}

/* Remove red underline on focused inputs */
.mud-input.mud-input-underline:after,
.mud-input-control-focused .mud-input.mud-input-underline:after {
border-bottom-color: transparent !important;
}

/* Remove red border on outlined focused inputs */
.mud-input-outlined.mud-focused .mud-input-outlined-border,
.mud-input-control-focused .mud-input-outlined-border {
border-color: rgba(255, 255, 255, 0.3) !important;
}

/* Ensure textarea has proper padding when focused */
textarea.mud-input-slot {
padding: 12px !important;
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is redundant padding specification for textarea elements. Lines 65-69 already set padding-left and padding-right to 12px for textarea.mud-input-slot, and then lines 141-143 set padding to 12px for the same selector. The second rule with the shorthand 'padding' property will override the first rule. Consider consolidating these rules to avoid confusion and improve maintainability.

Suggested change
padding: 12px !important;
padding-top: 12px !important;
padding-bottom: 12px !important;

Copilot uses AI. Check for mistakes.
}

/* ======================
Validation Error Styling
====================== */
/* Error state: SSW red border around the entire field */
.mud-input-control.mud-input-error .mud-input,
.mud-input-control.mud-input-error .mud-input-outlined-border {
border: 2px solid var(--ssw-red) !important;
border-radius: 4px;
}

/* Error state: light red/pink validation message text */
.mud-input-helper-text.mud-input-error,
.mud-input-control.mud-input-error .mud-input-helper-text {
color: var(--validation-pink) !important;
}

/* Error state: label should also be light red/pink */
.mud-input-control.mud-input-error .mud-input-label {
color: var(--validation-pink) !important;
}

/* Remove red on black background */
.mud-selected-item .mud-list-item-text .mud-typography {
color: #ffffff;
Expand Down
Loading