More UI tweaks (#1740)

This commit is contained in:
Aditya Chandel
2025-12-02 23:31:48 -07:00
committed by GitHub
parent 0b286619ae
commit bb31f67d80
34 changed files with 1105 additions and 160 deletions

View File

@@ -20,6 +20,7 @@ export class BookDialogHelperService {
modal: true,
closable: true,
contentStyle: {overflow: 'auto'},
styleClass: 'dynamic-dialog-minimal',
baseZIndex: 10,
style: {
position: 'absolute',
@@ -40,6 +41,7 @@ export class BookDialogHelperService {
dismissableMask: true,
closable: true,
contentStyle: {overflow: 'auto'},
styleClass: 'dynamic-dialog-minimal',
baseZIndex: 10,
style: {
position: 'absolute',

View File

@@ -467,6 +467,7 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy {
dismissableMask: true,
closable: true,
contentStyle: {overflow: 'auto'},
styleClass: 'dynamic-dialog-minimal',
baseZIndex: 10,
style: {
position: 'absolute',

View File

@@ -77,6 +77,7 @@
label="Create Shelf"
icon="pi pi-plus"
[outlined]="true"
severity="info"
(onClick)="createShelfDialog()"
/>
<div class="footer-actions">

View File

@@ -3,7 +3,6 @@
max-width: 550px;
display: flex;
flex-direction: column;
padding-top: 20px;
@media (max-width: 640px) {
width: 100%;
@@ -16,8 +15,7 @@
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem 3.5rem 1.25rem 1.25rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
border-bottom: none;
@@ -94,7 +92,7 @@
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
padding: 1.75rem;
background: var(--card-background);
border: 1px solid var(--border-color);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
@@ -342,7 +340,7 @@
justify-content: space-between;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
padding: 1.25rem 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;

View File

@@ -42,7 +42,7 @@
</div>
</div>
<div class="divider"></div>
<div class="gradient-divider"></div>
<div class="form-group highlight-group">
<label class="form-label">

View File

@@ -2,7 +2,6 @@
display: flex;
flex-direction: column;
min-width: 600px;
padding-top: 20px;
@media (max-width: 640px) {
min-width: auto;
@@ -15,7 +14,7 @@
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
@@ -300,18 +299,12 @@
}
}
.divider {
height: 1px;
background: linear-gradient(90deg, transparent, var(--border-color), transparent);
margin: 0.25rem 0;
}
.dialog-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 1rem 1.5rem;
padding: 1.25rem 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;

View File

@@ -45,6 +45,7 @@ export class LibraryShelfMenuService {
header: 'Edit Library',
modal: true,
closable: true,
styleClass: 'dynamic-dialog-minimal',
data: {
mode: 'edit',
libraryId: entity?.id

View File

@@ -73,7 +73,7 @@
</div>
</div>
<div class="divider"></div>
<div class="gradient-divider"></div>
<div class="form-group highlight-group">
<label class="form-label">
@@ -113,7 +113,7 @@
</div>
</div>
<div class="divider"></div>
<div class="gradient-divider"></div>
<div class="form-section">
<div class="form-group">
@@ -170,7 +170,7 @@
}
</div>
<div class="divider"></div>
<div class="gradient-divider"></div>
<div class="form-section">
<div class="form-group">

View File

@@ -8,7 +8,6 @@
border: 1px solid var(--border-color);
border-radius: 10px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
margin-top: 20px;
@media (max-width: 768px) {
width: 100%;
@@ -29,7 +28,7 @@
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem;
padding: 1.75rem 1.75rem 1.25rem 1.75rem;
background: var(--overlay-background);
border-bottom: 1px solid var(--border-color);
border-radius: 10px 10px 0 0;
@@ -254,7 +253,7 @@
width: 100%;
display: flex;
flex-direction: column;
padding: 1rem 1.5rem;
padding: 1rem 2rem;
flex: 1;
overflow-y: auto;

View File

@@ -83,6 +83,7 @@ export class LibraryCreatorComponent implements OnInit {
showHeader: false,
modal: true,
closable: true,
styleClass: 'dynamic-dialog-minimal',
contentStyle: {overflow: 'hidden'},
baseZIndex: 10
});

View File

@@ -3,7 +3,6 @@
max-width: 75rem;
display: flex;
flex-direction: column;
padding-top: 20px;
@media (max-width: 1280px) {
width: 100%;
@@ -16,8 +15,7 @@
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
border-bottom: none;
@@ -97,7 +95,7 @@
}
.magic-shelf-content {
padding: 1.5rem;
padding: 2rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-bottom: none;
@@ -221,7 +219,7 @@
justify-content: flex-end;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
padding: 1.25rem 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-radius: 0 0 10px 10px;

View File

@@ -112,7 +112,7 @@
</div>
@if (book?.metadata?.amazonRating || book?.metadata?.goodreadsRating || book?.metadata?.hardcoverRating || book?.metadata?.googleId) {
<div class="divider"></div>
<div class="gradient-divider"></div>
}
<div class="external-ratings">

View File

@@ -1,63 +1,203 @@
<form [formGroup]="emailProviderForm" (ngSubmit)="createEmailProvider()" class="space-y-6 p-6">
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Provider Name:</label>
<input type="text" pInputText formControlName="name" placeholder="Enter provider name..." class="w-full md:w-80"/>
</div>
@if (emailProviderForm.get('name')?.invalid && (emailProviderForm.get('name')?.dirty || emailProviderForm.get('name')?.touched)) {
<small class="text-red-500 pl-44">
Provider name is required.
</small>
}
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Host:</label>
<input type="text" pInputText formControlName="host" placeholder="Enter host..." class="w-full md:w-80"/>
</div>
@if (emailProviderForm.get('host')?.invalid && (emailProviderForm.get('host')?.dirty || emailProviderForm.get('host')?.touched)) {
<small class="text-red-500 pl-44">
Host is required.
</small>
}
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Port:</label>
<input type="number" pInputText formControlName="port" placeholder="Enter port..." class="w-full md:w-80"/>
</div>
@if (emailProviderForm.get('port')?.invalid && (emailProviderForm.get('port')?.dirty || emailProviderForm.get('port')?.touched)) {
<small class="text-red-500 pl-44">
Port is required and must be a number.
</small>
}
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Username:</label>
<input type="text" pInputText formControlName="username" placeholder="Enter username..." class="w-full md:w-80"/>
<div class="email-provider-creator">
<div class="panel-header">
<div class="header-icon-wrapper">
<i class="pi pi-envelope header-icon"></i>
</div>
<div class="header-text">
<h2 class="panel-title">Create Email Provider</h2>
<p class="panel-description">Configure a new email provider for sending notifications</p>
</div>
<p-button
icon="pi pi-times"
(click)="closeDialog()"
severity="secondary"
[text]="true"
[rounded]="true"
class="close-button"
pTooltip="Close"
tooltipPosition="left"/>
</div>
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Password:</label>
<input type="password" pInputText formControlName="password" placeholder="Enter password..." class="w-full md:w-80"/>
</div>
<form [formGroup]="emailProviderForm" (ngSubmit)="createEmailProvider()">
<div class="form-container">
<div class="form-group highlight-group">
<label for="providerName" class="form-label">
<i class="pi pi-tag label-icon"></i>
Provider Name
<span class="required-indicator">*</span>
</label>
<div class="input-wrapper">
<input
id="providerName"
type="text"
pInputText
formControlName="name"
class="input-full"
placeholder="e.g., Gmail SMTP, SendGrid"
[class.filled]="emailProviderForm.get('name')?.value"
autofocus
/>
@if (emailProviderForm.get('name')?.value && emailProviderForm.get('name')?.valid) {
<i class="pi pi-check-circle input-icon success"></i>
}
</div>
@if (emailProviderForm.get('name')?.invalid && (emailProviderForm.get('name')?.dirty || emailProviderForm.get('name')?.touched)) {
<small class="field-error">Provider name is required.</small>
}
</div>
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">From Address:</label>
<input type="text" pInputText formControlName="fromAddress" placeholder="Enter from address... " class="w-full md:w-80"/>
</div>
<div class="gradient-divider"></div>
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Auth:</label>
<p-checkbox formControlName="auth" [binary]="true"></p-checkbox>
</div>
<div class="form-group highlight-group">
<label for="host" class="form-label">
<i class="pi pi-server label-icon"></i>
Host
<span class="required-indicator">*</span>
</label>
<div class="input-wrapper">
<input
id="host"
type="text"
pInputText
formControlName="host"
class="input-full"
placeholder="e.g., smtp.gmail.com"
[class.filled]="emailProviderForm.get('host')?.value"
/>
@if (emailProviderForm.get('host')?.value && emailProviderForm.get('host')?.valid) {
<i class="pi pi-check-circle input-icon success"></i>
}
</div>
@if (emailProviderForm.get('host')?.invalid && (emailProviderForm.get('host')?.dirty || emailProviderForm.get('host')?.touched)) {
<small class="field-error">Host is required.</small>
}
</div>
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">StartTLS:</label>
<p-checkbox formControlName="startTls" [binary]="true"></p-checkbox>
</div>
<div class="form-group highlight-group">
<label for="port" class="form-label">
<i class="pi pi-hashtag label-icon"></i>
Port
<span class="required-indicator">*</span>
</label>
<div class="input-wrapper">
<input
id="port"
type="number"
pInputText
formControlName="port"
class="input-full"
placeholder="e.g., 587, 465"
[class.filled]="emailProviderForm.get('port')?.value"
/>
@if (emailProviderForm.get('port')?.value && emailProviderForm.get('port')?.valid) {
<i class="pi pi-check-circle input-icon success"></i>
}
</div>
@if (emailProviderForm.get('port')?.invalid && (emailProviderForm.get('port')?.dirty || emailProviderForm.get('port')?.touched)) {
<small class="field-error">Port is required and must be a number.</small>
}
</div>
<div class="flex justify-end">
<p-button label="Create Email Provider" [disabled]="emailProviderForm.invalid" type="submit">
</p-button>
</div>
<div class="gradient-divider"></div>
</form>
<div class="form-group highlight-group">
<label for="username" class="form-label">
<i class="pi pi-user label-icon"></i>
Username
</label>
<input
id="username"
type="text"
pInputText
formControlName="username"
class="input-full"
placeholder="Enter username"
/>
</div>
<div class="form-group highlight-group">
<label for="password" class="form-label">
<i class="pi pi-lock label-icon"></i>
Password
</label>
<input
id="password"
type="password"
pInputText
formControlName="password"
class="input-full"
placeholder="Enter password"
/>
</div>
<div class="form-group highlight-group">
<label for="fromAddress" class="form-label">
<i class="pi pi-at label-icon"></i>
From Address
</label>
<input
id="fromAddress"
type="text"
pInputText
formControlName="fromAddress"
class="input-full"
placeholder="e.g., noreply@example.com"
/>
</div>
<div class="gradient-divider"></div>
<div class="form-group highlight-group checkbox-group">
<div class="checkbox-row">
<p-checkbox formControlName="auth" [binary]="true" inputId="auth"></p-checkbox>
<label for="auth" class="checkbox-label">
<span class="checkbox-title">Enable Authentication</span>
<span class="checkbox-description">Require username and password for SMTP</span>
</label>
</div>
</div>
<div class="form-group highlight-group checkbox-group">
<div class="checkbox-row">
<p-checkbox formControlName="startTls" [binary]="true" inputId="startTls"></p-checkbox>
<label for="startTls" class="checkbox-label">
<span class="checkbox-title">Enable StartTLS</span>
<span class="checkbox-description">Use TLS encryption for secure connection</span>
</label>
</div>
</div>
</div>
<div class="dialog-footer">
<div class="validation-status">
@if (emailProviderForm.invalid) {
<div class="validation-message error">
<i class="pi pi-exclamation-circle"></i>
<span>Please fill in all required fields</span>
</div>
} @else {
<div class="validation-message success">
<i class="pi pi-check-circle"></i>
<span>Ready to create provider</span>
</div>
}
</div>
<div class="footer-actions">
<p-button
label="Cancel"
severity="secondary"
[outlined]="true"
(onClick)="closeDialog()"
type="button"
/>
<p-button
label="Create Provider"
icon="pi pi-plus"
severity="success"
type="submit"
[disabled]="emailProviderForm.invalid"
/>
</div>
</div>
</form>
</div>

View File

@@ -0,0 +1,365 @@
.email-provider-form {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.email-provider-creator {
display: flex;
flex-direction: column;
min-width: 600px;
@media (max-width: 640px) {
min-width: auto;
width: 100%;
}
}
.panel-header {
position: relative;
display: flex;
align-items: center;
gap: 1rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
.header-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: var(--primary-color);
border-radius: 10px;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
.header-icon {
font-size: 1.5rem;
color: var(--primary-contrast-color);
}
}
.header-text {
flex: 1;
.panel-title {
margin: 0 0 0.25rem 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--text-color);
}
.panel-description {
margin: 0;
font-size: 0.875rem;
color: var(--text-secondary-color);
}
}
.close-button {
position: absolute;
top: 0.75rem;
right: 0.75rem;
}
@media (max-width: 480px) {
padding: 1rem;
.header-icon-wrapper {
width: 40px;
height: 40px;
.header-icon {
font-size: 1.25rem;
}
}
.header-text {
.panel-title {
font-size: 1.125rem;
}
.panel-description {
font-size: 0.8rem;
}
}
.close-button {
top: 0.5rem;
right: 0.5rem;
}
}
}
.form-container {
display: flex;
flex-direction: column;
padding: 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
max-height: 60vh;
overflow-y: auto;
@media (max-width: 480px) {
padding: 1rem;
}
}
.form-row {
display: flex;
align-items: center;
gap: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
&.highlight-group {
padding: 0.875rem;
}
&.checkbox-group {
padding: 0.75rem 0.875rem;
}
@media (max-width: 480px) {
gap: 0.375rem;
margin-bottom: 1rem;
&.highlight-group {
padding: 0.625rem;
}
&.checkbox-group {
padding: 0.5rem 0.625rem;
}
}
}
.form-label {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.95rem;
font-weight: 600;
color: var(--text-color);
.label-icon {
color: var(--primary-color);
}
.required-indicator {
color: var(--p-red-500);
margin-left: 0.125rem;
}
@media (max-width: 480px) {
font-size: 0.875rem;
}
}
.form-input {
width: 100%;
max-width: 20rem;
}
.input-wrapper {
position: relative;
.input-full {
width: 100%;
padding-right: 2.5rem;
box-sizing: border-box;
}
.input-icon {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
font-size: 1rem;
pointer-events: none;
&.success {
color: var(--p-green-500);
}
}
}
.input-full {
width: 100%;
box-sizing: border-box;
}
.form-error {
color: #ef4444;
padding-left: 10rem;
font-size: 0.875rem;
}
.field-error {
color: #ef4444;
font-size: 0.875rem;
margin-top: 0.25rem;
display: flex;
align-items: center;
gap: 0.25rem;
&::before {
content: '';
font-size: 0.875rem;
}
}
.checkbox-row {
display: flex;
align-items: flex-start;
gap: 0.75rem;
.checkbox-label {
display: flex;
flex-direction: column;
gap: 0.125rem;
cursor: pointer;
flex: 1;
.checkbox-title {
font-size: 0.9375rem;
font-weight: 600;
color: var(--text-color);
}
.checkbox-description {
font-size: 0.8rem;
color: var(--text-secondary-color);
}
}
@media (max-width: 480px) {
gap: 0.5rem;
.checkbox-label {
.checkbox-title {
font-size: 0.875rem;
}
.checkbox-description {
font-size: 0.75rem;
}
}
}
}
.gradient-divider {
height: 1px;
background: linear-gradient(
to right,
transparent,
var(--border-color) 20%,
var(--border-color) 80%,
transparent
);
margin: 1.25rem 0;
@media (max-width: 480px) {
margin: 1rem 0;
}
}
.dialog-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 1.25rem 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
border-radius: 0 0 10px 10px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
.validation-status {
flex: 1;
min-width: 0;
}
.footer-actions {
display: flex;
gap: 0.75rem;
flex-shrink: 0;
}
@media (max-width: 640px) {
flex-direction: column;
align-items: stretch;
padding: 1rem;
gap: 0.75rem;
.validation-status {
width: 100%;
}
.footer-actions {
width: 100%;
justify-content: flex-end;
gap: 0.5rem;
}
}
}
.validation-message {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.5rem;
font-weight: 500;
border-radius: 6px;
font-size: 0.875rem;
width: fit-content;
max-width: 100%;
i {
font-size: 0.95rem;
}
&.error {
background: rgba(239, 68, 68, 0.1);
color: rgb(239, 68, 68);
border: 1px solid rgba(239, 68, 68, 0.3);
i {
color: rgb(239, 68, 68);
}
}
&.success {
background: rgba(34, 197, 94, 0.1);
color: rgb(34, 197, 94);
border: 1px solid rgba(34, 197, 94, 0.3);
i {
color: rgb(34, 197, 94);
}
}
@media (max-width: 640px) {
font-size: 0.8rem;
padding: 0.375rem 0.5rem;
width: 100%;
i {
font-size: 0.875rem;
}
}
}
.form-actions {
display: flex;
justify-content: flex-end;
}

View File

@@ -72,4 +72,8 @@ export class CreateEmailProviderDialogComponent implements OnInit {
}
});
}
closeDialog(): void {
this.ref.close();
}
}

View File

@@ -1,33 +1,121 @@
<form [formGroup]="emailRecipientForm" (ngSubmit)="createEmailRecipient()" class="space-y-6 p-6">
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Recipient Name:</label>
<input type="text" pInputText formControlName="name" placeholder="Enter recipient name..." class="w-full md:w-80"/>
</div>
@if (emailRecipientForm.get('name')?.invalid && (emailRecipientForm.get('name')?.dirty || emailRecipientForm.get('name')?.touched)) {
<small class="text-red-500 pl-44">
Recipient name is required.
</small>
}
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Email Address:</label>
<input type="email" pInputText formControlName="email" placeholder="Enter email address..." class="w-full md:w-80"/>
</div>
@if (emailRecipientForm.get('email')?.invalid && (emailRecipientForm.get('email')?.dirty || emailRecipientForm.get('email')?.touched)) {
<small class="text-red-500 pl-44">
Valid email is required.
</small>
}
<div class="flex items-center gap-4">
<label class="w-40 text-right font-medium">Default Recipient:</label>
<p-checkbox formControlName="defaultRecipient" [binary]="true"></p-checkbox>
<div class="email-recipient-creator">
<div class="panel-header">
<div class="header-icon-wrapper">
<i class="pi pi-user header-icon"></i>
</div>
<div class="header-text">
<h2 class="panel-title">Create Email Recipient</h2>
<p class="panel-description">Add a new recipient for email notifications</p>
</div>
<p-button
icon="pi pi-times"
(click)="closeDialog()"
severity="secondary"
[text]="true"
[rounded]="true"
class="close-button"
pTooltip="Close"
tooltipPosition="left"/>
</div>
<div class="flex justify-end">
<p-button label="Add Recipient" [disabled]="emailRecipientForm.invalid" type="submit">
</p-button>
</div>
<form [formGroup]="emailRecipientForm" (ngSubmit)="createEmailRecipient()">
<div class="form-container">
<div class="form-group highlight-group">
<label for="recipientName" class="form-label">
<i class="pi pi-user label-icon"></i>
Recipient Name
<span class="required-indicator">*</span>
</label>
<div class="input-wrapper">
<input
id="recipientName"
type="text"
pInputText
formControlName="name"
class="input-full"
placeholder="Enter recipient name..."
[class.filled]="emailRecipientForm.get('name')?.value"
autofocus
/>
@if (emailRecipientForm.get('name')?.value && emailRecipientForm.get('name')?.valid) {
<i class="pi pi-check-circle input-icon success"></i>
}
</div>
@if (emailRecipientForm.get('name')?.invalid && (emailRecipientForm.get('name')?.dirty || emailRecipientForm.get('name')?.touched)) {
<small class="field-error">Recipient name is required.</small>
}
</div>
</form>
<div class="gradient-divider"></div>
<div class="form-group highlight-group">
<label for="email" class="form-label">
<i class="pi pi-at label-icon"></i>
Email Address
<span class="required-indicator">*</span>
</label>
<div class="input-wrapper">
<input
id="email"
type="email"
pInputText
formControlName="email"
class="input-full"
placeholder="Enter email address..."
[class.filled]="emailRecipientForm.get('email')?.value"
/>
@if (emailRecipientForm.get('email')?.value && emailRecipientForm.get('email')?.valid) {
<i class="pi pi-check-circle input-icon success"></i>
}
</div>
@if (emailRecipientForm.get('email')?.invalid && (emailRecipientForm.get('email')?.dirty || emailRecipientForm.get('email')?.touched)) {
<small class="field-error">Valid email is required.</small>
}
</div>
<div class="gradient-divider"></div>
<div class="form-group highlight-group checkbox-group">
<div class="checkbox-row">
<p-checkbox formControlName="defaultRecipient" [binary]="true" inputId="defaultRecipient"></p-checkbox>
<label for="defaultRecipient" class="checkbox-label">
<span class="checkbox-title">Default Recipient</span>
<span class="checkbox-description">Set as default recipient for all notifications</span>
</label>
</div>
</div>
</div>
<div class="dialog-footer">
<div class="validation-status">
@if (emailRecipientForm.invalid) {
<div class="validation-message error">
<i class="pi pi-exclamation-circle"></i>
<span>Please fill in all required fields</span>
</div>
} @else {
<div class="validation-message success">
<i class="pi pi-check-circle"></i>
<span>Ready to add recipient</span>
</div>
}
</div>
<div class="footer-actions">
<p-button
label="Cancel"
severity="secondary"
[outlined]="true"
(onClick)="closeDialog()"
type="button"
/>
<p-button
label="Add Recipient"
icon="pi pi-plus"
severity="success"
type="submit"
[disabled]="emailRecipientForm.invalid"
/>
</div>
</div>
</form>
</div>

View File

@@ -0,0 +1,336 @@
.email-recipient-creator {
display: flex;
flex-direction: column;
min-width: 600px;
@media (max-width: 640px) {
min-width: auto;
width: 100%;
}
}
.panel-header {
position: relative;
display: flex;
align-items: center;
gap: 1rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
.header-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: var(--primary-color);
border-radius: 10px;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
.header-icon {
font-size: 1.5rem;
color: var(--primary-contrast-color);
}
}
.header-text {
flex: 1;
.panel-title {
margin: 0 0 0.25rem 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--text-color);
}
.panel-description {
margin: 0;
font-size: 0.875rem;
color: var(--text-secondary-color);
}
}
.close-button {
position: absolute;
top: 0.75rem;
right: 0.75rem;
}
@media (max-width: 480px) {
padding: 1rem;
.header-icon-wrapper {
width: 40px;
height: 40px;
.header-icon {
font-size: 1.25rem;
}
}
.header-text {
.panel-title {
font-size: 1.125rem;
}
.panel-description {
font-size: 0.8rem;
}
}
.close-button {
top: 0.5rem;
right: 0.5rem;
}
}
}
.form-container {
display: flex;
flex-direction: column;
padding: 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
max-height: 60vh;
overflow-y: auto;
@media (max-width: 480px) {
padding: 1rem;
}
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
&.highlight-group {
padding: 0.875rem;
}
&.checkbox-group {
padding: 0.75rem 0.875rem;
}
@media (max-width: 480px) {
gap: 0.375rem;
&.highlight-group {
padding: 0.625rem;
}
&.checkbox-group {
padding: 0.5rem 0.625rem;
}
}
}
.form-label {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.95rem;
font-weight: 600;
color: var(--text-color);
.label-icon {
color: var(--primary-color);
}
.required-indicator {
color: var(--p-red-500);
margin-left: 0.125rem;
}
@media (max-width: 480px) {
font-size: 0.875rem;
}
}
.input-wrapper {
position: relative;
.input-full {
width: 100%;
padding-right: 2.5rem;
box-sizing: border-box;
}
.input-icon {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
font-size: 1rem;
pointer-events: none;
&.success {
color: var(--p-green-500);
}
}
}
.input-full {
width: 100%;
box-sizing: border-box;
}
.field-error {
color: #ef4444;
font-size: 0.875rem;
margin-top: 0.25rem;
display: flex;
align-items: center;
gap: 0.25rem;
&::before {
content: '';
font-size: 0.875rem;
}
}
.checkbox-row {
display: flex;
align-items: flex-start;
gap: 0.75rem;
.checkbox-label {
display: flex;
flex-direction: column;
gap: 0.125rem;
cursor: pointer;
flex: 1;
.checkbox-title {
font-size: 0.9375rem;
font-weight: 600;
color: var(--text-color);
}
.checkbox-description {
font-size: 0.8rem;
color: var(--text-secondary-color);
}
}
@media (max-width: 480px) {
gap: 0.5rem;
.checkbox-label {
.checkbox-title {
font-size: 0.875rem;
}
.checkbox-description {
font-size: 0.75rem;
}
}
}
}
.gradient-divider {
height: 1px;
background: linear-gradient(
to right,
transparent,
var(--border-color) 20%,
var(--border-color) 80%,
transparent
);
margin: 1.25rem 0;
@media (max-width: 480px) {
margin: 1rem 0;
}
}
.dialog-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 1.25rem 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
border-radius: 0 0 10px 10px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
.validation-status {
flex: 1;
min-width: 0;
}
.footer-actions {
display: flex;
gap: 0.75rem;
flex-shrink: 0;
}
@media (max-width: 640px) {
flex-direction: column;
align-items: stretch;
padding: 1rem;
gap: 0.75rem;
.validation-status {
width: 100%;
}
.footer-actions {
width: 100%;
justify-content: flex-end;
gap: 0.5rem;
}
}
}
.validation-message {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.5rem;
font-weight: 500;
border-radius: 6px;
font-size: 0.875rem;
width: fit-content;
max-width: 100%;
i {
font-size: 0.95rem;
}
&.error {
background: rgba(239, 68, 68, 0.1);
color: rgb(239, 68, 68);
border: 1px solid rgba(239, 68, 68, 0.3);
i {
color: rgb(239, 68, 68);
}
}
&.success {
background: rgba(34, 197, 94, 0.1);
color: rgb(34, 197, 94);
border: 1px solid rgba(34, 197, 94, 0.3);
i {
color: rgb(34, 197, 94);
}
}
@media (max-width: 640px) {
font-size: 0.8rem;
padding: 0.375rem 0.5rem;
width: 100%;
i {
font-size: 0.875rem;
}
}
}

View File

@@ -6,6 +6,7 @@ import {Checkbox} from 'primeng/checkbox';
import {Button} from 'primeng/button';
import {InputText} from 'primeng/inputtext';
import {EmailV2RecipientService} from '../email-v2-recipient/email-v2-recipient.service';
import {Tooltip} from 'primeng/tooltip';
@Component({
selector: 'app-create-email-recipient-dialog',
@@ -13,7 +14,8 @@ import {EmailV2RecipientService} from '../email-v2-recipient/email-v2-recipient.
Checkbox,
ReactiveFormsModule,
Button,
InputText
InputText,
Tooltip
],
templateUrl: './create-email-recipient-dialog.component.html',
styleUrls: ['./create-email-recipient-dialog.component.scss']
@@ -33,6 +35,10 @@ export class CreateEmailRecipientDialogComponent {
});
}
closeDialog(): void {
this.ref.close();
}
createEmailRecipient(): void {
if (this.emailRecipientForm.invalid) {
this.messageService.add({

View File

@@ -128,7 +128,8 @@ export class EmailV2ProviderComponent implements OnInit {
header: 'Create Email Provider',
modal: true,
closable: true,
style: {position: 'absolute', top: '15%'},
showHeader: false,
styleClass: 'dynamic-dialog-minimal',
});
this.ref?.onClose.subscribe((result) => {
if (result) {

View File

@@ -21,7 +21,7 @@ import {CreateEmailRecipientDialogComponent} from '../create-email-recipient-dia
TableModule,
Tooltip,
FormsModule
],
],
templateUrl: './email-v2-recipient.component.html',
styleUrl: './email-v2-recipient.component.scss'
})
@@ -115,7 +115,8 @@ export class EmailV2RecipientComponent implements OnInit {
header: 'Add New Recipient',
modal: true,
closable: true,
style: {position: 'absolute', top: '15%'},
showHeader: false,
styleClass: 'dynamic-dialog-minimal',
});
this.ref?.onClose.subscribe((result) => {
if (result) {

View File

@@ -187,7 +187,7 @@
</div>
<p-dialog
header="Create User"
header="Create OPDS User"
[(visible)]="showCreateUserDialog"
[modal]="true"
styleClass="user-dialog"

View File

@@ -88,6 +88,8 @@
</div>
</div>
<div class="gradient-divider"></div>
<div class="form-section">
<h3 class="section-title">
<i class="pi pi-book"></i>
@@ -114,6 +116,8 @@
</div>
</div>
<div class="gradient-divider"></div>
<div class="form-section">
<h3 class="section-title">
<i class="pi pi-shield"></i>

View File

@@ -3,7 +3,6 @@
max-width: 650px;
display: flex;
flex-direction: column;
padding-top: 20px;
@media (max-width: 768px) {
width: 100%;
@@ -15,8 +14,7 @@
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
@@ -82,13 +80,12 @@
display: flex;
flex-direction: column;
gap: 2rem;
padding: 2rem 1.5rem 1.5rem 1.5rem;
padding: 2rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
max-height: 60vh;
overflow-y: auto;
max-height: 60vh;
&::-webkit-scrollbar {
width: 6px;
@@ -118,7 +115,7 @@
.form-section {
display: flex;
flex-direction: column;
gap: 1rem;
gap: 0.75rem;
.section-title {
display: flex;
@@ -129,7 +126,6 @@
font-weight: 600;
color: var(--text-color);
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--border-color);
i {
color: var(--primary-color);
@@ -287,7 +283,7 @@
justify-content: space-between;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
padding: 1.25rem 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
@@ -328,4 +324,3 @@
}
}
}

View File

@@ -110,6 +110,7 @@ export class UserManagementComponent implements OnInit, OnDestroy {
showHeader: false,
modal: true,
closable: true,
styleClass: 'dynamic-dialog-minimal',
});
this.ref?.onClose.subscribe((result) => {
if (result) {

View File

@@ -76,6 +76,8 @@
</div>
</div>
<div class="gradient-divider"></div>
<div class="form-section">
<h3 class="section-title">
<i class="pi pi-shield"></i>

View File

@@ -3,7 +3,6 @@
max-width: 650px;
display: flex;
flex-direction: column;
padding-top: 20px;
@media (max-width: 768px) {
width: 100%;
@@ -16,8 +15,7 @@
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem 3.5rem 1.25rem 1.25rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
@@ -97,8 +95,8 @@
.form-container {
display: flex;
flex-direction: column;
gap: 3.5rem;
padding: 2rem 1.5rem 1.5rem 1.5rem;
gap: 2rem;
padding: 2rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;

View File

@@ -5,7 +5,7 @@
Open Metadata Center
</h2>
<p class="settings-description">
Decide how you want to view book details inline as a full page or in a pop-up dialog.
Decide how you want to view book details - inline as a full page or in a pop-up dialog.
</p>
</div>

View File

@@ -38,10 +38,8 @@
</div>
</div>
@if (value === 'library') {
<div class="divider"></div>
<div class="gradient-divider"></div>
<div class="library-selection">
<div class="section-header">
<span class="section-title">
@@ -77,7 +75,7 @@
</div>
}
<div class="divider"></div>
<div class="gradient-divider"></div>
<div class="file-upload-section">
<div class="section-header">

View File

@@ -3,7 +3,6 @@
max-width: 700px;
display: flex;
flex-direction: column;
padding-top: 20px;
@media (max-width: 768px) {
width: 100%;
@@ -16,8 +15,7 @@
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
border-bottom: none;
@@ -100,7 +98,7 @@
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
padding: 1.75rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-radius: 0 0 10px 10px;
@@ -427,12 +425,6 @@
}
}
.divider {
height: 1px;
background: linear-gradient(90deg, transparent, var(--border-color), transparent);
margin: 0.25rem 0;
}
:host ::ng-deep .p-fileupload-content p-progressbar {
display: none !important;
}

View File

@@ -13,7 +13,7 @@
<div class="current-path">
<div class="path-label">
<i class="pi pi-map-marker"></i>
<span>Current:</span>
<span>Current Path:</span>
</div>
<div class="path-value">
{{ selectedProductName || '/' }}

View File

@@ -12,7 +12,6 @@
max-width: 550px;
display: flex;
flex-direction: column;
padding-top: 20px;
@media (max-width: 640px) {
width: 100%;
@@ -25,7 +24,7 @@
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem 1.25rem;
padding: 1.5rem 1.5rem 1.25rem 1.5rem;
border-radius: 10px 10px 0 0;
border: 1px solid var(--border-color);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
@@ -91,7 +90,7 @@
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem 1.5rem;
padding: 1rem 2rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
@@ -100,7 +99,7 @@
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.875rem;
padding: 0.625rem;
background: var(--overlay-background);
border-radius: 6px;
@@ -114,7 +113,7 @@
white-space: nowrap;
i {
color: var(--primary-color);
color: coral;
}
}
@@ -202,7 +201,7 @@
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
padding: 1rem 2rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;
@@ -504,7 +503,7 @@
justify-content: space-between;
align-items: center;
gap: 0.75rem;
padding: 1rem 1.5rem;
padding: 1.25rem 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
border-top: none;

View File

@@ -55,7 +55,7 @@
}
:host(.active-menuitem) .menu-item-container {
background-color: color-mix(in srgb, var(--p-primary-300), transparent 92%);
background-color: color-mix(in srgb, var(--p-primary-300), transparent 93%);
border-radius: 8px;
}

View File

@@ -13,14 +13,15 @@ export class DialogLauncherService {
dialogService = inject(DialogService);
open(options: { component: any; header: string; top?: string; width?: string; showHeader?: boolean }): DynamicDialogRef | null {
open(options: { component: any; header: string; top?: string; width?: string; showHeader?: boolean; styleClass?: string }): DynamicDialogRef | null {
const isMobile = window.innerWidth <= 768;
const {component, header, top, width, showHeader = true} = options;
const {component, header, top, width, showHeader = true, styleClass} = options;
return this.dialogService.open(component, {
header,
showHeader,
modal: true,
closable: true,
styleClass: styleClass,
style: {
position: 'absolute',
...(top ? {top} : {}),
@@ -50,6 +51,7 @@ export class DialogLauncherService {
this.open({
component: LibraryCreatorComponent,
header: 'Create New Library',
styleClass: 'dynamic-dialog-minimal',
showHeader: false
});
}
@@ -58,7 +60,8 @@ export class DialogLauncherService {
this.open({
component: BookUploaderComponent,
header: 'Book Uploader',
showHeader: false
showHeader: false,
styleClass: 'dynamic-dialog-minimal'
});
}
@@ -66,6 +69,7 @@ export class DialogLauncherService {
this.open({
component: UserProfileDialogComponent,
header: 'User Profile Information',
styleClass: 'dynamic-dialog-minimal',
showHeader: false
});
}
@@ -74,6 +78,7 @@ export class DialogLauncherService {
this.open({
component: MagicShelfComponent,
header: 'Magic Shelf Creator',
styleClass: 'dynamic-dialog-minimal',
showHeader: false
});
}

View File

@@ -3,3 +3,19 @@
.hidden {
display: none;
}
.dynamic-dialog-minimal.p-dialog {
border: none;
.p-dialog-content {
padding: 0;
}
}
.gradient-divider {
min-height: 1px;
height: 1px;
background: linear-gradient(90deg, transparent, var(--border-color), transparent);
margin: 0.25rem 0;
flex-shrink: 0;
}