WEB-492: Material Design 3 implementation: Login
This commit is contained in:
+6
-4
@@ -15,9 +15,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"selector-attribute-quotes": "always",
|
"selector-attribute-quotes": "always",
|
||||||
"max-nesting-depth": 5,
|
"max-nesting-depth": 7,
|
||||||
"selector-max-compound-selectors": 5,
|
"selector-max-compound-selectors": 8,
|
||||||
"selector-max-specificity": "1,3,2",
|
"selector-max-specificity": null,
|
||||||
|
"no-descending-specificity": null,
|
||||||
"declaration-no-important": null,
|
"declaration-no-important": null,
|
||||||
"at-rule-no-vendor-prefix": true,
|
"at-rule-no-vendor-prefix": true,
|
||||||
"at-rule-no-unknown": null,
|
"at-rule-no-unknown": null,
|
||||||
@@ -46,7 +47,8 @@
|
|||||||
"/^mat/",
|
"/^mat/",
|
||||||
"/^mifosx/",
|
"/^mifosx/",
|
||||||
"fa-icon",
|
"fa-icon",
|
||||||
"ng-template"
|
"ng-template",
|
||||||
|
"/^md-/"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
Generated
+223
-425
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,7 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
|
"@material/web": "^2.4.1",
|
||||||
"@ngx-translate/core": "^16.0.4",
|
"@ngx-translate/core": "^16.0.4",
|
||||||
"@ngx-translate/http-loader": "^16.0.1",
|
"@ngx-translate/http-loader": "^16.0.1",
|
||||||
"@swimlane/ngx-graph": "^11.0.0",
|
"@swimlane/ngx-graph": "^11.0.0",
|
||||||
|
|||||||
@@ -52,14 +52,19 @@ export default defineConfig({
|
|||||||
screenshot: 'only-on-failure',
|
screenshot: 'only-on-failure',
|
||||||
|
|
||||||
// Default navigation timeout (increased for CI and financial app)
|
// Default navigation timeout (increased for CI and financial app)
|
||||||
navigationTimeout: 60000,
|
navigationTimeout: 120000,
|
||||||
|
|
||||||
// Default action timeout
|
// Default action timeout
|
||||||
actionTimeout: 20000
|
actionTimeout: 30000,
|
||||||
|
|
||||||
|
// Configure backend URL for tests
|
||||||
|
extraHTTPHeaders: {
|
||||||
|
Accept: 'application/json'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Global test timeout (per test)
|
// Global test timeout (per test)
|
||||||
timeout: process.env.CI ? 90000 : 60000,
|
timeout: process.env.CI ? 180000 : 120000,
|
||||||
|
|
||||||
// Configure projects for different browsers
|
// Configure projects for different browsers
|
||||||
projects: [
|
projects: [
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ export class LoginPage extends BasePage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the username input field.
|
* Get the username input field.
|
||||||
* Uses codegen selector for the actual input.
|
* Uses formcontrolname attribute for reliable selection.
|
||||||
*/
|
*/
|
||||||
get usernameInput(): Locator {
|
get usernameInput(): Locator {
|
||||||
return this.page.getByRole('textbox', { name: 'Username' });
|
return this.page.locator('input[formcontrolname="username"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,10 +49,10 @@ export class LoginPage extends BasePage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the password input field.
|
* Get the password input field.
|
||||||
* Uses codegen selector for the actual input.
|
* Uses formcontrolname attribute for reliable selection.
|
||||||
*/
|
*/
|
||||||
get passwordInput(): Locator {
|
get passwordInput(): Locator {
|
||||||
return this.page.getByRole('textbox', { name: 'Password' });
|
return this.page.locator('input[formcontrolname="password"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,6 +63,10 @@ export class LoginPage extends BasePage {
|
|||||||
return this.page.getByRole('button', { name: 'Login' });
|
return this.page.getByRole('button', { name: 'Login' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public divLocator(className: string): Locator {
|
||||||
|
return this.page.locator(className);
|
||||||
|
}
|
||||||
|
|
||||||
// Additional helper locators for validation and assertions
|
// Additional helper locators for validation and assertions
|
||||||
private readonly locators = {
|
private readonly locators = {
|
||||||
errorMessage: 'mat-error',
|
errorMessage: 'mat-error',
|
||||||
@@ -105,20 +109,17 @@ export class LoginPage extends BasePage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform login with the given credentials.
|
* Perform login with the given credentials.
|
||||||
* Uses the exact interaction sequence from codegen.
|
|
||||||
*
|
*
|
||||||
* @param username - The username to enter
|
* @param username - The username to enter
|
||||||
* @param password - The password to enter
|
* @param password - The password to enter
|
||||||
*/
|
*/
|
||||||
async login(username: string, password: string): Promise<void> {
|
async login(username: string, password: string): Promise<void> {
|
||||||
// Click the username wrapper div (as per codegen)
|
|
||||||
await this.usernameDivWrapper.click();
|
|
||||||
// Fill the username input
|
// Fill the username input
|
||||||
|
await this.usernameInput.click();
|
||||||
await this.usernameInput.fill(username);
|
await this.usernameInput.fill(username);
|
||||||
|
|
||||||
// Click the password wrapper div (as per codegen)
|
|
||||||
await this.passwordDivWrapper.click();
|
|
||||||
// Fill the password input
|
// Fill the password input
|
||||||
|
await this.passwordInput.click();
|
||||||
await this.passwordInput.fill(password);
|
await this.passwordInput.fill(password);
|
||||||
|
|
||||||
// Click the login button
|
// Click the login button
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { LoginPage } from 'playwright/pages/login.page';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login Responsive Layout Tests
|
||||||
|
*
|
||||||
|
* This test suite validates that the login page displays correctly
|
||||||
|
* and fills the viewport at different screen sizes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const VIEWPORTS = {
|
||||||
|
mobile: { width: 375, height: 667 }, // iPhone SE
|
||||||
|
mobileLandscape: { width: 667, height: 375 },
|
||||||
|
tablet: { width: 768, height: 1024 }, // iPad
|
||||||
|
desktop: { width: 1280, height: 720 }, // Desktop
|
||||||
|
largeDesktop: { width: 1920, height: 1080 } // Full HD
|
||||||
|
};
|
||||||
|
|
||||||
|
test.describe('Login Page - Responsive Layout', () => {
|
||||||
|
let loginPage: LoginPage;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
loginPage = new LoginPage(page);
|
||||||
|
await loginPage.navigate();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fill viewport on mobile portrait (375x667)', async ({ page }) => {
|
||||||
|
await page.setViewportSize(VIEWPORTS.mobile);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
// Check login wrapper fills viewport
|
||||||
|
const loginWrapper = loginPage.divLocator('.login-wrapper');
|
||||||
|
await expect(loginWrapper).toBeVisible();
|
||||||
|
|
||||||
|
const wrapperBox = await loginWrapper.boundingBox();
|
||||||
|
expect(wrapperBox?.height).toBeGreaterThanOrEqual(VIEWPORTS.mobile.height - 10);
|
||||||
|
|
||||||
|
// Check login panel exists and is visible
|
||||||
|
const loginPanel = loginPage.divLocator('.login-panel');
|
||||||
|
await expect(loginPanel).toBeVisible();
|
||||||
|
|
||||||
|
// Check all essential elements are visible
|
||||||
|
await expect(loginPage.divLocator('.logo-section')).toBeVisible();
|
||||||
|
await expect(loginPage.divLocator('.form-section')).toBeVisible();
|
||||||
|
await expect(loginPage.divLocator('.login-footer')).toBeVisible();
|
||||||
|
|
||||||
|
// Verify no horizontal scroll
|
||||||
|
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
|
||||||
|
expect(bodyWidth).toBeLessThanOrEqual(VIEWPORTS.mobile.width + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fill viewport on tablet (768x1024)', async ({ page }) => {
|
||||||
|
await page.setViewportSize(VIEWPORTS.tablet);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
const loginWrapper = loginPage.divLocator('.login-wrapper');
|
||||||
|
await expect(loginWrapper).toBeVisible();
|
||||||
|
|
||||||
|
const wrapperBox = await loginWrapper.boundingBox();
|
||||||
|
expect(wrapperBox?.height).toBeGreaterThanOrEqual(VIEWPORTS.tablet.height - 10);
|
||||||
|
|
||||||
|
// On tablet, hero panel should be hidden
|
||||||
|
const heroPanel = loginPage.divLocator('.hero-panel');
|
||||||
|
await expect(heroPanel).not.toBeVisible();
|
||||||
|
|
||||||
|
// Login panel should be visible
|
||||||
|
const loginPanel = loginPage.divLocator('.login-panel');
|
||||||
|
await expect(loginPanel).toBeVisible();
|
||||||
|
|
||||||
|
// All sections should be visible
|
||||||
|
await expect(loginPage.divLocator('.logo-section')).toBeVisible();
|
||||||
|
await expect(loginPage.divLocator('.form-section')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fill viewport on desktop (1280x720)', async ({ page }) => {
|
||||||
|
await page.setViewportSize(VIEWPORTS.desktop);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
const loginWrapper = loginPage.divLocator('.login-wrapper');
|
||||||
|
await expect(loginWrapper).toBeVisible();
|
||||||
|
|
||||||
|
const wrapperBox = await loginWrapper.boundingBox();
|
||||||
|
expect(wrapperBox?.height).toBeGreaterThanOrEqual(VIEWPORTS.desktop.height - 10);
|
||||||
|
|
||||||
|
// On desktop, hero panel should be visible
|
||||||
|
const heroPanel = loginPage.divLocator('.hero-panel');
|
||||||
|
await expect(heroPanel).toBeVisible();
|
||||||
|
|
||||||
|
// Login panel should be visible
|
||||||
|
const loginPanel = loginPage.divLocator('.login-panel');
|
||||||
|
await expect(loginPanel).toBeVisible();
|
||||||
|
|
||||||
|
// Login card should be centered
|
||||||
|
const loginCard = loginPage.divLocator('.login-card');
|
||||||
|
await expect(loginCard).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should display all content without scrolling on desktop', async ({ page }) => {
|
||||||
|
await page.setViewportSize(VIEWPORTS.desktop);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
// Check if login card content is fully visible
|
||||||
|
const loginCard = loginPage.divLocator('.login-card');
|
||||||
|
const cardBox = await loginCard.boundingBox();
|
||||||
|
|
||||||
|
// Logo should be visible
|
||||||
|
const logo = loginPage.divLocator('.logo-image');
|
||||||
|
const logoBox = await logo.boundingBox();
|
||||||
|
expect(logoBox).toBeTruthy();
|
||||||
|
|
||||||
|
// Username input should be visible
|
||||||
|
const usernameInput = page.locator('input[formcontrolname="username"]');
|
||||||
|
const usernameBox = await usernameInput.boundingBox();
|
||||||
|
expect(usernameBox).toBeTruthy();
|
||||||
|
|
||||||
|
// Password input should be visible
|
||||||
|
const passwordInput = page.locator('input[formcontrolname="password"]');
|
||||||
|
const passwordBox = await passwordInput.boundingBox();
|
||||||
|
expect(passwordBox).toBeTruthy();
|
||||||
|
|
||||||
|
// Login button should be visible
|
||||||
|
const loginButton = page.locator('mifosx-m3-button[type="submit"]');
|
||||||
|
const buttonBox = await loginButton.boundingBox();
|
||||||
|
expect(buttonBox).toBeTruthy();
|
||||||
|
|
||||||
|
// Resources section should be visible
|
||||||
|
const resourcesSection = page.locator('.resources-section');
|
||||||
|
const resourcesBox = await resourcesSection.boundingBox();
|
||||||
|
expect(resourcesBox).toBeTruthy();
|
||||||
|
|
||||||
|
// Footer should be visible
|
||||||
|
const footer = loginPage.divLocator('.login-footer');
|
||||||
|
const footerBox = await footer.boundingBox();
|
||||||
|
expect(footerBox).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle small mobile screens (375x667) with all content visible', async ({ page }) => {
|
||||||
|
await page.setViewportSize(VIEWPORTS.mobile);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
// Scroll to bottom to ensure footer is accessible
|
||||||
|
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||||
|
|
||||||
|
// Footer should be visible after scroll
|
||||||
|
const footer = loginPage.divLocator('.login-footer');
|
||||||
|
await expect(footer).toBeVisible();
|
||||||
|
|
||||||
|
// Scroll back to top
|
||||||
|
await page.evaluate(() => window.scrollTo(0, 0));
|
||||||
|
|
||||||
|
// Logo and form should be visible at top
|
||||||
|
await expect(loginPage.divLocator('.logo-section')).toBeVisible();
|
||||||
|
await expect(loginPage.divLocator('.form-section')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should maintain proper spacing between elements on all viewports', async ({ page }) => {
|
||||||
|
const viewports = [
|
||||||
|
VIEWPORTS.mobile,
|
||||||
|
VIEWPORTS.tablet,
|
||||||
|
VIEWPORTS.desktop
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const viewport of viewports) {
|
||||||
|
await page.setViewportSize(viewport);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
const logo = loginPage.divLocator('.logo-section');
|
||||||
|
const form = loginPage.divLocator('.form-section');
|
||||||
|
|
||||||
|
// All sections should be visible
|
||||||
|
await expect(logo).toBeVisible();
|
||||||
|
await expect(form).toBeVisible();
|
||||||
|
|
||||||
|
if (viewport.width > 1024) {
|
||||||
|
const resources = loginPage.divLocator('.resources-section');
|
||||||
|
await expect(resources).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get bounding boxes
|
||||||
|
const logoBox = await logo.boundingBox();
|
||||||
|
const formBox = await form.boundingBox();
|
||||||
|
|
||||||
|
// Form should be below logo
|
||||||
|
if (logoBox && formBox) {
|
||||||
|
expect(formBox.y).toBeGreaterThan(logoBox.y + logoBox.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have horizontal overflow on any viewport', async ({ page }) => {
|
||||||
|
const viewports = [
|
||||||
|
VIEWPORTS.mobile,
|
||||||
|
VIEWPORTS.tablet,
|
||||||
|
VIEWPORTS.desktop
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const viewport of viewports) {
|
||||||
|
await page.setViewportSize(viewport);
|
||||||
|
await page.goto('http://localhost:4200/login');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
// Check no horizontal scroll
|
||||||
|
const hasHorizontalScroll = await page.evaluate(() => {
|
||||||
|
return document.body.scrollWidth > window.innerWidth;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(hasHorizontalScroll).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should center login card vertically on desktop', async ({ page }) => {
|
||||||
|
await page.setViewportSize(VIEWPORTS.desktop);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
const loginCard = page.locator('.login-card');
|
||||||
|
const cardBox = await loginCard.boundingBox();
|
||||||
|
|
||||||
|
const viewportHeight = VIEWPORTS.desktop.height;
|
||||||
|
|
||||||
|
// Card should be roughly centered (with some tolerance)
|
||||||
|
if (cardBox) {
|
||||||
|
const cardCenter = cardBox.y + cardBox.height / 2;
|
||||||
|
const viewportCenter = viewportHeight / 2;
|
||||||
|
|
||||||
|
// Allow 100px tolerance for header/footer
|
||||||
|
expect(Math.abs(cardCenter - viewportCenter)).toBeLessThan(150);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show all form fields on mobile', async ({ page }) => {
|
||||||
|
await page.setViewportSize(VIEWPORTS.mobile);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
// Username field
|
||||||
|
const usernameField = page.locator('mat-form-field').filter({ hasText: 'Username' });
|
||||||
|
await expect(usernameField).toBeVisible();
|
||||||
|
|
||||||
|
// Password field
|
||||||
|
const passwordField = page.locator('mat-form-field').filter({ hasText: 'Password' });
|
||||||
|
await expect(passwordField).toBeVisible();
|
||||||
|
|
||||||
|
// Login button
|
||||||
|
const loginButton = page.locator('mifosx-m3-button[type="submit"]');
|
||||||
|
await expect(loginButton).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show logo on all screen sizes', async ({ page }) => {
|
||||||
|
const viewports = [
|
||||||
|
VIEWPORTS.mobile,
|
||||||
|
VIEWPORTS.tablet,
|
||||||
|
VIEWPORTS.desktop
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const viewport of viewports) {
|
||||||
|
await page.setViewportSize(viewport);
|
||||||
|
// Verify the login form is visible
|
||||||
|
await loginPage.assertOnLoginPage();
|
||||||
|
|
||||||
|
const logo = page.locator('.logo-image');
|
||||||
|
await expect(logo).toBeVisible();
|
||||||
|
|
||||||
|
// Logo should have appropriate size
|
||||||
|
const logoBox = await logo.boundingBox();
|
||||||
|
expect(logoBox?.width).toBeGreaterThan(0);
|
||||||
|
expect(logoBox?.height).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Material Design 3 Toolbar Design Tokens
|
||||||
|
*
|
||||||
|
* This file contains all the design tokens for the toolbar component
|
||||||
|
* following Material Design 3 guidelines.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
// Surface Colors - Light Theme
|
||||||
|
--toolbar-surface: var(--md-sys-color-surface-container, #f3f3f3);
|
||||||
|
--toolbar-on-surface: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
--toolbar-on-surface-variant: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
--toolbar-primary: var(--md-sys-color-primary, #1074b9);
|
||||||
|
--toolbar-on-primary: var(--md-sys-color-on-primary, #fff);
|
||||||
|
--toolbar-primary-container: var(--md-sys-color-primary-container, #d1e4ff);
|
||||||
|
--toolbar-on-primary-container: var(--md-sys-color-on-primary-container, #001d35);
|
||||||
|
--toolbar-surface-container-high: var(--md-sys-color-surface-container-high, #e8e8eb);
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
--toolbar-height: 64px;
|
||||||
|
--toolbar-height-mobile: 56px;
|
||||||
|
--toolbar-padding-inline: 16px;
|
||||||
|
--toolbar-padding-inline-mobile: 8px;
|
||||||
|
--toolbar-gap: 8px;
|
||||||
|
--toolbar-gap-small: 4px;
|
||||||
|
|
||||||
|
// Navigation Items
|
||||||
|
--toolbar-nav-item-padding: 12px 16px;
|
||||||
|
--toolbar-nav-item-padding-mobile: 8px 12px;
|
||||||
|
--toolbar-nav-item-border-radius: 20px;
|
||||||
|
--toolbar-nav-item-gap: 8px;
|
||||||
|
--toolbar-nav-item-min-height: 40px;
|
||||||
|
|
||||||
|
// Typography
|
||||||
|
--toolbar-font-family: 'Roboto', system-ui, sans-serif;
|
||||||
|
--toolbar-font-size: 14px;
|
||||||
|
--toolbar-font-weight: 500;
|
||||||
|
--toolbar-font-weight-active: 600;
|
||||||
|
--toolbar-line-height: 20px;
|
||||||
|
--toolbar-letter-spacing: 0.1px;
|
||||||
|
|
||||||
|
// Icon sizing
|
||||||
|
--toolbar-icon-size: 20px;
|
||||||
|
--toolbar-icon-button-size: 48px;
|
||||||
|
|
||||||
|
// State Layers (MD3 standard opacities)
|
||||||
|
--toolbar-state-hover-opacity: 0.08;
|
||||||
|
--toolbar-state-focus-opacity: 0.12;
|
||||||
|
--toolbar-state-pressed-opacity: 0.12;
|
||||||
|
--toolbar-state-active-opacity: 0.16;
|
||||||
|
|
||||||
|
// Elevation (MD3 Level 2)
|
||||||
|
--toolbar-elevation: 0px 2px 4px rgb(0 0 0 / 4%), 0px 1px 2px rgb(0 0 0 / 6%);
|
||||||
|
--toolbar-elevation-scrolled: 0px 4px 8px rgb(0 0 0 / 6%), 0px 2px 4px rgb(0 0 0 / 8%);
|
||||||
|
|
||||||
|
// Motion (MD3 easing curves)
|
||||||
|
--toolbar-easing-standard: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--toolbar-easing-emphasized: cubic-bezier(0.2, 0, 0, 1);
|
||||||
|
--toolbar-duration-short: 200ms;
|
||||||
|
--toolbar-duration-medium: 250ms;
|
||||||
|
|
||||||
|
// Search Component
|
||||||
|
--toolbar-search-background: var(--md-sys-color-surface-container-high, #e8e8eb);
|
||||||
|
--toolbar-search-border-radius: 28px;
|
||||||
|
--toolbar-search-padding: 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
// Surface Colors - Dark Theme
|
||||||
|
--toolbar-surface: var(--md-sys-color-surface-container, #2c2f36);
|
||||||
|
--toolbar-on-surface: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
--toolbar-on-surface-variant: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
--toolbar-primary: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
--toolbar-primary-container: var(--md-sys-color-primary-container, #003a6b);
|
||||||
|
--toolbar-on-primary-container: var(--md-sys-color-on-primary-container, #d1e4ff);
|
||||||
|
--toolbar-surface-container-high: var(--md-sys-color-surface-container-high, #3d4047);
|
||||||
|
|
||||||
|
// Elevation (MD3 Level 2 - Dark theme has stronger shadows)
|
||||||
|
--toolbar-elevation: 0px 2px 4px rgb(0 0 0 / 12%), 0px 1px 2px rgb(0 0 0 / 16%);
|
||||||
|
--toolbar-elevation-scrolled: 0px 4px 8px rgb(0 0 0 / 16%), 0px 2px 4px rgb(0 0 0 / 20%);
|
||||||
|
|
||||||
|
// Search Component
|
||||||
|
--toolbar-search-background: var(--md-sys-color-surface-container-high, #3d4047);
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSidenav } from '@angular/material/sidenav';
|
import { MatSidenav } from '@angular/material/sidenav';
|
||||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||||
import { Router, RouterLink } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
/** rxjs Imports */
|
/** rxjs Imports */
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
@@ -31,7 +31,7 @@ import { ConfigurationWizardService } from '../../../configuration-wizard/config
|
|||||||
import { ConfigurationWizardComponent } from '../../../configuration-wizard/configuration-wizard.component';
|
import { ConfigurationWizardComponent } from '../../../configuration-wizard/configuration-wizard.component';
|
||||||
import { NotificationsTrayComponent } from 'app/shared/notifications-tray/notifications-tray.component';
|
import { NotificationsTrayComponent } from 'app/shared/notifications-tray/notifications-tray.component';
|
||||||
import { MatToolbar } from '@angular/material/toolbar';
|
import { MatToolbar } from '@angular/material/toolbar';
|
||||||
import { MatIconButton, MatButton } from '@angular/material/button';
|
import { MatIconButton } from '@angular/material/button';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { MatMenuTrigger, MatMenu, MatMenuItem } from '@angular/material/menu';
|
import { MatMenuTrigger, MatMenu, MatMenuItem } from '@angular/material/menu';
|
||||||
@@ -43,8 +43,6 @@ import { ThemeToggleComponent } from '../../../shared/theme-toggle/theme-toggle.
|
|||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
import { DocumentationLinksService } from 'app/shared/services/documentation-links.service';
|
import { DocumentationLinksService } from 'app/shared/services/documentation-links.service';
|
||||||
|
|
||||||
import { environment } from '../../../../environments/environment';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toolbar component.
|
* Toolbar component.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,39 +2,47 @@
|
|||||||
@if (!oauthEnabled) {
|
@if (!oauthEnabled) {
|
||||||
<div>
|
<div>
|
||||||
<form class="layout-column" [formGroup]="loginForm" (ngSubmit)="login()" id="login-form">
|
<form class="layout-column" [formGroup]="loginForm" (ngSubmit)="login()" id="login-form">
|
||||||
<mat-form-field class="login-input flex-align-center">
|
<mat-form-field class="login-field flex-align-center">
|
||||||
<span matPrefix class="m-r-10">
|
|
||||||
<fa-icon icon="user-circle" size="lg"></fa-icon>
|
|
||||||
</span>
|
|
||||||
<mat-label>{{ 'labels.inputs.Username' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Username' | translate }}</mat-label>
|
||||||
<input matInput type="text" autocomplete="off" formControlName="username" />
|
<input
|
||||||
|
matInput
|
||||||
|
type="text"
|
||||||
|
autocomplete="username"
|
||||||
|
formControlName="username"
|
||||||
|
[placeholder]="'labels.placeholders.Enter your username' | translate"
|
||||||
|
/>
|
||||||
|
<mifosx-m3-icon matIconPrefix name="account_circle"></mifosx-m3-icon>
|
||||||
@if (loginForm.controls.username.hasError('required')) {
|
@if (loginForm.controls.username.hasError('required')) {
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{ 'labels.inputs.Username' | translate }} <strong>{{ 'labels.commons.is required' | translate }}</strong>
|
{{ 'labels.inputs.Username' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.is required' | translate }}</strong>
|
||||||
</mat-error>
|
</mat-error>
|
||||||
}
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="login-input flex-align-center">
|
|
||||||
@if (!loginForm.controls.password.value) {
|
<!-- Password Field -->
|
||||||
<span matPrefix class="m-r-10">
|
<mat-form-field class="login-field" appearance="fill">
|
||||||
<fa-icon icon="lock" size="lg"></fa-icon>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
<mat-label>{{ 'labels.inputs.Password' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Password' | translate }}</mat-label>
|
||||||
<input matInput type="{{ passwordInputType }}" formControlName="password" />
|
<input
|
||||||
|
matInput
|
||||||
|
[type]="passwordInputType"
|
||||||
|
autocomplete="current-password"
|
||||||
|
formControlName="password"
|
||||||
|
[placeholder]="'labels.placeholders.Enter your password' | translate"
|
||||||
|
/>
|
||||||
|
<mifosx-m3-icon matIconPrefix name="lock"></mifosx-m3-icon>
|
||||||
@if (loginForm.controls.password.value && !loading) {
|
@if (loginForm.controls.password.value && !loading) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
matSuffix
|
matSuffix
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(mousedown)="togglePasswordVisibility()"
|
(click)="togglePasswordVisibility()"
|
||||||
(mouseup)="togglePasswordVisibility()"
|
[attr.aria-label]="passwordInputType === 'password' ? 'Show password' : 'Hide password'"
|
||||||
>
|
>
|
||||||
@if (passwordInputType === 'password') {
|
@if (passwordInputType === 'password') {
|
||||||
<fa-icon icon="eye"></fa-icon>
|
<mifosx-m3-icon name="visibility"></mifosx-m3-icon>
|
||||||
}
|
} @else {
|
||||||
@if (passwordInputType === 'text') {
|
<mifosx-m3-icon name="visibility_off"></mifosx-m3-icon>
|
||||||
<fa-icon icon="eye-slash"></fa-icon>
|
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@@ -45,20 +53,32 @@
|
|||||||
</mat-error>
|
</mat-error>
|
||||||
}
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Loading Progress -->
|
||||||
@if (loading) {
|
@if (loading) {
|
||||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar mode="indeterminate" class="login-progress"></mat-progress-bar>
|
||||||
}
|
}
|
||||||
<button
|
|
||||||
mat-raised-button
|
<!-- Submit Button -->
|
||||||
color="primary"
|
<mifosx-m3-button
|
||||||
class="login-button-first flex-align-center"
|
variant="filled"
|
||||||
[disabled]="!loginForm.valid"
|
type="submit"
|
||||||
|
class="login-submit-button"
|
||||||
|
[disabled]="!loginForm.valid || loading"
|
||||||
|
[fullWidth]="true"
|
||||||
|
[label]="(loading ? 'labels.buttons.Signing in...' : 'labels.buttons.Login') | translate"
|
||||||
>
|
>
|
||||||
{{ 'labels.buttons.Login' | translate }}
|
|
||||||
@if (loading) {
|
@if (loading) {
|
||||||
<mat-spinner [diameter]="20"></mat-spinner>
|
<mat-spinner [diameter]="20" class="button-spinner"></mat-spinner>
|
||||||
}
|
}
|
||||||
</button>
|
</mifosx-m3-button>
|
||||||
|
|
||||||
|
<!-- Forgot Password Link -->
|
||||||
|
<div class="login-actions">
|
||||||
|
<a href="#" class="forgot-password-link">
|
||||||
|
{{ 'labels.links.Forgot Password?' | translate }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,476 @@
|
|||||||
|
/**
|
||||||
|
* Modern Material Design 3 Login Form Styles
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ===== Main Form Container =====
|
||||||
|
.modern-login-form {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
#login-form {
|
#login-form {
|
||||||
.login-input {
|
display: flex;
|
||||||
width: 14rem;
|
flex-direction: column;
|
||||||
margin-bottom: 0.1rem;
|
gap: 0.25rem;
|
||||||
}
|
animation: slide-in 0.4s ease-out;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
[matPrefix] fa-icon {
|
// ===== Tenant Selector Styles =====
|
||||||
font-size: 16px;
|
::ng-deep .tenant-section {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
top: 50%;
|
|
||||||
left: 190px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button-first {
|
mifosx-tenant-selector {
|
||||||
width: 14rem;
|
display: block;
|
||||||
margin-top: 0.8rem;
|
width: 100%;
|
||||||
background-color: #1976d2;
|
|
||||||
color: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-spinner {
|
#tenant-selector {
|
||||||
float: right;
|
width: 100%;
|
||||||
margin: 0.5rem 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
// Apply same styling as login fields
|
||||||
|
.mat-mdc-text-field-wrapper {
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-focus-overlay {
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
background-color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
opacity: 0.04;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mat-focused {
|
||||||
|
.mat-mdc-form-field-focus-overlay {
|
||||||
|
opacity: 0.12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-line-ripple::after {
|
||||||
|
border-bottom-color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-select {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-icon-prefix {
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
|
||||||
|
fa-icon {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenantselector {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .mat-mdc-form-field-infix {
|
// ===== Form Fields =====
|
||||||
width: 144px !important;
|
.login-field {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
// Field container
|
||||||
|
.mat-mdc-text-field-wrapper {
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus overlay
|
||||||
|
.mat-mdc-form-field-focus-overlay {
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
background-color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
opacity: 0.04;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
&.mat-focused {
|
||||||
|
.mat-mdc-form-field-focus-overlay {
|
||||||
|
opacity: 0.12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom indicator line
|
||||||
|
.mdc-line-ripple::after {
|
||||||
|
border-bottom-color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input field
|
||||||
|
.mat-mdc-input-element {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
padding: 0.875rem 0.75rem;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix icon
|
||||||
|
.mat-mdc-form-field-icon-prefix {
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
|
||||||
|
fa-icon {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suffix button (password toggle)
|
||||||
|
.mat-mdc-form-field-icon-suffix {
|
||||||
|
button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
background: #1074b914;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error state
|
||||||
|
&.mat-form-field-invalid {
|
||||||
|
.mdc-line-ripple::after {
|
||||||
|
border-bottom-color: var(--md-sys-color-error, #ba1a1a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-icon-prefix fa-icon {
|
||||||
|
color: var(--md-sys-color-error, #ba1a1a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
.mat-mdc-form-field-error {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
color: var(--md-sys-color-error, #ba1a1a);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-margin {
|
// ===== Loading Progress =====
|
||||||
margin-top: 8rem;
|
.login-progress {
|
||||||
margin-bottom: 8rem;
|
height: 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: -0.5rem 0 0.5rem;
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
.mdc-linear-progress__bar-inner {
|
||||||
|
border-color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Submit Button =====
|
||||||
|
.login-submit-button {
|
||||||
|
min-height: 48px;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
|
||||||
|
::ng-deep md-filled-button {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-spinner {
|
||||||
|
margin-left: 0.75rem;
|
||||||
|
|
||||||
|
::ng-deep circle {
|
||||||
|
stroke: currentcolor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Login Actions =====
|
||||||
|
.login-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password-link {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #1074b914;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 2px solid var(--md-sys-color-primary, #1074b9);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== OIDC Login =====
|
||||||
|
.oidc-login-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oidc-login-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oidc-description {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oidc-logged-in {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 2rem;
|
||||||
|
background: var(--md-sys-color-primary-container, #d1e4ff);
|
||||||
|
border-radius: 16px;
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--md-sys-color-on-primary-container, #001d36);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.oidc-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
mifosx-m3-button {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.protected-data {
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--md-sys-color-surface-container, #f3f3f3);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--md-sys-color-outline-variant, #c4c6d0);
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Dark Theme Support =====
|
||||||
|
:host-context(.dark-theme) {
|
||||||
|
// Tenant selector dark theme
|
||||||
|
::ng-deep .tenant-section {
|
||||||
|
mifosx-tenant-selector {
|
||||||
|
#tenant-selector {
|
||||||
|
.mat-mdc-select {
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-icon-prefix {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field {
|
||||||
|
::ng-deep {
|
||||||
|
.mat-mdc-input-element {
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-icon-prefix {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-icon-suffix button {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
background: #5ba2ec1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password-link {
|
||||||
|
color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #5ba2ec1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.oidc-description {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-message {
|
||||||
|
background: var(--md-sys-color-primary-container, #003a6b);
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: var(--md-sys-color-on-primary-container, #d1e4ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.protected-data {
|
||||||
|
background: var(--md-sys-color-surface-container, #2c2f36);
|
||||||
|
border-color: var(--md-sys-color-outline-variant, #44474e);
|
||||||
|
|
||||||
|
pre {
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Responsive Design =====
|
||||||
|
@media (width <= 768px) {
|
||||||
|
#login-form {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-submit-button {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-actions {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 480px) {
|
||||||
|
#login-form {
|
||||||
|
gap: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
::ng-deep {
|
||||||
|
.mat-mdc-input-element {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
padding: 0.65rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-icon-prefix {
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-submit-button {
|
||||||
|
min-height: 42px;
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-actions {
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password-link {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
padding: 0.35rem 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oidc-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
mifosx-m3-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Animations =====
|
||||||
|
@keyframes slide-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import { finalize } from 'rxjs/operators';
|
|||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { AuthenticationService } from '../../core/authentication/authentication.service';
|
import { AuthenticationService } from '../../core/authentication/authentication.service';
|
||||||
import { MatPrefix } from '@angular/material/form-field';
|
import { MatPrefix } from '@angular/material/form-field';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { M3IconComponent } from '../../shared/m3-ui/m3-icon/m3-icon.component';
|
||||||
|
import { M3ButtonComponent } from '../../shared/m3-ui/m3-button/m3-button.component';
|
||||||
import { MatProgressBar } from '@angular/material/progress-bar';
|
import { MatProgressBar } from '@angular/material/progress-bar';
|
||||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
@@ -25,7 +26,8 @@ import { environment } from '../../../environments/environment';
|
|||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
MatPrefix,
|
MatPrefix,
|
||||||
FaIconComponent,
|
M3IconComponent,
|
||||||
|
M3ButtonComponent,
|
||||||
MatProgressBar,
|
MatProgressBar,
|
||||||
MatProgressSpinner
|
MatProgressSpinner
|
||||||
]
|
]
|
||||||
|
|||||||
+157
-126
@@ -1,148 +1,179 @@
|
|||||||
<!-- The viewport is divided into two parts having max-width 70% and 30% -->
|
<!-- Modern Material Design 3 Login Layout -->
|
||||||
<div class="layout-row">
|
<div class="login-wrapper">
|
||||||
<!--
|
<!-- Left Panel - Hero Section (Hidden on mobile) -->
|
||||||
Content in 70%: (TODO: Allow customization by organization)
|
<div class="hero-panel hide-lt-md">
|
||||||
Cover Image
|
<div class="hero-overlay"></div>
|
||||||
Organization/Application Name
|
<div class="hero-content">
|
||||||
Organization/Application Description
|
<div class="hero-text">
|
||||||
|
<h1 class="hero-title">{{ 'APP_NAME' | translate }}</h1>
|
||||||
This content will be hidden for smaller screens.
|
<p class="hero-subtitle">
|
||||||
-->
|
{{ 'labels.text.A' | translate }}
|
||||||
|
<span class="hero-highlight">{{ 'labels.text.global community' | translate }}</span>
|
||||||
<div class="flex-70 layout-column align-center align-items-center hide-lt-sm cover">
|
{{ 'labels.text.elimination of poverty' | translate }}
|
||||||
<div class="cover-content">
|
</p>
|
||||||
<h1 class="mat-display-1 cover-title">{{ 'APP_NAME' | translate }}</h1>
|
<div class="hero-features">
|
||||||
<p class="mat-headline cover-description">
|
<div class="feature-item">
|
||||||
{{ 'APP_NAME' | translate }} {{ 'labels.text.is designed by the' | translate }}
|
<fa-icon icon="shield-alt" class="feature-icon"></fa-icon>
|
||||||
<a [href]="'mifosHome' | documentationLink" target="_blank" rel="noopener noreferrer" class="cover-link">{{
|
<span class="feature-text">{{ 'labels.heading.Secure & Reliable' | translate }}</span>
|
||||||
'labels.text.Mifos Initiative' | translate
|
</div>
|
||||||
}}</a
|
<div class="feature-item">
|
||||||
>. {{ 'labels.text.A' | translate }}
|
<fa-icon icon="users" class="feature-icon"></fa-icon>
|
||||||
<a [href]="'mifosCommunity' | documentationLink" target="_blank" rel="noopener noreferrer" class="cover-link">{{
|
<span class="feature-text">{{ 'labels.heading.Community Driven' | translate }}</span>
|
||||||
'labels.text.global community' | translate
|
</div>
|
||||||
}}</a>
|
<div class="feature-item">
|
||||||
{{ 'labels.text.elimination of poverty' | translate }}
|
<fa-icon icon="globe" class="feature-icon"></fa-icon>
|
||||||
<a [href]="'mifosVolunteer' | documentationLink" target="_blank" rel="noopener noreferrer" class="cover-link"
|
<span class="feature-text">{{ 'labels.heading.Global Impact' | translate }}</span>
|
||||||
>{{ 'labels.text.Get involved' | translate }}!</a
|
</div>
|
||||||
>
|
</div>
|
||||||
</p>
|
<div class="hero-links">
|
||||||
|
<a [href]="'mifosHome' | documentationLink" target="_blank" rel="noopener noreferrer" class="hero-link">
|
||||||
|
{{ 'labels.text.Mifos Initiative' | translate }}
|
||||||
|
<fa-icon icon="external-link-alt" size="sm"></fa-icon>
|
||||||
|
</a>
|
||||||
|
<a [href]="'mifosVolunteer' | documentationLink" target="_blank" rel="noopener noreferrer" class="hero-link">
|
||||||
|
{{ 'labels.text.Get involved' | translate }}
|
||||||
|
<fa-icon icon="external-link-alt" size="sm"></fa-icon>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<mifosx-footer variant="compact"></mifosx-footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!-- Right Panel - Login Form -->
|
||||||
Content in 30%: (TODO: Allow customization of Name, Logo and Contact Information by organization)
|
<div class="login-panel">
|
||||||
Language Selector
|
<!-- Header Controls -->
|
||||||
Organization/Application Name with Logo
|
<div class="login-header">
|
||||||
Form
|
|
||||||
Contact Information/Resources
|
|
||||||
Footer
|
|
||||||
-->
|
|
||||||
|
|
||||||
<div class="layout-column flex-gt-sm-30 login-container">
|
|
||||||
<!-- Language Selector to the extreme right -->
|
|
||||||
<div class="layout-row-reverse">
|
|
||||||
<mifosx-language-selector class="p-t-10 p-r-10 width-40"></mifosx-language-selector>
|
|
||||||
<mifosx-theme-toggle class="p-t-10"></mifosx-theme-toggle>
|
|
||||||
@if (allowServerSwitch()) {
|
@if (allowServerSwitch()) {
|
||||||
<mifosx-server-selector class="p-t-10"></mifosx-server-selector>
|
<mifosx-server-selector class="header-control" appearance="outline" [showLabel]="false">
|
||||||
|
</mifosx-server-selector>
|
||||||
}
|
}
|
||||||
|
<mifosx-theme-toggle class="header-control theme-toggle"></mifosx-theme-toggle>
|
||||||
|
<mifosx-language-selector class="header-control" appearance="outline" [showLabel]="false">
|
||||||
|
</mifosx-language-selector>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Logo with Organization Name -->
|
<!-- Main Login Card -->
|
||||||
<div class="layout-row align-center-center flex-auto">
|
<div class="login-card-container">
|
||||||
<img [src]="logoPath" (error)="onLogoError()" alt="{{ 'APP_NAME' | translate }} Logo" class="img-container" />
|
<div class="login-card">
|
||||||
</div>
|
<!-- Logo Section -->
|
||||||
|
<div class="logo-section">
|
||||||
|
@if (!themeDarkEnabled) {
|
||||||
|
<img [src]="logoPath" (error)="onLogoError()" alt="{{ 'APP_NAME' | translate }} Logo" class="logo-image" />
|
||||||
|
} @else {
|
||||||
|
<img src="assets/images/white-mifos.png" alt="{{ 'APP_NAME' | translate }} Logo" class="logo-image" />
|
||||||
|
}
|
||||||
|
<h2 class="welcome-text">{{ 'labels.heading.Welcome to Mifos' | translate }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Form Section -->
|
<!-- Form Section -->
|
||||||
<div class="layout-column align-center-center">
|
<div class="form-section">
|
||||||
<!-- Tenant Identifier Name -->
|
<!-- Tenant Selector -->
|
||||||
@if (displayTenantSelector()) {
|
@if (displayTenantSelector()) {
|
||||||
<mifosx-tenant-selector></mifosx-tenant-selector>
|
<div class="tenant-section">
|
||||||
}
|
<mifosx-tenant-selector></mifosx-tenant-selector>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<!--
|
<!-- Login Forms -->
|
||||||
Type of Form:
|
@if (!resetPassword && !twoFactorAuthenticationRequired) {
|
||||||
Login
|
<mifosx-login-form></mifosx-login-form>
|
||||||
Reset Password
|
}
|
||||||
Two Factor Authentication
|
|
||||||
-->
|
|
||||||
@if (!resetPassword && !twoFactorAuthenticationRequired) {
|
|
||||||
<mifosx-login-form></mifosx-login-form>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="layout-row">
|
@if (resetPassword) {
|
||||||
@if (resetPassword) {
|
<mifosx-reset-password></mifosx-reset-password>
|
||||||
<mifosx-reset-password></mifosx-reset-password>
|
}
|
||||||
}
|
|
||||||
@if (twoFactorAuthenticationRequired) {
|
@if (twoFactorAuthenticationRequired) {
|
||||||
<mifosx-two-factor-authentication></mifosx-two-factor-authentication>
|
<mifosx-two-factor-authentication></mifosx-two-factor-authentication>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Resources Links -->
|
||||||
|
<div class="resources-section">
|
||||||
|
<div class="resources-divider">
|
||||||
|
<span class="divider-text">{{ 'labels.links.Resources' | translate }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="resources-links">
|
||||||
|
<button mat-button [matMenuTriggerFor]="resourcesMenu" class="resource-button">
|
||||||
|
<fa-icon icon="book" size="sm"></fa-icon>
|
||||||
|
{{ 'labels.links.Resources' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-button [matMenuTriggerFor]="communityMenu" class="resource-button">
|
||||||
|
<fa-icon icon="users" size="sm"></fa-icon>
|
||||||
|
{{ 'labels.links.Community' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-button [matMenuTriggerFor]="contributeMenu" class="resource-button">
|
||||||
|
<fa-icon icon="heart" size="sm"></fa-icon>
|
||||||
|
{{ 'labels.links.Contribute' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="layout-row">
|
<!-- Footer -->
|
||||||
<!-- Contact Information/Resources (hidden on smaller screens) -->
|
<div class="login-footer">
|
||||||
<mat-list class="information-list align-items-center custom-horizontal-list hide-lt-lg">
|
<div class="layout-column copy-label">
|
||||||
<mat-list-item
|
<p class="footer-content align-center">
|
||||||
><button mat-button [matMenuTriggerFor]="resourcesMenu">
|
{{ 'APP_NAME' | translate }} {{ 'labels.text.by' | translate }} Mifos
|
||||||
{{ 'labels.links.Resources' | translate }}
|
</p>
|
||||||
</button></mat-list-item
|
</div>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
(click)="reloadSettings()"
|
||||||
|
class="reload-button"
|
||||||
|
matTooltip="Reload Settings"
|
||||||
|
attr.aria-label="Reload Settings"
|
||||||
>
|
>
|
||||||
<mat-list-item
|
<mifosx-m3-icon name="refresh"></mifosx-m3-icon>
|
||||||
><button mat-button [matMenuTriggerFor]="communityMenu">
|
</button>
|
||||||
{{ 'labels.links.Community' | translate }}
|
</div>
|
||||||
</button></mat-list-item
|
|
||||||
>
|
|
||||||
<mat-list-item
|
|
||||||
><button mat-button [matMenuTriggerFor]="contributeMenu">
|
|
||||||
{{ 'labels.links.Contribute' | translate }}
|
|
||||||
</button></mat-list-item
|
|
||||||
>
|
|
||||||
</mat-list>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<mifosx-footer [styleClass]="'m-t-90'"></mifosx-footer>
|
|
||||||
|
|
||||||
<div class="layout-row m-b-20 m-r-10" align="end">
|
|
||||||
<a (click)="reloadSettings()"><fa-icon icon="refresh"></fa-icon></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Contact Information/Resources Menus -->
|
<!-- Resource Menus -->
|
||||||
<mat-menu #resourcesMenu="matMenu">
|
<mat-menu #resourcesMenu="matMenu" class="modern-menu">
|
||||||
<a [href]="'userManual' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
<a [href]="'userManual' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
'labels.links.User Manual' | translate
|
<fa-icon icon="book-open" class="menu-icon"></fa-icon>
|
||||||
}}</a>
|
{{ 'labels.links.User Manual' | translate }}
|
||||||
<a [href]="'fineractFunctionalSpecs' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
</a>
|
||||||
'labels.links.Functional Specifications' | translate
|
<a [href]="'fineractFunctionalSpecs' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
}}</a>
|
<fa-icon icon="file-alt" class="menu-icon"></fa-icon>
|
||||||
<a [href]="'fineractContributorZone' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
{{ 'labels.links.Functional Specifications' | translate }}
|
||||||
'labels.links.Developer Zone' | translate
|
</a>
|
||||||
}}</a>
|
<a [href]="'fineractContributorZone' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
|
<fa-icon icon="code" class="menu-icon"></fa-icon>
|
||||||
|
{{ 'labels.links.Developer Zone' | translate }}
|
||||||
|
</a>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<mat-menu #communityMenu="matMenu">
|
<mat-menu #communityMenu="matMenu" class="modern-menu">
|
||||||
<a [href]="'mifosUserGroup' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
<a [href]="'mifosUserGroup' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
'labels.links.User Group' | translate
|
<fa-icon icon="users" class="menu-icon"></fa-icon>
|
||||||
}}</a>
|
{{ 'labels.links.User Group' | translate }}
|
||||||
<a [href]="'mifosDeveloperGroup' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
</a>
|
||||||
'labels.links.Developer Group' | translate
|
<a [href]="'mifosDeveloperGroup' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
}}</a>
|
<fa-icon icon="laptop-code" class="menu-icon"></fa-icon>
|
||||||
<a [href]="'mifosIrc' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
{{ 'labels.links.Developer Group' | translate }}
|
||||||
'labels.links.IRC' | translate
|
</a>
|
||||||
}}</a>
|
<a [href]="'mifosIrc' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
|
<fa-icon icon="comments" class="menu-icon"></fa-icon>
|
||||||
|
{{ 'labels.links.IRC' | translate }}
|
||||||
|
</a>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<mat-menu #contributeMenu="matMenu">
|
<mat-menu #contributeMenu="matMenu" class="modern-menu">
|
||||||
<a [href]="'keyDesignPrinciples' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
<a [href]="'keyDesignPrinciples' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
'labels.links.Key Design Principles' | translate
|
<fa-icon icon="lightbulb" class="menu-icon"></fa-icon>
|
||||||
}}</a>
|
{{ 'labels.links.Key Design Principles' | translate }}
|
||||||
<a [href]="'sourceforgeMifos' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
</a>
|
||||||
'labels.links.Working with Code' | translate
|
<a [href]="'sourceforgeMifos' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
}}</a>
|
<fa-icon icon="code-branch" class="menu-icon"></fa-icon>
|
||||||
<a [href]="'mifosDonate' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>{{
|
{{ 'labels.links.Working with Code' | translate }}
|
||||||
'labels.links.Donate' | translate
|
</a>
|
||||||
}}</a>
|
<a [href]="'mifosDonate' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
|
||||||
|
<fa-icon icon="hand-holding-heart" class="menu-icon"></fa-icon>
|
||||||
|
{{ 'labels.links.Donate' | translate }}
|
||||||
|
</a>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|||||||
+749
-102
@@ -1,140 +1,787 @@
|
|||||||
mifosx-server-selector {
|
/**
|
||||||
padding-left: 5px;
|
* Modern Material Design 3 Login Component Styles
|
||||||
width: 40%;
|
*/
|
||||||
|
|
||||||
|
// ===== Main Wrapper =====
|
||||||
|
.login-wrapper {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--md-sys-color-background, #fafafa);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-form-field-infix {
|
// ===== Hero Panel (Left Side) =====
|
||||||
width: 144px !important;
|
.hero-panel {
|
||||||
}
|
flex: 0 0 70%;
|
||||||
|
position: relative;
|
||||||
mifosx-tenant-selector {
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 14rem;
|
|
||||||
margin-bottom: 0.1rem;
|
|
||||||
|
|
||||||
::ng-deep .mat-mdc-form-field {
|
|
||||||
width: 14rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep .mat-mdc-form-field-infix {
|
|
||||||
width: 144px !important;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep [matPrefix] fa-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
position: absolute;
|
|
||||||
left: 190px;
|
|
||||||
border-radius: 8px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover {
|
|
||||||
background: url('../../assets/images/cover_image_resized.webp') no-repeat center center;
|
background: url('../../assets/images/cover_image_resized.webp') no-repeat center center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
position: relative;
|
overflow: hidden;
|
||||||
z-index: 0;
|
|
||||||
max-width: 70vw;
|
|
||||||
|
|
||||||
&::after {
|
// Dark overlay for better text contrast
|
||||||
content: '';
|
.hero-overlay {
|
||||||
opacity: 0.5;
|
|
||||||
background: linear-gradient(#000, #000);
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
display: block;
|
|
||||||
z-index: 1;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(135deg, #1074b99e 0%, #004989a6 100%);
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.width-40 {
|
.hero-content {
|
||||||
width: 40%;
|
position: relative;
|
||||||
}
|
|
||||||
|
|
||||||
.cover-content {
|
|
||||||
display: block;
|
|
||||||
max-width: 80%;
|
|
||||||
margin: 100px;
|
|
||||||
color: #fff;
|
|
||||||
text-align: justify;
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
font-size: 1.2rem;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
.cover-title {
|
.hero-text {
|
||||||
border-bottom: 2px solid #fff;
|
max-width: 600px;
|
||||||
margin: 0 0 20px;
|
color: #fff;
|
||||||
padding-bottom: 10px;
|
animation: fade-in-up 0.8s ease-out;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer in hero panel
|
||||||
|
mifosx-footer {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 2rem;
|
||||||
|
|
||||||
|
::ng-deep .footer-compact {
|
||||||
|
color: rgb(255 255 255 / 90%);
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.footer-info {
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: rgb(255 255 255 / 80%);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: rgb(255 255 255 / 95%);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-separator {
|
||||||
|
color: rgb(255 255 255 / 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copyright {
|
||||||
|
color: rgb(255 255 255 / 75%);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 1.5rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #e3f2fd 100%);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0 0 3rem;
|
||||||
|
opacity: 0.95;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-highlight {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #b4d575;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-features {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #ffffff1a;
|
||||||
|
border-radius: 12px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #ffffff26;
|
||||||
|
transform: translateX(8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #b4d575;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-text {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-link {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: 2px solid #ffffff4d;
|
||||||
|
border-radius: 24px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #ffffff1a;
|
||||||
|
border-color: #fff;
|
||||||
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cover-description {
|
fa-icon {
|
||||||
.cover-link {
|
opacity: 0.7;
|
||||||
text-decoration: underline;
|
}
|
||||||
color: #fff;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
// ===== Login Panel (Right Side) =====
|
||||||
color: #d3d3d3;
|
.login-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--md-sys-color-surface, #fff);
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.header-control {
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle {
|
||||||
|
min-height: -webkit-fill-available;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theme Toggle Button Styles
|
||||||
|
::ng-deep mifosx-theme-toggle {
|
||||||
|
button {
|
||||||
|
color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #1074b914;
|
||||||
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language Selector Outlined Styles
|
||||||
|
::ng-deep mifosx-language-selector {
|
||||||
|
width: 20%;
|
||||||
|
|
||||||
|
#language-selector.outlined-variant {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
// Compact outlined field for header
|
||||||
|
.mat-mdc-form-field-flex {
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-infix {
|
||||||
|
padding: 10px 0;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rounded corners for Material Design 3
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading {
|
||||||
|
border-radius: 12px 0 0 12px;
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server Selector Outlined Styles
|
||||||
|
::ng-deep mifosx-server-selector {
|
||||||
|
width: 70%;
|
||||||
|
|
||||||
|
#server-selector.outlined-variant {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
// Compact outlined field for header
|
||||||
|
.mat-mdc-form-field-flex {
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-infix {
|
||||||
|
padding: 10px 0;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rounded corners for Material Design 3
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading {
|
||||||
|
border-radius: 12px 0 0 12px;
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-radius: 0 12px 12px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-container {
|
// ===== Login Card Container =====
|
||||||
box-shadow: 1px 1px 50px #000;
|
.login-card-container {
|
||||||
height: 100vh;
|
flex: 1 1 auto;
|
||||||
width: 100%;
|
|
||||||
min-width: 21.5rem;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
|
|
||||||
.img-container {
|
|
||||||
max-width: 18rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-column {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
animation: fade-in 0.6s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-horizontal-list {
|
.login-card {
|
||||||
display: flex !important;
|
width: 100%;
|
||||||
flex-direction: row !important;
|
max-width: 480px;
|
||||||
|
background: var(--md-sys-color-surface-container-low, #fff);
|
||||||
|
border-radius: 28px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow:
|
||||||
|
0 2px 4px #00000014,
|
||||||
|
0 4px 12px #0000000f,
|
||||||
|
0 12px 32px #0000001f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Logo Section =====
|
||||||
|
.logo-section {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
animation: fade-in-down 0.6s ease-out 0.2s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-image {
|
||||||
|
max-width: 180px;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
filter: drop-shadow(0 2px 8px #0000001a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-subtitle {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Form Section =====
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: fade-in-up 0.6s ease-out 0.3s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tenant-section {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Resources Section =====
|
||||||
|
.resources-section {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
animation: fade-in-up 0.6s ease-out 0.4s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resources-divider {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--md-sys-color-outline-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider-text {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 1rem;
|
||||||
|
background: var(--md-sys-color-surface-container-low, #fff);
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resources-links {
|
||||||
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
gap: 0.5rem;
|
||||||
gap: 1.5rem;
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
mat-list-item {
|
.resource-button {
|
||||||
width: auto !important;
|
font-size: 0.875rem;
|
||||||
min-width: 0 !important;
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
padding: 0 !important;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
[mat-button] {
|
fa-icon {
|
||||||
white-space: nowrap;
|
margin-right: 0.4rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #1074b914;
|
||||||
|
|
||||||
|
fa-icon {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-t-10 {
|
// ===== Footer =====
|
||||||
padding-top: 0;
|
.login-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
margin-top: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
mifosx-footer {
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reload-button {
|
||||||
|
color: var(--mat-app-text-color, #c4c6d0);
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-r-10 {
|
// ===== Modern Menus =====
|
||||||
padding-right: 10px;
|
::ng-deep .modern-menu {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 12px #0000001a,
|
||||||
|
0 2px 4px #0000000f;
|
||||||
|
|
||||||
|
.mat-mdc-menu-item {
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 4px 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .menu-icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .login-container mifosx-footer .divider {
|
// ===== Animations =====
|
||||||
margin: 0 !important;
|
@keyframes fade-in {
|
||||||
width: 14rem !important;
|
from {
|
||||||
margin-top: 2px !important;
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .login-container #footer {
|
@keyframes fade-in-up {
|
||||||
align-items: unset !important;
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-down {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Dark Theme Support =====
|
||||||
|
:host-context(.dark-theme) {
|
||||||
|
.login-wrapper {
|
||||||
|
background: #1a1c1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-panel {
|
||||||
|
background: #1a1c1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
background: #1a1c1e;
|
||||||
|
box-shadow:
|
||||||
|
0 1px 2px #0000004d,
|
||||||
|
0 2px 6px #0003,
|
||||||
|
0 8px 24px #0000004d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-image {
|
||||||
|
max-width: 20rem;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: 63px;
|
||||||
|
content: url('../../assets/images/white-mifos.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-subtitle {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resources-divider {
|
||||||
|
&::before {
|
||||||
|
background: var(--md-sys-color-outline-variant, #44474e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider-text {
|
||||||
|
background: var(--md-sys-color-surface-container-low, #2c2f36);
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-button {
|
||||||
|
color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #5ba2ec1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header controls dark theme
|
||||||
|
.login-header {
|
||||||
|
::ng-deep mifosx-theme-toggle {
|
||||||
|
button {
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #5ba2ec1f;
|
||||||
|
color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language selector and server selector dark theme styles are handled by the components themselves
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Responsive Design =====
|
||||||
|
|
||||||
|
// Tablet
|
||||||
|
@media (width <= 1024px) {
|
||||||
|
.hero-panel {
|
||||||
|
flex: 0 0 45%;
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 2.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-content {
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
mifosx-footer {
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
|
||||||
|
::ng-deep .footer-compact {
|
||||||
|
.footer-info {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copyright {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide resources section on medium screens
|
||||||
|
.resources-section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile
|
||||||
|
@media (width <= 768px) {
|
||||||
|
.login-wrapper {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-panel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-panel {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-container {
|
||||||
|
padding: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
height: auto;
|
||||||
|
min-height: calc(100vh - 56px); // 56px for header
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
::ng-deep mifosx-language-selector {
|
||||||
|
width: 30%;
|
||||||
|
|
||||||
|
#language-selector.outlined-variant {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep mifosx-server-selector {
|
||||||
|
width: 60%;
|
||||||
|
|
||||||
|
#server-selector.outlined-variant {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep mifosx-theme-toggle {
|
||||||
|
button {
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 24px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-section {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-image {
|
||||||
|
max-width: 140px;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide resources section on medium and small screens
|
||||||
|
.resources-section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
mifosx-footer {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reload-button {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
border: none;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small mobile
|
||||||
|
@media (width <= 480px) {
|
||||||
|
.login-header {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
padding: 0.75rem 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-section {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-image {
|
||||||
|
max-width: 110px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-subtitle {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide resources section on small screens
|
||||||
|
.resources-section {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer {
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Utilities =====
|
||||||
|
.hide-lt-md {
|
||||||
|
@media (width <= 768px) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-lt-lg {
|
||||||
|
@media (width <= 1024px) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Alert } from '../core/alert/alert.model';
|
|||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { AlertService } from '../core/alert/alert.service';
|
import { AlertService } from '../core/alert/alert.service';
|
||||||
|
import { ThemingService } from '../shared/theme-toggle/theming.service';
|
||||||
|
|
||||||
/** Environment Imports */
|
/** Environment Imports */
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
@@ -21,11 +22,11 @@ import { TenantSelectorComponent } from '../shared/tenant-selector/tenant-select
|
|||||||
import { LoginFormComponent } from './login-form/login-form.component';
|
import { LoginFormComponent } from './login-form/login-form.component';
|
||||||
import { ResetPasswordComponent } from './reset-password/reset-password.component';
|
import { ResetPasswordComponent } from './reset-password/reset-password.component';
|
||||||
import { TwoFactorAuthenticationComponent } from './two-factor-authentication/two-factor-authentication.component';
|
import { TwoFactorAuthenticationComponent } from './two-factor-authentication/two-factor-authentication.component';
|
||||||
import { MatList, MatListItem } from '@angular/material/list';
|
|
||||||
import { MatMenuTrigger, MatMenu, MatMenuItem } from '@angular/material/menu';
|
import { MatMenuTrigger, MatMenu, MatMenuItem } from '@angular/material/menu';
|
||||||
import { FooterComponent } from '../shared/footer/footer.component';
|
import { FooterComponent } from '../shared/footer/footer.component';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
import { M3IconComponent } from '../shared/m3-ui/m3-icon/m3-icon.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login component.
|
* Login component.
|
||||||
@@ -43,18 +44,18 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
LoginFormComponent,
|
LoginFormComponent,
|
||||||
ResetPasswordComponent,
|
ResetPasswordComponent,
|
||||||
TwoFactorAuthenticationComponent,
|
TwoFactorAuthenticationComponent,
|
||||||
MatList,
|
|
||||||
MatListItem,
|
|
||||||
MatMenuTrigger,
|
MatMenuTrigger,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
FaIconComponent,
|
FaIconComponent,
|
||||||
MatMenu,
|
MatMenu,
|
||||||
MatMenuItem
|
MatMenuItem,
|
||||||
|
M3IconComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class LoginComponent implements OnInit, OnDestroy {
|
export class LoginComponent implements OnInit, OnDestroy {
|
||||||
private alertService = inject(AlertService);
|
private alertService = inject(AlertService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
private themingService = inject(ThemingService);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
|
|
||||||
public environment = environment;
|
public environment = environment;
|
||||||
@@ -66,12 +67,26 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
/** Subscription to alerts. */
|
/** Subscription to alerts. */
|
||||||
alert$: Subscription;
|
alert$: Subscription;
|
||||||
logoPath = 'assets/images/default_home.png';
|
logoPath = 'assets/images/default_home.png';
|
||||||
|
/** Subscription to theme changes. */
|
||||||
|
theme$: Subscription;
|
||||||
|
|
||||||
|
themeDarkEnabled: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribes to alert event of alert service.
|
* Subscribes to alert event of alert service and theme changes.
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.updateLogo();
|
this.updateLogo();
|
||||||
|
this.themeDarkEnabled = this.settingsService.themeDarkEnabled;
|
||||||
|
// Subscribe to theme changes
|
||||||
|
this.theme$ = this.themingService.theme.subscribe((value: string) => {
|
||||||
|
this.themeDarkEnabled = this.settingsService.themeDarkEnabled;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize theme based on settings
|
||||||
|
this.themingService.setDarkMode(!!this.settingsService.themeDarkEnabled);
|
||||||
|
|
||||||
|
// Subscribe to alerts
|
||||||
this.alert$ = this.alertService.alertEvent.subscribe((alertEvent: Alert) => {
|
this.alert$ = this.alertService.alertEvent.subscribe((alertEvent: Alert) => {
|
||||||
const alertType = alertEvent.type;
|
const alertType = alertEvent.type;
|
||||||
if (alertType === 'Password Expired') {
|
if (alertType === 'Password Expired') {
|
||||||
@@ -91,10 +106,15 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribes from alerts.
|
* Unsubscribes from alerts and theme changes.
|
||||||
*/
|
*/
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.alert$.unsubscribe();
|
if (this.alert$) {
|
||||||
|
this.alert$.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.theme$) {
|
||||||
|
this.theme$.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadSettings(): void {
|
reloadSettings(): void {
|
||||||
|
|||||||
@@ -19,8 +19,7 @@
|
|||||||
>
|
>
|
||||||
@if (passwordInputType === 'password') {
|
@if (passwordInputType === 'password') {
|
||||||
<fa-icon icon="eye"></fa-icon>
|
<fa-icon icon="eye"></fa-icon>
|
||||||
}
|
} @else {
|
||||||
@if (passwordInputType === 'text') {
|
|
||||||
<fa-icon icon="eye-slash"></fa-icon>
|
<fa-icon icon="eye-slash"></fa-icon>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,41 +1,59 @@
|
|||||||
<div class="layout-column m-b-20 f12" ngClass="{{ styleClass }}" id="footer" *ngIf="displayBackEndInfo">
|
@if (variant === 'compact') {
|
||||||
<mat-divider class="divider align-center"></mat-divider>
|
<!-- Compact mode for Login -->
|
||||||
<div class="layout-column m-b-20 content-wrapper footer-center">
|
<div class="footer-compact">
|
||||||
<table class="versions">
|
@if (displayBackEndInfo) {
|
||||||
<tr>
|
<div class="footer-info">
|
||||||
<td class="footer-content">Tenant</td>
|
<span class="info-item">
|
||||||
<td class="right footer-content">
|
<span class="info-label">Mifos:</span>
|
||||||
{{ tenant | titlecase }}
|
<span class="info-value">{{ versions.mifos }}</span>
|
||||||
</td>
|
</span>
|
||||||
</tr>
|
<span class="info-separator">•</span>
|
||||||
<tr>
|
<span class="info-item">
|
||||||
<td class="footer-content">Mifos</td>
|
<span class="info-label">{{ 'labels.text.Fineract' | translate }}:</span>
|
||||||
<td class="right footer-content">
|
<span class="info-value">{{ versions.fineract.version }}</span>
|
||||||
{{ versions.mifos }} - <b>{{ hash }}</b>
|
</span>
|
||||||
</td>
|
<span class="info-separator">•</span>
|
||||||
</tr>
|
<span class="info-item">
|
||||||
<tr>
|
<span class="info-label">{{ 'labels.inputs.Server' | translate }}:</span>
|
||||||
<td class="footer-content">{{ 'labels.text.Fineract' | translate }}</td>
|
<span class="info-value">{{ server }}</span>
|
||||||
<td class="right footer-content">
|
</span>
|
||||||
{{ versions.fineract.version }} - <b>{{ versions.fineract.hash }}</b>
|
</div>
|
||||||
</td>
|
}
|
||||||
</tr>
|
|
||||||
<tr *ngIf="displayBackEndInfo">
|
|
||||||
<td class="footer-content">{{ 'labels.inputs.Server' | translate }}</td>
|
|
||||||
<td class="right footer-content">
|
|
||||||
{{ server }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngIf="isBusinessDateDefined">
|
|
||||||
<td class="footer-content">{{ 'labels.text.Current Business Date' | translate }}:</td>
|
|
||||||
<td class="center footer-content business-date">
|
|
||||||
<b>{{ businessDate | date: 'EEEE, MMMM dd, y' }}</b>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
} @else {
|
||||||
<div class="layout-column m-b-20 copy-label">
|
<!-- Default mode for Shell -->
|
||||||
<p class="footer-content align-center">{{ 'APP_NAME' | translate }} {{ 'labels.text.by' | translate }} Mifos</p>
|
@if (displayBackEndInfo) {
|
||||||
</div>
|
<div class="layout-column m-b-20 f12" ngClass="{{ styleClass }}" id="footer">
|
||||||
</div>
|
<div class="layout-column m-t-10 m-b-10 content-wrapper footer-center">
|
||||||
|
<table class="versions">
|
||||||
|
<tr>
|
||||||
|
<td class="footer-content">Mifos</td>
|
||||||
|
<td class="right footer-content">
|
||||||
|
{{ versions.mifos }} - <b>{{ hash }}</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="footer-content">{{ 'labels.text.Fineract' | translate }}</td>
|
||||||
|
<td class="right footer-content">
|
||||||
|
{{ versions.fineract.version }} - <b>{{ versions.fineract.hash }}</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="footer-content">{{ 'labels.inputs.Server' | translate }}</td>
|
||||||
|
<td class="right footer-content">
|
||||||
|
{{ server }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@if (isBusinessDateDefined) {
|
||||||
|
<tr>
|
||||||
|
<td class="footer-content">{{ 'labels.text.Current Business Date' | translate }}:</td>
|
||||||
|
<td class="center footer-content business-date">
|
||||||
|
<b>{{ businessDate | date: 'EEEE, MMMM dd, y' }}</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,58 @@
|
|||||||
@use 'assets/styles/helper';
|
@use 'assets/styles/helper';
|
||||||
@use 'assets/styles/colours' as *;
|
@use 'assets/styles/colours' as *;
|
||||||
|
|
||||||
|
// ===== Compact Mode (Login) =====
|
||||||
|
.footer-compact {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
|
||||||
|
.footer-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: 500;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-weight: 400;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-separator {
|
||||||
|
opacity: 0.4;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copyright {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark theme support for compact mode
|
||||||
|
.dark-theme .footer-compact {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Default Mode (Shell) =====
|
||||||
#footer {
|
#footer {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
@@ -48,5 +100,21 @@
|
|||||||
.copy-label {
|
.copy-label {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: large;
|
font-size: medium;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Responsive for Compact Mode =====
|
||||||
|
@media (width <= 480px) {
|
||||||
|
.footer-compact {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
|
||||||
|
.footer-info {
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copyright {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { VersionService } from 'app/system/version.service';
|
|||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { NgClass, DatePipe } from '@angular/common';
|
import { NgClass, DatePipe } from '@angular/common';
|
||||||
import { MatDivider } from '@angular/material/divider';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +24,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
NgClass,
|
NgClass,
|
||||||
MatDivider,
|
|
||||||
DatePipe
|
DatePipe
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -38,6 +36,7 @@ export class FooterComponent implements OnInit, OnDestroy {
|
|||||||
private versionService = inject(VersionService);
|
private versionService = inject(VersionService);
|
||||||
|
|
||||||
@Input() styleClass: string = '';
|
@Input() styleClass: string = '';
|
||||||
|
@Input() variant: 'default' | 'compact' = 'default';
|
||||||
|
|
||||||
/** Mifos X version. */
|
/** Mifos X version. */
|
||||||
versions: any = {
|
versions: any = {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
faFileUpload,
|
faFileUpload,
|
||||||
faFileWord,
|
faFileWord,
|
||||||
faFillDrip,
|
faFillDrip,
|
||||||
|
faGlobe,
|
||||||
faHandHoldingUsd,
|
faHandHoldingUsd,
|
||||||
faHome,
|
faHome,
|
||||||
faKey,
|
faKey,
|
||||||
@@ -113,7 +114,17 @@ import {
|
|||||||
faPause,
|
faPause,
|
||||||
faReceipt,
|
faReceipt,
|
||||||
faTableCells,
|
faTableCells,
|
||||||
faSave
|
faSave,
|
||||||
|
faExternalLinkAlt,
|
||||||
|
faHeart,
|
||||||
|
faBookOpen,
|
||||||
|
faCode,
|
||||||
|
faLaptopCode,
|
||||||
|
faComments,
|
||||||
|
faLightbulb,
|
||||||
|
faCodeBranch,
|
||||||
|
faHandHoldingHand,
|
||||||
|
faHandHoldingHeart
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,6 +148,7 @@ export class IconsModule {
|
|||||||
faBars,
|
faBars,
|
||||||
faBell,
|
faBell,
|
||||||
faBook,
|
faBook,
|
||||||
|
faBookOpen,
|
||||||
faBriefcase,
|
faBriefcase,
|
||||||
faBuilding,
|
faBuilding,
|
||||||
faCalculator,
|
faCalculator,
|
||||||
@@ -150,9 +162,12 @@ export class IconsModule {
|
|||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
faChevronRight,
|
faChevronRight,
|
||||||
faCircle,
|
faCircle,
|
||||||
|
faComments,
|
||||||
faHeartBroken,
|
faHeartBroken,
|
||||||
faClock,
|
faClock,
|
||||||
faCloud,
|
faCloud,
|
||||||
|
faCode,
|
||||||
|
faCodeBranch,
|
||||||
faCog,
|
faCog,
|
||||||
faCogs,
|
faCogs,
|
||||||
faCommentAlt,
|
faCommentAlt,
|
||||||
@@ -163,6 +178,7 @@ export class IconsModule {
|
|||||||
faExchange,
|
faExchange,
|
||||||
faExclamationCircle,
|
faExclamationCircle,
|
||||||
faExclamationTriangle,
|
faExclamationTriangle,
|
||||||
|
faExternalLinkAlt,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faFile,
|
faFile,
|
||||||
@@ -173,9 +189,15 @@ export class IconsModule {
|
|||||||
faFileExcel,
|
faFileExcel,
|
||||||
faFileExport,
|
faFileExport,
|
||||||
faFillDrip,
|
faFillDrip,
|
||||||
|
faGlobe,
|
||||||
|
faHandHoldingHand,
|
||||||
|
faHandHoldingHeart,
|
||||||
faHandHoldingUsd,
|
faHandHoldingUsd,
|
||||||
|
faHeart,
|
||||||
faHome,
|
faHome,
|
||||||
faKey,
|
faKey,
|
||||||
|
faLaptopCode,
|
||||||
|
faLightbulb,
|
||||||
faLink,
|
faLink,
|
||||||
faList,
|
faList,
|
||||||
faListUl,
|
faListUl,
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
<mat-form-field id="language-selector" class="m-l-10">
|
<mat-form-field id="language-selector" [appearance]="appearance" [class.outlined-variant]="appearance === 'outline'">
|
||||||
<mat-label>{{ 'labels.inputs.Language' | translate }}</mat-label>
|
@if (showLabel) {
|
||||||
<mat-select [formControl]="languageSelector" (selectionChange)="setLanguage()" class="languageselector">
|
<mat-label>{{ 'labels.inputs.Language' | translate }}</mat-label>
|
||||||
|
}
|
||||||
|
<mat-select
|
||||||
|
[formControl]="languageSelector"
|
||||||
|
(selectionChange)="setLanguage()"
|
||||||
|
class="languageselector"
|
||||||
|
[placeholder]="!showLabel ? ('labels.inputs.Language' | translate) : ''"
|
||||||
|
>
|
||||||
@for (language of languages; track language) {
|
@for (language of languages; track language) {
|
||||||
<mat-option [value]="language">
|
<mat-option [value]="language">
|
||||||
{{ 'languages.' + language | translate }}
|
{{ 'languages.' + language | translate }}
|
||||||
|
|||||||
@@ -1,19 +1,131 @@
|
|||||||
::ng-deep .mat-mdc-form-field-bottom-align::before {
|
// ===== Base Language Selector Styles =====
|
||||||
border-bottom-color: white;
|
#language-selector {
|
||||||
|
&.mat-mdc-form-field {
|
||||||
|
// Default styles (fill variant)
|
||||||
|
&:not(.outlined-variant) {
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
.mat-mdc-text-field-wrapper {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outlined variant styles (used in login)
|
||||||
|
&.outlined-variant {
|
||||||
|
.mat-mdc-text-field-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-flex {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-infix {
|
||||||
|
padding: 12px 0;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outlined border styles with Material Design 3
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-outline, #79747e);
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
&:hover:not(.mat-form-field-disabled) {
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focused state
|
||||||
|
&.mat-focused {
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select styling
|
||||||
|
.mat-mdc-select {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-select-arrow {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label styling
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .mdc-line-ripple::before {
|
// Dark theme support for outlined variant
|
||||||
border-bottom-color: white;
|
.dark-theme {
|
||||||
}
|
#language-selector.outlined-variant {
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-outline, #938f99);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep .mdc-line-ripple::after {
|
&:hover:not(.mat-form-field-disabled) {
|
||||||
border-bottom-color: white;
|
.mdc-notched-outline {
|
||||||
}
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep .mat-mdc-form-field .mdc-line-ripple::before {
|
&.mat-focused {
|
||||||
border-bottom-color: white !important;
|
.mdc-notched-outline {
|
||||||
}
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep .mat-mdc-form-field .mdc-line-ripple::after {
|
.mat-mdc-floating-label {
|
||||||
border-bottom-color: white !important;
|
color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-select {
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-select-arrow {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/** Angular Imports */
|
/** Angular Imports */
|
||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject, Input } from '@angular/core';
|
||||||
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
|
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
@@ -25,6 +25,12 @@ export class LanguageSelectorComponent {
|
|||||||
private translateService = inject(TranslateService);
|
private translateService = inject(TranslateService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Appearance of the form field (fill, outline). Defaults to 'fill'. */
|
||||||
|
@Input() appearance: 'fill' | 'outline' = 'fill';
|
||||||
|
|
||||||
|
/** Show label in the form field. Defaults to true. */
|
||||||
|
@Input() showLabel: boolean = true;
|
||||||
|
|
||||||
/** Language selector form control. */
|
/** Language selector form control. */
|
||||||
languageSelector = new UntypedFormControl();
|
languageSelector = new UntypedFormControl();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Material Design 3 UI Components
|
||||||
|
*
|
||||||
|
* This module exports all Material Design 3 components for easy importing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { M3ButtonComponent, M3ButtonVariant } from './m3-button/m3-button.component';
|
||||||
|
export { M3IconComponent, M3IconStyle } from './m3-icon/m3-icon.component';
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<div [class]="containerClasses">
|
||||||
|
@if (variant === 'filled') {
|
||||||
|
<md-filled-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||||
|
{{ label }}<ng-content></ng-content>
|
||||||
|
</md-filled-button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (variant === 'outlined') {
|
||||||
|
<md-outlined-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||||
|
{{ label }}<ng-content></ng-content>
|
||||||
|
</md-outlined-button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (variant === 'elevated') {
|
||||||
|
<md-elevated-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||||
|
{{ label }}<ng-content></ng-content>
|
||||||
|
</md-elevated-button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (variant === 'text') {
|
||||||
|
<md-text-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||||
|
{{ label }}<ng-content></ng-content>
|
||||||
|
</md-text-button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (variant === 'tonal') {
|
||||||
|
<md-filled-tonal-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||||
|
{{ label }}<ng-content></ng-content>
|
||||||
|
</md-filled-tonal-button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Material Web Button Component Styles
|
||||||
|
// Base styles are applied globally in src/theme/_material-web.scss
|
||||||
|
|
||||||
|
.m3-button-container {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&--full-width {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
md-filled-button,
|
||||||
|
md-outlined-button,
|
||||||
|
md-elevated-button,
|
||||||
|
md-text-button,
|
||||||
|
md-filled-tonal-button {
|
||||||
|
color: var(--md-filled-button-disabled-label-text-color, #44474e);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
// Import Material Web Components
|
||||||
|
import '@material/web/button/filled-button.js';
|
||||||
|
import '@material/web/button/outlined-button.js';
|
||||||
|
import '@material/web/button/elevated-button.js';
|
||||||
|
import '@material/web/button/text-button.js';
|
||||||
|
import '@material/web/button/filled-tonal-button.js';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
export type M3ButtonVariant = 'filled' | 'outlined' | 'elevated' | 'text' | 'tonal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material Design 3 Button Component
|
||||||
|
*
|
||||||
|
* Wrapper for @material/web button components following Material Design 3 specifications.
|
||||||
|
* Uses native Material Design 3 web components with full theming support.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Five button variants: filled, outlined, elevated, text, and tonal
|
||||||
|
* - Automatic light/dark theme support
|
||||||
|
* - Proper disabled states with gray colors
|
||||||
|
* - Hover, focus, and pressed states with ripple effects
|
||||||
|
* - Full width support
|
||||||
|
* - Icon support (leading and trailing)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage with label input
|
||||||
|
* <mifosx-m3-button
|
||||||
|
* variant="filled"
|
||||||
|
* [label]="'Submit'"
|
||||||
|
* (clicked)="onSubmit()">
|
||||||
|
* </mifosx-m3-button>
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With content projection
|
||||||
|
* <mifosx-m3-button variant="outlined" (clicked)="onCancel()">
|
||||||
|
* Cancel
|
||||||
|
* </mifosx-m3-button>
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Full width disabled button
|
||||||
|
* <mifosx-m3-button
|
||||||
|
* variant="filled"
|
||||||
|
* [label]="'Loading...'"
|
||||||
|
* [disabled]="true"
|
||||||
|
* [fullWidth]="true">
|
||||||
|
* </mifosx-m3-button>
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With translated label
|
||||||
|
* <mifosx-m3-button
|
||||||
|
* variant="tonal"
|
||||||
|
* type="submit"
|
||||||
|
* [label]="'labels.buttons.Save' | translate"
|
||||||
|
* (clicked)="onSave()">
|
||||||
|
* </mifosx-m3-button>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-m3-button',
|
||||||
|
templateUrl: './m3-button.component.html',
|
||||||
|
styleUrls: ['./m3-button.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
STANDALONE_SHARED_IMPORTS,
|
||||||
|
CommonModule
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class M3ButtonComponent {
|
||||||
|
/**
|
||||||
|
* Button variant
|
||||||
|
* - filled: Solid background (default, highest emphasis)
|
||||||
|
* - outlined: Bordered button (medium emphasis)
|
||||||
|
* - elevated: Raised button with shadow (medium emphasis)
|
||||||
|
* - text: Text-only button (low emphasis)
|
||||||
|
* - tonal: Subtle background button (medium emphasis)
|
||||||
|
*/
|
||||||
|
@Input() variant: M3ButtonVariant = 'filled';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button type attribute
|
||||||
|
*/
|
||||||
|
@Input() type: 'button' | 'submit' | 'reset' = 'button';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disabled state
|
||||||
|
*/
|
||||||
|
@Input() disabled: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button label text
|
||||||
|
*/
|
||||||
|
@Input() label?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon to display before text
|
||||||
|
*/
|
||||||
|
@Input() icon?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon to display after text
|
||||||
|
*/
|
||||||
|
@Input() trailingIcon?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the button should take full width
|
||||||
|
*/
|
||||||
|
@Input() fullWidth: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click event emitter
|
||||||
|
*/
|
||||||
|
@Output() clicked = new EventEmitter<Event>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle button click
|
||||||
|
*/
|
||||||
|
onClick(event: Event): void {
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.clicked.emit(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Material Web Component tag name based on variant
|
||||||
|
*/
|
||||||
|
get componentTag(): string {
|
||||||
|
switch (this.variant) {
|
||||||
|
case 'filled':
|
||||||
|
return 'md-filled-button';
|
||||||
|
case 'outlined':
|
||||||
|
return 'md-outlined-button';
|
||||||
|
case 'elevated':
|
||||||
|
return 'md-elevated-button';
|
||||||
|
case 'text':
|
||||||
|
return 'md-text-button';
|
||||||
|
case 'tonal':
|
||||||
|
return 'md-filled-tonal-button';
|
||||||
|
default:
|
||||||
|
return 'md-filled-button';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get container classes
|
||||||
|
*/
|
||||||
|
get containerClasses(): string {
|
||||||
|
return this.fullWidth ? 'm3-button-container--full-width' : 'm3-button-container';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<span [class]="iconClass" [ngStyle]="iconStyles" class="m3-icon">{{ name }}</span>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Material Symbols Font Faces
|
||||||
|
// These will be loaded from Google Fonts or local files
|
||||||
|
@use 'theme/material-web' as *;
|
||||||
|
|
||||||
|
.m3-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
user-select: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
// Smooth rendering
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-rendering: optimizelegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Material Symbols base styles
|
||||||
|
.material-symbols-outlined,
|
||||||
|
.material-symbols-filled,
|
||||||
|
.material-symbols-rounded,
|
||||||
|
.material-symbols-sharp {
|
||||||
|
font-family: 'Material Symbols Outlined', sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: normal;
|
||||||
|
text-transform: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-wrap: normal;
|
||||||
|
direction: ltr;
|
||||||
|
color: var(--md-icon-prefix-label-text-color, #44474e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filled variant
|
||||||
|
.material-symbols-filled {
|
||||||
|
font-variation-settings:
|
||||||
|
'FILL' 1,
|
||||||
|
'wght' 400,
|
||||||
|
'GRAD' 0,
|
||||||
|
'opsz' 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outlined variant (default)
|
||||||
|
.material-symbols-outlined {
|
||||||
|
font-variation-settings:
|
||||||
|
'FILL' 0,
|
||||||
|
'wght' 400,
|
||||||
|
'GRAD' 0,
|
||||||
|
'opsz' 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rounded variant
|
||||||
|
.material-symbols-rounded {
|
||||||
|
font-family: 'Material Symbols Rounded', sans-serif;
|
||||||
|
font-variation-settings:
|
||||||
|
'FILL' 0,
|
||||||
|
'wght' 400,
|
||||||
|
'GRAD' 0,
|
||||||
|
'opsz' 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sharp variant
|
||||||
|
.material-symbols-sharp {
|
||||||
|
font-family: 'Material Symbols Sharp', sans-serif;
|
||||||
|
font-variation-settings:
|
||||||
|
'FILL' 0,
|
||||||
|
'wght' 400,
|
||||||
|
'GRAD' 0,
|
||||||
|
'opsz' 24;
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Component, Input, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
export type M3IconStyle = 'outlined' | 'filled' | 'rounded' | 'sharp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material Design 3 Icon Component
|
||||||
|
*
|
||||||
|
* Wrapper for Material Symbols icons following Material Design 3 specifications.
|
||||||
|
* Uses Google's Material Symbols font for consistent iconography.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Multiple icon styles: outlined (default), filled, rounded, and sharp
|
||||||
|
* - Automatic theme support
|
||||||
|
* - Size control
|
||||||
|
* - Color customization via CSS
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage
|
||||||
|
* <mifosx-m3-icon name="account_circle"></mifosx-m3-icon>
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With filled style
|
||||||
|
* <mifosx-m3-icon name="lock" style="filled"></mifosx-m3-icon>
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With custom size
|
||||||
|
* <mifosx-m3-icon name="visibility" [size]="24"></mifosx-m3-icon>
|
||||||
|
*
|
||||||
|
* Common icon mappings from Font Awesome:
|
||||||
|
* - user-circle → account_circle
|
||||||
|
* - lock → lock
|
||||||
|
* - eye → visibility
|
||||||
|
* - eye-slash → visibility_off
|
||||||
|
* - home → home
|
||||||
|
* - search → search
|
||||||
|
* - settings → settings
|
||||||
|
* - menu → menu
|
||||||
|
* - close → close
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-m3-icon',
|
||||||
|
templateUrl: './m3-icon.component.html',
|
||||||
|
styleUrls: ['./m3-icon.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class M3IconComponent {
|
||||||
|
/**
|
||||||
|
* Icon name from Material Symbols
|
||||||
|
* See: https://fonts.google.com/icons
|
||||||
|
*/
|
||||||
|
@Input() name!: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon style variant
|
||||||
|
* - outlined: Default style with strokes (default)
|
||||||
|
* - filled: Solid filled icons
|
||||||
|
* - rounded: Rounded corner icons
|
||||||
|
* - sharp: Sharp corner icons
|
||||||
|
*/
|
||||||
|
@Input() style: M3IconStyle = 'outlined';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon size in pixels
|
||||||
|
* Default is 24px (Material Design standard)
|
||||||
|
*/
|
||||||
|
@Input() size: number = 24;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the icon should be filled (alternative to style="filled")
|
||||||
|
* This is a convenience property for backwards compatibility
|
||||||
|
*/
|
||||||
|
@Input() filled: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the CSS class for the icon style
|
||||||
|
*/
|
||||||
|
get iconClass(): string {
|
||||||
|
const styleToUse = this.filled ? 'filled' : this.style;
|
||||||
|
return `material-symbols-${styleToUse}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the inline styles for the icon
|
||||||
|
*/
|
||||||
|
get iconStyles(): { [key: string]: string } {
|
||||||
|
return {
|
||||||
|
'font-size': `${this.size}px`,
|
||||||
|
width: `${this.size}px`,
|
||||||
|
height: `${this.size}px`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
@if (existMoreThanOneServer) {
|
@if (existMoreThanOneServer) {
|
||||||
<mat-form-field id="server-selector">
|
<mat-form-field id="server-selector" [appearance]="appearance" [class.outlined-variant]="appearance === 'outline'">
|
||||||
<mat-label>{{ 'labels.inputs.Server' | translate }}</mat-label>
|
@if (showLabel) {
|
||||||
|
<mat-label>{{ 'labels.inputs.Server' | translate }}</mat-label>
|
||||||
|
}
|
||||||
<mat-select
|
<mat-select
|
||||||
[formControl]="serverSelector"
|
[formControl]="serverSelector"
|
||||||
(selectionChange)="setServer()"
|
(selectionChange)="setServer()"
|
||||||
panelClass="server-panel"
|
panelClass="server-panel"
|
||||||
panelWidth="null"
|
panelWidth="null"
|
||||||
|
[placeholder]="!showLabel ? ('labels.inputs.Server' | translate) : ''"
|
||||||
>
|
>
|
||||||
<div class="sticky">
|
<div class="sticky">
|
||||||
<form [formGroup]="form" (ngSubmit)="addNewServer()">
|
<form [formGroup]="form" (ngSubmit)="addNewServer()">
|
||||||
|
|||||||
@@ -1,3 +1,138 @@
|
|||||||
|
// ===== Base Server Selector Styles =====
|
||||||
|
#server-selector {
|
||||||
|
&.mat-mdc-form-field {
|
||||||
|
// Default styles (fill variant)
|
||||||
|
&:not(.outlined-variant) {
|
||||||
|
mat-form-field {
|
||||||
|
width: 96%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outlined variant styles (used in login)
|
||||||
|
&.outlined-variant {
|
||||||
|
.mat-mdc-text-field-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-flex {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field-infix {
|
||||||
|
padding: 12px 0;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outlined border styles with Material Design 3
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-outline, #79747e);
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
&:hover:not(.mat-form-field-disabled) {
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focused state
|
||||||
|
&.mat-focused {
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select styling
|
||||||
|
.mat-mdc-select {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-select-arrow {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label styling
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #44474e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark theme support for outlined variant
|
||||||
|
.dark-theme {
|
||||||
|
#server-selector.outlined-variant {
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-outline, #938f99);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.mat-form-field-disabled) {
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mat-focused {
|
||||||
|
.mdc-notched-outline {
|
||||||
|
.mdc-notched-outline__leading,
|
||||||
|
.mdc-notched-outline__notch,
|
||||||
|
.mdc-notched-outline__trailing {
|
||||||
|
border-color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-select {
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-select-arrow {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-floating-label {
|
||||||
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky {
|
||||||
|
background-color: var(--md-sys-color-surface-container, #2c2f36);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Common Styles =====
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -12,8 +147,8 @@
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
z-index: 1; // appear above mat-option
|
z-index: 1; // appear above mat-option
|
||||||
top: 0; // fix at the top
|
top: 0; // fix at the top
|
||||||
background-color: white; // don't see mat-option when scrolling
|
background-color: var(--md-sys-color-surface-container, white); // don't see mat-option when scrolling
|
||||||
box-shadow: 1px -2px 10px 0 grey; // shadow at the bottom
|
box-shadow: 1px -2px 10px 0 rgb(0 0 0 / 10%); // shadow at the bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-form-field {
|
mat-form-field {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/** Angular Imports */
|
/** Angular Imports */
|
||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject, Input } from '@angular/core';
|
||||||
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
|
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { UntypedFormBuilder, Validators } from '@angular/forms';
|
import { UntypedFormBuilder, Validators } from '@angular/forms';
|
||||||
@@ -27,6 +27,12 @@ export class ServerSelectorComponent implements OnInit {
|
|||||||
dialog = inject(MatDialog);
|
dialog = inject(MatDialog);
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
|
||||||
|
/** Appearance of the form field (fill, outline). Defaults to 'fill'. */
|
||||||
|
@Input() appearance: 'fill' | 'outline' = 'fill';
|
||||||
|
|
||||||
|
/** Show label in the form field. Defaults to true. */
|
||||||
|
@Input() showLabel: boolean = true;
|
||||||
|
|
||||||
/** Input server. */
|
/** Input server. */
|
||||||
form: any;
|
form: any;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
<mat-form-field class="flex-fill align-center" id="tenant-selector">
|
<mat-form-field class="flex-fill align-center" id="tenant-selector" appearance="fill">
|
||||||
<span matPrefix class="m-r-10">
|
|
||||||
<fa-icon icon="building" size="lg"></fa-icon>
|
|
||||||
</span>
|
|
||||||
<mat-label>{{ 'labels.inputs.Tenant' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Tenant' | translate }}</mat-label>
|
||||||
<mat-select [formControl]="tenantSelector" (selectionChange)="setTenantIdentifier()" class="tenantselector">
|
<mat-select [formControl]="tenantSelector" (selectionChange)="setTenantIdentifier()" class="tenantselector">
|
||||||
@for (tenant of tenants; track tenant) {
|
@for (tenant of tenants; track tenant) {
|
||||||
@@ -10,4 +7,5 @@
|
|||||||
</mat-option>
|
</mat-option>
|
||||||
}
|
}
|
||||||
</mat-select>
|
</mat-select>
|
||||||
|
<mifosx-m3-icon matIconPrefix name="domain"></mifosx-m3-icon>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|||||||
@@ -1,5 +1,2 @@
|
|||||||
#tenant-selector,
|
// Styles are now controlled by parent component (login-form)
|
||||||
.tenantselector {
|
// to ensure consistency with other form fields
|
||||||
width: 14rem;
|
|
||||||
margin-bottom: 0.6rem;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
|
|||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { AlertService } from 'app/core/alert/alert.service';
|
import { AlertService } from 'app/core/alert/alert.service';
|
||||||
import { MatFormField, MatPrefix, MatLabel } from '@angular/material/form-field';
|
import { MatFormField, MatPrefix, MatLabel } from '@angular/material/form-field';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { M3IconComponent } from 'app/shared/m3-ui/m3-icon/m3-icon.component';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -13,7 +13,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
MatPrefix,
|
MatPrefix,
|
||||||
FaIconComponent
|
M3IconComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class TenantSelectorComponent implements OnInit {
|
export class TenantSelectorComponent implements OnInit {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<button mat-icon-button (click)="toggleTheme()" class="m-l-15 m-r-5">
|
<button mat-icon-button (click)="toggleTheme()" class="m-l-5 m-r-5">
|
||||||
<mat-icon>{{ darkModeOn ? 'wb_sunny' : 'nights_stay' }}</mat-icon>
|
<mifosx-m3-icon [name]="darkModeOn ? 'wb_sunny' : 'nights_stay'"></mifosx-m3-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Component, OnChanges, OnInit, SimpleChanges, inject } from '@angular/co
|
|||||||
import { ThemingService } from './theming.service';
|
import { ThemingService } from './theming.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { MatIconButton } from '@angular/material/button';
|
import { MatIconButton } from '@angular/material/button';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { M3IconComponent } from '../m3-ui/m3-icon/m3-icon.component';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -12,7 +12,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
MatIconButton,
|
MatIconButton,
|
||||||
MatIcon
|
M3IconComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ThemeToggleComponent implements OnInit, OnChanges {
|
export class ThemeToggleComponent implements OnInit, OnChanges {
|
||||||
@@ -29,8 +29,16 @@ export class ThemeToggleComponent implements OnInit, OnChanges {
|
|||||||
this.darkModeOn = !!this.settingsService.themeDarkEnabled;
|
this.darkModeOn = !!this.settingsService.themeDarkEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle between light and dark themes
|
||||||
|
* This method handles the complete theme switching process:
|
||||||
|
* 1. Toggles the local state
|
||||||
|
* 2. Persists the preference to settings
|
||||||
|
*/
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
|
// Step 1: Toggle the dark mode state
|
||||||
this.darkModeOn = !this.darkModeOn;
|
this.darkModeOn = !this.darkModeOn;
|
||||||
|
// Step 2: Persist the theme preference to localStorage via settings service
|
||||||
this.settingsService.setThemeDarkEnabled(this.darkModeOn);
|
this.settingsService.setThemeDarkEnabled(this.darkModeOn);
|
||||||
this.themingService.setDarkMode(this.darkModeOn);
|
this.themingService.setDarkMode(this.darkModeOn);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,10 @@ export class ThemingService {
|
|||||||
this.darkModeOn = isDarkMode;
|
this.darkModeOn = isDarkMode;
|
||||||
if (isDarkMode) {
|
if (isDarkMode) {
|
||||||
document.body.classList.add('dark-theme');
|
document.body.classList.add('dark-theme');
|
||||||
|
document.body.classList.remove('light-theme');
|
||||||
this.theme.next('dark-theme');
|
this.theme.next('dark-theme');
|
||||||
} else {
|
} else {
|
||||||
|
document.body.classList.add('light-theme');
|
||||||
document.body.classList.remove('dark-theme');
|
document.body.classList.remove('dark-theme');
|
||||||
this.theme.next('light-theme');
|
this.theme.next('light-theme');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,5 @@ export const STANDALONE_SHARED_IMPORTS = [
|
|||||||
NgxTranslatePipe,
|
NgxTranslatePipe,
|
||||||
TranslatePipe
|
TranslatePipe
|
||||||
];
|
];
|
||||||
|
export { M3ButtonComponent } from './shared/m3-ui/m3-button/m3-button.component';
|
||||||
export { HasPermissionDirective } from './directives/has-permission/has-permission.directive';
|
export { HasPermissionDirective } from './directives/has-permission/has-permission.directive';
|
||||||
|
|||||||
@@ -27,8 +27,7 @@
|
|||||||
>
|
>
|
||||||
@if (passwordInputType === 'password') {
|
@if (passwordInputType === 'password') {
|
||||||
<fa-icon icon="eye"></fa-icon>
|
<fa-icon icon="eye"></fa-icon>
|
||||||
}
|
} @else {
|
||||||
@if (passwordInputType === 'text') {
|
|
||||||
<fa-icon icon="eye-slash"></fa-icon>
|
<fa-icon icon="eye-slash"></fa-icon>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -3,19 +3,24 @@
|
|||||||
|
|
||||||
// BackEnd Environment variables
|
// BackEnd Environment variables
|
||||||
window['env']['fineractApiUrls'] = '$FINERACT_API_URLS';
|
window['env']['fineractApiUrls'] = '$FINERACT_API_URLS';
|
||||||
|
|
||||||
window['env']['fineractApiUrl'] = '$FINERACT_API_URL';
|
window['env']['fineractApiUrl'] = '$FINERACT_API_URL';
|
||||||
|
|
||||||
window['env']['apiProvider'] = '$FINERACT_API_PROVIDER';
|
window['env']['apiProvider'] = '$FINERACT_API_PROVIDER';
|
||||||
|
|
||||||
window['env']['apiVersion'] = '$FINERACT_API_VERSION';
|
window['env']['apiVersion'] = '$FINERACT_API_VERSION';
|
||||||
|
|
||||||
window['env']['apiActuator'] = '$FINERACT_API_ACTUATOR';
|
window['env']['apiActuator'] = '$FINERACT_API_ACTUATOR';
|
||||||
|
|
||||||
window['env']['fineractPlatformTenantId'] = '$FINERACT_PLATFORM_TENANT_IDENTIFIER';
|
window['env']['fineractPlatformTenantId'] = '$FINERACT_PLATFORM_TENANT_IDENTIFIER';
|
||||||
|
|
||||||
window['env']['fineractPlatformTenantIds'] = '$FINERACT_PLATFORM_TENANTS_IDENTIFIER';
|
window['env']['fineractPlatformTenantIds'] = '$FINERACT_PLATFORM_TENANTS_IDENTIFIER';
|
||||||
|
|
||||||
window['env']['tenantLogoUrl'] = '$TENANT_LOGO_URL';
|
window['env']['tenantLogoUrl'] = '$TENANT_LOGO_URL';
|
||||||
|
|
||||||
// Language Environment variables
|
// Language Environment variables
|
||||||
window['env']['defaultLanguage'] = '$MIFOS_DEFAULT_LANGUAGE';
|
window['env']['defaultLanguage'] = '$MIFOS_DEFAULT_LANGUAGE';
|
||||||
|
|
||||||
window['env']['supportedLanguages'] = '$MIFOS_SUPPORTED_LANGUAGES';
|
window['env']['supportedLanguages'] = '$MIFOS_SUPPORTED_LANGUAGES';
|
||||||
|
|
||||||
window['env']['preloadClients'] = '$MIFOS_PRELOAD_CLIENTS';
|
window['env']['preloadClients'] = '$MIFOS_PRELOAD_CLIENTS';
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Vybrat vše",
|
"Select All": "Vybrat vše",
|
||||||
"Show more": "Zobrazit více",
|
"Show more": "Zobrazit více",
|
||||||
"Show less": "Ukaž méně",
|
"Show less": "Ukaž méně",
|
||||||
|
"Signing in...": "Přihlašování",
|
||||||
"Staff": "Personál",
|
"Staff": "Personál",
|
||||||
"Staff Assignment History": "Historie přidělení zaměstnanců",
|
"Staff Assignment History": "Historie přidělení zaměstnanců",
|
||||||
"Subledger Account": "Účet vedlejší knihy",
|
"Subledger Account": "Účet vedlejší knihy",
|
||||||
@@ -1242,7 +1243,10 @@
|
|||||||
"Not Yet Amortized Amount": "Dosud neamortizovana castka",
|
"Not Yet Amortized Amount": "Dosud neamortizovana castka",
|
||||||
"Adjusted Amount": "Upravená částka",
|
"Adjusted Amount": "Upravená částka",
|
||||||
"Charged Off Amount": "Odepsaná částka",
|
"Charged Off Amount": "Odepsaná částka",
|
||||||
"Date": "Datum"
|
"Date": "Datum",
|
||||||
|
"Secure & Reliable": "Bezpečné a spolehlivé",
|
||||||
|
"Community Driven": "Komunitně řízené",
|
||||||
|
"Global Impact": "Globální dopad"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2725,7 +2729,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Přidat nový server",
|
"Add new server": "Přidat nový server",
|
||||||
"Search Activity": "Aktivita vyhledávání"
|
"Search Activity": "Aktivita vyhledávání",
|
||||||
|
"Enter your username": "Zadejte své uživatelské jméno",
|
||||||
|
"Enter your password": "Zadejte své heslo"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Aktivní",
|
"Active": "Aktivní",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Wählen Sie Alle",
|
"Select All": "Wählen Sie Alle",
|
||||||
"Show more": "Zeig mehr",
|
"Show more": "Zeig mehr",
|
||||||
"Show less": "Zeige weniger",
|
"Show less": "Zeige weniger",
|
||||||
|
"Signing in...": "Anmelden",
|
||||||
"Staff": "Personal",
|
"Staff": "Personal",
|
||||||
"Staff Assignment History": "Verlauf der Personalzuweisungen",
|
"Staff Assignment History": "Verlauf der Personalzuweisungen",
|
||||||
"Subledger Account": "Nebenbuchkonto",
|
"Subledger Account": "Nebenbuchkonto",
|
||||||
@@ -1243,7 +1244,10 @@
|
|||||||
"Not Yet Amortized Amount": "Noch nicht amortisierter Betrag",
|
"Not Yet Amortized Amount": "Noch nicht amortisierter Betrag",
|
||||||
"Adjusted Amount": "Angepasster Betrag",
|
"Adjusted Amount": "Angepasster Betrag",
|
||||||
"Charged Off Amount": "Abgeschriebener Betrag",
|
"Charged Off Amount": "Abgeschriebener Betrag",
|
||||||
"Date": "Datum"
|
"Date": "Datum",
|
||||||
|
"Secure & Reliable": "Sicher und zuverlässig",
|
||||||
|
"Community Driven": "Gemeinschaftsorientiert",
|
||||||
|
"Global Impact": "Globale Auswirkungen"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2725,7 +2729,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Neuen Server hinzufügen",
|
"Add new server": "Neuen Server hinzufügen",
|
||||||
"Search Activity": "Suchaktivität"
|
"Search Activity": "Suchaktivität",
|
||||||
|
"Enter your username": "Geben Sie Ihren Benutzernamen ein",
|
||||||
|
"Enter your password": "Geben Sie Ihr Passwort ein"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Aktiv",
|
"Active": "Aktiv",
|
||||||
|
|||||||
@@ -506,6 +506,7 @@
|
|||||||
"Select All": "Select All",
|
"Select All": "Select All",
|
||||||
"Show less": "Show less",
|
"Show less": "Show less",
|
||||||
"Show more": "Show more",
|
"Show more": "Show more",
|
||||||
|
"Signing in...": "Signing in...",
|
||||||
"Staff": "Staff",
|
"Staff": "Staff",
|
||||||
"Staff Assignment History": "Staff Assignment History",
|
"Staff Assignment History": "Staff Assignment History",
|
||||||
"Subledger Account": "Subledger Account",
|
"Subledger Account": "Subledger Account",
|
||||||
@@ -1247,7 +1248,10 @@
|
|||||||
"Not Yet Amortized Amount": "Not Yet Amortized Amount",
|
"Not Yet Amortized Amount": "Not Yet Amortized Amount",
|
||||||
"Adjusted Amount": "Adjusted Amount",
|
"Adjusted Amount": "Adjusted Amount",
|
||||||
"Charged Off Amount": "Charged Off Amount",
|
"Charged Off Amount": "Charged Off Amount",
|
||||||
"Date": "Date"
|
"Date": "Date",
|
||||||
|
"Secure & Reliable": "Secure & Reliable",
|
||||||
|
"Community Driven": "Community Driven",
|
||||||
|
"Global Impact": "Global Impact"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2735,7 +2739,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Add new server",
|
"Add new server": "Add new server",
|
||||||
"Search Activity": "Search Activity"
|
"Search Activity": "Search Activity",
|
||||||
|
"Enter your username": "Enter your username",
|
||||||
|
"Enter your password": "Enter your password"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Active",
|
"Active": "Active",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Seleccionar todo",
|
"Select All": "Seleccionar todo",
|
||||||
"Show more": "Mostrar más",
|
"Show more": "Mostrar más",
|
||||||
"Show less": "Muestra menos",
|
"Show less": "Muestra menos",
|
||||||
|
"Signing in...": "Iniciando sesión",
|
||||||
"Staff": "Asesor",
|
"Staff": "Asesor",
|
||||||
"Staff Assignment History": "Historial de asignaciones de asesor",
|
"Staff Assignment History": "Historial de asignaciones de asesor",
|
||||||
"Subledger Account": "Cuenta auxiliar",
|
"Subledger Account": "Cuenta auxiliar",
|
||||||
@@ -1243,7 +1244,10 @@
|
|||||||
"Not Yet Amortized Amount": "Monto aun no amortizado",
|
"Not Yet Amortized Amount": "Monto aun no amortizado",
|
||||||
"Adjusted Amount": "Importe ajustado",
|
"Adjusted Amount": "Importe ajustado",
|
||||||
"Charged Off Amount": "Importe cancelado",
|
"Charged Off Amount": "Importe cancelado",
|
||||||
"Date": "Fecha"
|
"Date": "Fecha",
|
||||||
|
"Secure & Reliable": "Seguro y Confiable",
|
||||||
|
"Community Driven": "Impulsado por la Comunidad",
|
||||||
|
"Global Impact": "Impacto Global"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2724,7 +2728,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Agregar nuevo servidor",
|
"Add new server": "Agregar nuevo servidor",
|
||||||
"Search Activity": "Actividad de búsqueda"
|
"Search Activity": "Actividad de búsqueda",
|
||||||
|
"Enter your username": "Ingrese su nombre de usuario",
|
||||||
|
"Enter your password": "Ingrese su contraseña"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Activo",
|
"Active": "Activo",
|
||||||
@@ -2912,7 +2918,6 @@
|
|||||||
"Create Recurring Deposits Account": "Crear cuenta de depósitos recurrentes",
|
"Create Recurring Deposits Account": "Crear cuenta de depósitos recurrentes",
|
||||||
"Create Report": "Crear reporte",
|
"Create Report": "Crear reporte",
|
||||||
"Create SMS Campaign": "Crear campaña de SMS",
|
"Create SMS Campaign": "Crear campaña de SMS",
|
||||||
"Create Savings Product": "Crear producto de ahorro",
|
|
||||||
"Create Savings Account": "Crear Cuenta de Ahorros",
|
"Create Savings Account": "Crear Cuenta de Ahorros",
|
||||||
"Create Self Service User": "Crear usuario de autoservicio",
|
"Create Self Service User": "Crear usuario de autoservicio",
|
||||||
"Create Share Product": "Crear producto de Acciones",
|
"Create Share Product": "Crear producto de Acciones",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Seleccionar todo",
|
"Select All": "Seleccionar todo",
|
||||||
"Show more": "Mostrar más",
|
"Show more": "Mostrar más",
|
||||||
"Show less": "Muestra menos",
|
"Show less": "Muestra menos",
|
||||||
|
"Signing in...": "Iniciando sesión",
|
||||||
"Staff": "Asesor",
|
"Staff": "Asesor",
|
||||||
"Staff Assignment History": "Historial de asignaciones de asesor",
|
"Staff Assignment History": "Historial de asignaciones de asesor",
|
||||||
"Subledger Account": "Cuenta auxiliar",
|
"Subledger Account": "Cuenta auxiliar",
|
||||||
@@ -1242,7 +1243,10 @@
|
|||||||
"Not Yet Amortized Amount": "Monto aun no amortizado",
|
"Not Yet Amortized Amount": "Monto aun no amortizado",
|
||||||
"Adjusted Amount": "Importe ajustado",
|
"Adjusted Amount": "Importe ajustado",
|
||||||
"Charged Off Amount": "Importe cancelado",
|
"Charged Off Amount": "Importe cancelado",
|
||||||
"Date": "Fecha"
|
"Date": "Fecha",
|
||||||
|
"Secure & Reliable": "Seguro y Confiable",
|
||||||
|
"Community Driven": "Impulsado por la Comunidad",
|
||||||
|
"Global Impact": "Impacto Global"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2727,7 +2731,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Agregar nuevo servidor",
|
"Add new server": "Agregar nuevo servidor",
|
||||||
"Search Activity": "Actividad de búsqueda"
|
"Search Activity": "Actividad de búsqueda",
|
||||||
|
"Enter your username": "Ingrese su nombre de usuario",
|
||||||
|
"Enter your password": "Ingrese su contraseña"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Activo",
|
"Active": "Activo",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Tout sélectionner",
|
"Select All": "Tout sélectionner",
|
||||||
"Show more": "Montre plus",
|
"Show more": "Montre plus",
|
||||||
"Show less": "Montrer moins",
|
"Show less": "Montrer moins",
|
||||||
|
"Signing in...": "Connexion",
|
||||||
"Staff": "Personnel",
|
"Staff": "Personnel",
|
||||||
"Staff Assignment History": "Historique des affectations du personnel",
|
"Staff Assignment History": "Historique des affectations du personnel",
|
||||||
"Subledger Account": "Compte auxiliaire",
|
"Subledger Account": "Compte auxiliaire",
|
||||||
@@ -1243,7 +1244,10 @@
|
|||||||
"Not Yet Amortized Amount": "Montant pas encore amorti",
|
"Not Yet Amortized Amount": "Montant pas encore amorti",
|
||||||
"Adjusted Amount": "Montant ajusté",
|
"Adjusted Amount": "Montant ajusté",
|
||||||
"Charged Off Amount": "Montant radié",
|
"Charged Off Amount": "Montant radié",
|
||||||
"Date": "Date"
|
"Date": "Date",
|
||||||
|
"Secure & Reliable": "Sécurisé et fiable",
|
||||||
|
"Community Driven": "Axé sur la communauté",
|
||||||
|
"Global Impact": "Impact mondial"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2723,7 +2727,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Ajouter un nouveau serveur",
|
"Add new server": "Ajouter un nouveau serveur",
|
||||||
"Search Activity": "Activité de recherche"
|
"Search Activity": "Activité de recherche",
|
||||||
|
"Enter your username": "Saisissez votre nom d'utilisateur",
|
||||||
|
"Enter your password": "Saisissez votre mot de passe"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Actif",
|
"Active": "Actif",
|
||||||
@@ -2911,7 +2917,6 @@
|
|||||||
"Create Recurring Deposits Account": "Créer un compte de dépôts récurrents",
|
"Create Recurring Deposits Account": "Créer un compte de dépôts récurrents",
|
||||||
"Create Report": "Creer un rapport",
|
"Create Report": "Creer un rapport",
|
||||||
"Create SMS Campaign": "Créer une campagne SMS",
|
"Create SMS Campaign": "Créer une campagne SMS",
|
||||||
"Create Savings Product": "Créer un produit d'épargne",
|
|
||||||
"Create Savings Account": "Créer un compte d'épargne",
|
"Create Savings Account": "Créer un compte d'épargne",
|
||||||
"Create Self Service User": "Créer un utilisateur libre-service",
|
"Create Self Service User": "Créer un utilisateur libre-service",
|
||||||
"Create Share Product": "Créer un produit partagé",
|
"Create Share Product": "Créer un produit partagé",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Seleziona tutto",
|
"Select All": "Seleziona tutto",
|
||||||
"Show more": "Mostra di più",
|
"Show more": "Mostra di più",
|
||||||
"Show less": "Mostra meno",
|
"Show less": "Mostra meno",
|
||||||
|
"Signing in...": "Accesso",
|
||||||
"Staff": "Personale",
|
"Staff": "Personale",
|
||||||
"Staff Assignment History": "Cronologia delle assegnazioni del personale",
|
"Staff Assignment History": "Cronologia delle assegnazioni del personale",
|
||||||
"Subledger Account": "Conto secondario",
|
"Subledger Account": "Conto secondario",
|
||||||
@@ -1243,7 +1244,10 @@
|
|||||||
"Not Yet Amortized Amount": "Importo non ancora ammortizzato",
|
"Not Yet Amortized Amount": "Importo non ancora ammortizzato",
|
||||||
"Adjusted Amount": "Importo rettificato",
|
"Adjusted Amount": "Importo rettificato",
|
||||||
"Charged Off Amount": "Importo stornato",
|
"Charged Off Amount": "Importo stornato",
|
||||||
"Date": "Data"
|
"Date": "Data",
|
||||||
|
"Secure & Reliable": "Sicuro e affidabile",
|
||||||
|
"Community Driven": "Guidato dalla comunità",
|
||||||
|
"Global Impact": "Impatto globale"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2723,7 +2727,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Aggiungi nuovo server",
|
"Add new server": "Aggiungi nuovo server",
|
||||||
"Search Activity": "Attività di ricerca"
|
"Search Activity": "Attività di ricerca",
|
||||||
|
"Enter your username": "Inserisci il tuo nome utente",
|
||||||
|
"Enter your password": "Inserisci la tua password"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Attivo",
|
"Active": "Attivo",
|
||||||
@@ -2911,7 +2917,6 @@
|
|||||||
"Create Recurring Deposits Account": "Crea un conto per depositi ricorrenti",
|
"Create Recurring Deposits Account": "Crea un conto per depositi ricorrenti",
|
||||||
"Create Report": "Crea rapporto",
|
"Create Report": "Crea rapporto",
|
||||||
"Create SMS Campaign": "Crea campagna SMS",
|
"Create SMS Campaign": "Crea campagna SMS",
|
||||||
"Create Savings Product": "Crea prodotto salvabile",
|
|
||||||
"Create Savings Account": "Crea un conto di risparmio",
|
"Create Savings Account": "Crea un conto di risparmio",
|
||||||
"Create Self Service User": "Crea utente self-service",
|
"Create Self Service User": "Crea utente self-service",
|
||||||
"Create Share Product": "Crea prodotto condiviso",
|
"Create Share Product": "Crea prodotto condiviso",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "모두 선택",
|
"Select All": "모두 선택",
|
||||||
"Show more": "자세히보기",
|
"Show more": "자세히보기",
|
||||||
"Show less": "간략히 보기",
|
"Show less": "간략히 보기",
|
||||||
|
"Signing in...": "로그인",
|
||||||
"Staff": "직원",
|
"Staff": "직원",
|
||||||
"Staff Assignment History": "직원 배정 내역",
|
"Staff Assignment History": "직원 배정 내역",
|
||||||
"Subledger Account": "보조원장 계정",
|
"Subledger Account": "보조원장 계정",
|
||||||
@@ -1244,7 +1245,10 @@
|
|||||||
"Not Yet Amortized Amount": "Not Yet Amortized Amount",
|
"Not Yet Amortized Amount": "Not Yet Amortized Amount",
|
||||||
"Adjusted Amount": "조정된 금액",
|
"Adjusted Amount": "조정된 금액",
|
||||||
"Charged Off Amount": "상각 금액",
|
"Charged Off Amount": "상각 금액",
|
||||||
"Date": "날짜"
|
"Date": "날짜",
|
||||||
|
"Secure & Reliable": "안전하고 신뢰할 수 있음",
|
||||||
|
"Community Driven": "커뮤니티 주도형",
|
||||||
|
"Global Impact": "글로벌 영향"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2723,7 +2727,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "새 서버 추가",
|
"Add new server": "새 서버 추가",
|
||||||
"Search Activity": "검색 활동"
|
"Search Activity": "검색 활동",
|
||||||
|
"Enter your username": "사용자 이름을 입력하세요",
|
||||||
|
"Enter your password": "비밀번호를 입력하세요"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "활동적인",
|
"Active": "활동적인",
|
||||||
@@ -2911,7 +2917,6 @@
|
|||||||
"Create Recurring Deposits Account": "정기예금 계좌 생성",
|
"Create Recurring Deposits Account": "정기예금 계좌 생성",
|
||||||
"Create Report": "보고서 작성",
|
"Create Report": "보고서 작성",
|
||||||
"Create SMS Campaign": "SMS 캠페인 생성",
|
"Create SMS Campaign": "SMS 캠페인 생성",
|
||||||
"Create Savings Product": "저장상품 생성",
|
|
||||||
"Create Savings Account": "저축 계좌 만들기",
|
"Create Savings Account": "저축 계좌 만들기",
|
||||||
"Create Self Service User": "셀프 서비스 사용자 생성",
|
"Create Self Service User": "셀프 서비스 사용자 생성",
|
||||||
"Create Share Product": "공유상품 생성",
|
"Create Share Product": "공유상품 생성",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Pasirinkti viską",
|
"Select All": "Pasirinkti viską",
|
||||||
"Show more": "Rodyti daugiau",
|
"Show more": "Rodyti daugiau",
|
||||||
"Show less": "Rodyti mažiau",
|
"Show less": "Rodyti mažiau",
|
||||||
|
"Signing in...": "Prisijungimas",
|
||||||
"Staff": "Personalas",
|
"Staff": "Personalas",
|
||||||
"Staff Assignment History": "Darbuotojų paskyrimo istorija",
|
"Staff Assignment History": "Darbuotojų paskyrimo istorija",
|
||||||
"Subledger Account": "Subledger sąskaita",
|
"Subledger Account": "Subledger sąskaita",
|
||||||
@@ -1243,7 +1244,10 @@
|
|||||||
"Not Yet Amortized Amount": "Dar neamortizuota suma",
|
"Not Yet Amortized Amount": "Dar neamortizuota suma",
|
||||||
"Adjusted Amount": "Koreguota suma",
|
"Adjusted Amount": "Koreguota suma",
|
||||||
"Charged Off Amount": "Nurašyta suma",
|
"Charged Off Amount": "Nurašyta suma",
|
||||||
"Date": "Data"
|
"Date": "Data",
|
||||||
|
"Secure & Reliable": "Saugu ir patikima",
|
||||||
|
"Community Driven": "Bendruomenės varomas",
|
||||||
|
"Global Impact": "Pasaulinis poveikis"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2725,7 +2729,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Pridėti naują serverį",
|
"Add new server": "Pridėti naują serverį",
|
||||||
"Search Activity": "Paieškos veikla"
|
"Search Activity": "Paieškos veikla",
|
||||||
|
"Enter your username": "Įveskite savo vartotojo vardą",
|
||||||
|
"Enter your password": "Įveskite slaptažodį"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Aktyvus",
|
"Active": "Aktyvus",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Izvēlēties visus",
|
"Select All": "Izvēlēties visus",
|
||||||
"Show more": "Parādīt vairāk",
|
"Show more": "Parādīt vairāk",
|
||||||
"Show less": "Rādīt mazāk",
|
"Show less": "Rādīt mazāk",
|
||||||
|
"Signing in...": "Pierakstīšanās",
|
||||||
"Staff": "Personāls",
|
"Staff": "Personāls",
|
||||||
"Staff Assignment History": "Personāla norīkošanas vēsture",
|
"Staff Assignment History": "Personāla norīkošanas vēsture",
|
||||||
"Subledger Account": "Subledger konts",
|
"Subledger Account": "Subledger konts",
|
||||||
@@ -1243,7 +1244,10 @@
|
|||||||
"Not Yet Amortized Amount": "Vel nav amortizeta summa",
|
"Not Yet Amortized Amount": "Vel nav amortizeta summa",
|
||||||
"Adjusted Amount": "Koriģētā summa",
|
"Adjusted Amount": "Koriģētā summa",
|
||||||
"Charged Off Amount": "Norakstītā summa",
|
"Charged Off Amount": "Norakstītā summa",
|
||||||
"Date": "Datums"
|
"Date": "Datums",
|
||||||
|
"Secure & Reliable": "Drošs un uzticams",
|
||||||
|
"Community Driven": "Kopienas vadīts",
|
||||||
|
"Global Impact": "Globālā ietekme"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2723,7 +2727,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Pievienot jaunu serveri",
|
"Add new server": "Pievienot jaunu serveri",
|
||||||
"Search Activity": "Meklēšanas darbība"
|
"Search Activity": "Meklēšanas darbība",
|
||||||
|
"Enter your username": "Ievadiet savu lietotājvārdu",
|
||||||
|
"Enter your password": "Ievadiet savu paroli"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Aktīvs",
|
"Active": "Aktīvs",
|
||||||
@@ -2803,7 +2809,6 @@
|
|||||||
"All Recurring Deposits": "Visi atkārtotie noguldījumi",
|
"All Recurring Deposits": "Visi atkārtotie noguldījumi",
|
||||||
"All Savings": "Visi ietaupījumi",
|
"All Savings": "Visi ietaupījumi",
|
||||||
"Savings Account Details": "Krājkonta informācija",
|
"Savings Account Details": "Krājkonta informācija",
|
||||||
"Create Savings Product": "Izveidot uzkrājumu produktu",
|
|
||||||
"Allocate Cash": "Piešķirt skaidru naudu",
|
"Allocate Cash": "Piešķirt skaidru naudu",
|
||||||
"Allows you to create new fixed deposit product": "Šī opcija ļauj izveidot jaunu fiksētā depozīta produktu.",
|
"Allows you to create new fixed deposit product": "Šī opcija ļauj izveidot jaunu fiksētā depozīta produktu.",
|
||||||
"This option allows you to create new users in your organization": "Šī opcija ļauj izveidot jaunus lietotājus jūsu organizācijā",
|
"This option allows you to create new users in your organization": "Šī opcija ļauj izveidot jaunus lietotājus jūsu organizācijā",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "सबै छान्नु",
|
"Select All": "सबै छान्नु",
|
||||||
"Show more": "थप देखाउनुहोस्",
|
"Show more": "थप देखाउनुहोस्",
|
||||||
"Show less": "कम देखाउनुहोस्",
|
"Show less": "कम देखाउनुहोस्",
|
||||||
|
"Signing in...": "साइन इन गर्दै",
|
||||||
"Staff": "कर्मचारी",
|
"Staff": "कर्मचारी",
|
||||||
"Staff Assignment History": "कर्मचारी असाइनमेन्ट इतिहास",
|
"Staff Assignment History": "कर्मचारी असाइनमेन्ट इतिहास",
|
||||||
"Subledger Account": "Subledger खाता",
|
"Subledger Account": "Subledger खाता",
|
||||||
@@ -1241,7 +1242,10 @@
|
|||||||
"Not Yet Amortized Amount": "Not Yet Amortized Amount",
|
"Not Yet Amortized Amount": "Not Yet Amortized Amount",
|
||||||
"Adjusted Amount": "समायोजित रकम",
|
"Adjusted Amount": "समायोजित रकम",
|
||||||
"Charged Off Amount": "चार्ज गरिएको रकम",
|
"Charged Off Amount": "चार्ज गरिएको रकम",
|
||||||
"Date": "मिति"
|
"Date": "मिति",
|
||||||
|
"Secure & Reliable": "सुरक्षित र भरपर्दो",
|
||||||
|
"Community Driven": "समुदाय संचालित",
|
||||||
|
"Global Impact": "विश्वव्यापी प्रभाव"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2721,7 +2725,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "नयाँ सर्भर थप्नुहोस्",
|
"Add new server": "नयाँ सर्भर थप्नुहोस्",
|
||||||
"Search Activity": "खोज गतिविधि"
|
"Search Activity": "खोज गतिविधि",
|
||||||
|
"Enter your username": "आफ्नो प्रयोगकर्ता नाम प्रविष्ट गर्नुहोस्",
|
||||||
|
"Enter your password": "आफ्नो पासवर्ड प्रविष्ट गर्नुहोस्"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "सक्रिय",
|
"Active": "सक्रिय",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Selecionar tudo",
|
"Select All": "Selecionar tudo",
|
||||||
"Show more": "Mostre mais",
|
"Show more": "Mostre mais",
|
||||||
"Show less": "Mostre menos",
|
"Show less": "Mostre menos",
|
||||||
|
"Signing in...": "Entrando",
|
||||||
"Staff": "Funcionários",
|
"Staff": "Funcionários",
|
||||||
"Staff Assignment History": "Histórico de Atribuição de Pessoal",
|
"Staff Assignment History": "Histórico de Atribuição de Pessoal",
|
||||||
"Subledger Account": "Conta auxiliar",
|
"Subledger Account": "Conta auxiliar",
|
||||||
@@ -1243,7 +1244,10 @@
|
|||||||
"Not Yet Amortized Amount": "Valor ainda nao amortizado",
|
"Not Yet Amortized Amount": "Valor ainda nao amortizado",
|
||||||
"Adjusted Amount": "Valor ajustado",
|
"Adjusted Amount": "Valor ajustado",
|
||||||
"Charged Off Amount": "Valor baixado",
|
"Charged Off Amount": "Valor baixado",
|
||||||
"Date": "Data"
|
"Date": "Data",
|
||||||
|
"Secure & Reliable": "Seguro e confiável",
|
||||||
|
"Community Driven": "Impulsionado pela comunidade",
|
||||||
|
"Global Impact": "Impacto global"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2723,7 +2727,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Adicionar novo servidor",
|
"Add new server": "Adicionar novo servidor",
|
||||||
"Search Activity": "Atividade de pesquisa"
|
"Search Activity": "Atividade de pesquisa",
|
||||||
|
"Enter your username": "Digite seu nome de usuário",
|
||||||
|
"Enter your password": "Digite sua senha"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Ativo",
|
"Active": "Ativo",
|
||||||
@@ -2911,7 +2917,6 @@
|
|||||||
"Create Recurring Deposits Account": "Criar conta de depósitos recorrentes",
|
"Create Recurring Deposits Account": "Criar conta de depósitos recorrentes",
|
||||||
"Create Report": "Criar relatório",
|
"Create Report": "Criar relatório",
|
||||||
"Create SMS Campaign": "Criar campanha SMS",
|
"Create SMS Campaign": "Criar campanha SMS",
|
||||||
"Create Savings Product": "Criar produto salvador",
|
|
||||||
"Create Savings Account": "Criar conta poupança",
|
"Create Savings Account": "Criar conta poupança",
|
||||||
"Create Self Service User": "Criar usuário de autoatendimento",
|
"Create Self Service User": "Criar usuário de autoatendimento",
|
||||||
"Create Share Product": "Criar produto compartilhado",
|
"Create Share Product": "Criar produto compartilhado",
|
||||||
|
|||||||
@@ -505,6 +505,7 @@
|
|||||||
"Select All": "Chagua Zote",
|
"Select All": "Chagua Zote",
|
||||||
"Show more": "Onyesha zaidi",
|
"Show more": "Onyesha zaidi",
|
||||||
"Show less": "Onyesha kidogo",
|
"Show less": "Onyesha kidogo",
|
||||||
|
"Signing in...": "Kuingia",
|
||||||
"Staff": "Wafanyakazi",
|
"Staff": "Wafanyakazi",
|
||||||
"Staff Assignment History": "Historia ya Kazi ya Wafanyakazi",
|
"Staff Assignment History": "Historia ya Kazi ya Wafanyakazi",
|
||||||
"Subledger Account": "Akaunti ya Subleja",
|
"Subledger Account": "Akaunti ya Subleja",
|
||||||
@@ -1241,7 +1242,10 @@
|
|||||||
"Not Yet Amortized Amount": "Kiasi Bado Kijaondolewa",
|
"Not Yet Amortized Amount": "Kiasi Bado Kijaondolewa",
|
||||||
"Adjusted Amount": "Kiasi Kilichorekebishwa",
|
"Adjusted Amount": "Kiasi Kilichorekebishwa",
|
||||||
"Charged Off Amount": "Kiasi Kilichofutwa",
|
"Charged Off Amount": "Kiasi Kilichofutwa",
|
||||||
"Date": "Tarehe"
|
"Date": "Tarehe",
|
||||||
|
"Secure & Reliable": "Salama na ya Kuaminika",
|
||||||
|
"Community Driven": "Inaendeshwa na Jumuiya",
|
||||||
|
"Global Impact": "Athari ya Kimataifa"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"accounting": {
|
"accounting": {
|
||||||
@@ -2721,7 +2725,9 @@
|
|||||||
},
|
},
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"Add new server": "Ongeza seva mpya",
|
"Add new server": "Ongeza seva mpya",
|
||||||
"Search Activity": "Shughuli ya Utafutaji"
|
"Search Activity": "Shughuli ya Utafutaji",
|
||||||
|
"Enter your username": "Ingiza jina lako la mtumiaji",
|
||||||
|
"Enter your password": "Ingiza nenosiri lako"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"Active": "Inayotumika",
|
"Active": "Inayotumika",
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
|
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Mono:300" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Mono:300" rel="stylesheet" />
|
||||||
<!-- Load environment variables -->
|
<!-- Load environment variables -->
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@use 'theme/mifosx-theme';
|
@use 'theme/mifosx-theme';
|
||||||
|
@use 'theme/material-web' as *;
|
||||||
@use 'theme/pictonblue-yellowgreen' as *;
|
@use 'theme/pictonblue-yellowgreen' as *;
|
||||||
@use 'theme/indigo-pink' as *;
|
@use 'theme/indigo-pink' as *;
|
||||||
@use 'theme/pink-bluegrey' as *;
|
@use 'theme/pink-bluegrey' as *;
|
||||||
@@ -24,6 +25,65 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Material Select Panel Styles (Material Design 3) =====
|
||||||
|
.mat-mdc-select-panel {
|
||||||
|
background: var(--md-sys-color-surface-container, #fff);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 4px #00000014,
|
||||||
|
0 4px 12px #0000000f,
|
||||||
|
0 8px 24px #00000014;
|
||||||
|
|
||||||
|
.mat-mdc-option {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 16px;
|
||||||
|
color: var(--md-sys-color-on-surface, #1a1c1e);
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #1074b914;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mat-mdc-option-active {
|
||||||
|
background-color: #1074b91f;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mdc-list-item--selected {
|
||||||
|
background-color: #1074b91f;
|
||||||
|
color: var(--md-sys-color-primary, #1074b9);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-list-item__primary-text {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark theme select panel
|
||||||
|
.dark-theme .mat-mdc-select-panel {
|
||||||
|
background: var(--md-sys-color-surface-container, #2c2f36);
|
||||||
|
|
||||||
|
.mat-mdc-option {
|
||||||
|
color: var(--md-sys-color-on-surface, #e2e2e5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #5ba2ec14;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mat-mdc-option-active {
|
||||||
|
background-color: #5ba2ec1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mdc-list-item--selected {
|
||||||
|
background-color: #5ba2ec1f;
|
||||||
|
color: var(--md-sys-color-primary, #5ba2ec);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.scrollbar-styling {
|
.scrollbar-styling {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: #889 #f1f1f1;
|
scrollbar-color: #889 #f1f1f1;
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ body.dark-theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.copy-label {
|
.copy-label {
|
||||||
color: white !important;
|
color: var(--md-sys-color-on-surface-variant, #c4c6d0);
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
|
|||||||
@@ -0,0 +1,317 @@
|
|||||||
|
/**
|
||||||
|
* Material Web Components Global Styles
|
||||||
|
* These styles apply to @material/web components throughout the app
|
||||||
|
* Following Material Design 3 specifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Material Web Button Components base styling
|
||||||
|
md-filled-button,
|
||||||
|
md-outlined-button,
|
||||||
|
md-elevated-button,
|
||||||
|
md-text-button,
|
||||||
|
md-filled-tonal-button {
|
||||||
|
// Ensure buttons are properly displayed
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
// Ensure minimum height for consistency
|
||||||
|
min-height: 40px;
|
||||||
|
|
||||||
|
// Typography
|
||||||
|
--md-filled-button-label-text-size: 14px;
|
||||||
|
--md-filled-button-label-text-weight: 500;
|
||||||
|
--md-filled-button-label-text-font: 'Roboto', sans-serif;
|
||||||
|
--md-outlined-button-label-text-size: 14px;
|
||||||
|
--md-outlined-button-label-text-weight: 500;
|
||||||
|
--md-outlined-button-label-text-font: 'Roboto', sans-serif;
|
||||||
|
--md-elevated-button-label-text-size: 14px;
|
||||||
|
--md-elevated-button-label-text-weight: 500;
|
||||||
|
--md-elevated-button-label-text-font: 'Roboto', sans-serif;
|
||||||
|
--md-text-button-label-text-size: 14px;
|
||||||
|
--md-text-button-label-text-weight: 500;
|
||||||
|
--md-text-button-label-text-font: 'Roboto', sans-serif;
|
||||||
|
--md-filled-tonal-button-label-text-size: 14px;
|
||||||
|
--md-filled-tonal-button-label-text-weight: 500;
|
||||||
|
--md-filled-tonal-button-label-text-font: 'Roboto', sans-serif;
|
||||||
|
// Border radius (Material Design 3 uses 20px for buttons)
|
||||||
|
--md-filled-button-container-shape: 20px;
|
||||||
|
--md-outlined-button-container-shape: 20px;
|
||||||
|
--md-elevated-button-container-shape: 20px;
|
||||||
|
--md-text-button-container-shape: 20px;
|
||||||
|
--md-filled-tonal-button-container-shape: 20px;
|
||||||
|
|
||||||
|
// Padding
|
||||||
|
--md-filled-button-leading-space: 24px;
|
||||||
|
--md-filled-button-trailing-space: 24px;
|
||||||
|
--md-outlined-button-leading-space: 24px;
|
||||||
|
--md-outlined-button-trailing-space: 24px;
|
||||||
|
--md-elevated-button-leading-space: 24px;
|
||||||
|
--md-elevated-button-trailing-space: 24px;
|
||||||
|
--md-text-button-leading-space: 24px;
|
||||||
|
--md-text-button-trailing-space: 24px;
|
||||||
|
--md-filled-tonal-button-leading-space: 24px;
|
||||||
|
--md-filled-tonal-button-trailing-space: 24px;
|
||||||
|
|
||||||
|
// Apply theme colors using Material Design tokens
|
||||||
|
--md-sys-color-primary: #1074b9;
|
||||||
|
--md-sys-color-on-primary: #fff;
|
||||||
|
--md-sys-color-surface-container-highest: #e1e2ec;
|
||||||
|
--md-sys-color-on-surface: #303135;
|
||||||
|
--md-sys-color-on-surface-variant: #44474e;
|
||||||
|
|
||||||
|
// ========== FILLED BUTTON ==========
|
||||||
|
// Normal state
|
||||||
|
--md-filled-button-container-color: #1074b9;
|
||||||
|
--md-filled-button-label-text-color: #fff;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-filled-button-hover-state-layer-color: #fff;
|
||||||
|
--md-filled-button-hover-state-layer-opacity: 8%;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-filled-button-focus-state-layer-color: #fff;
|
||||||
|
--md-filled-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-filled-button-pressed-state-layer-color: #fff;
|
||||||
|
--md-filled-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-filled-button-disabled-container-color: #e0e0e0;
|
||||||
|
--md-filled-button-disabled-container-opacity: 1;
|
||||||
|
--md-filled-button-disabled-label-text-color: #9e9e9e;
|
||||||
|
--md-filled-button-disabled-label-text-opacity: 1;
|
||||||
|
|
||||||
|
// ========== OUTLINED BUTTON ==========
|
||||||
|
// Normal state
|
||||||
|
--md-outlined-button-outline-color: #1074b9;
|
||||||
|
--md-outlined-button-label-text-color: #1074b9;
|
||||||
|
--md-outlined-button-outline-width: 1px;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-outlined-button-hover-state-layer-color: #1074b9;
|
||||||
|
--md-outlined-button-hover-state-layer-opacity: 8%;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-outlined-button-focus-state-layer-color: #1074b9;
|
||||||
|
--md-outlined-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-outlined-button-pressed-state-layer-color: #1074b9;
|
||||||
|
--md-outlined-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-outlined-button-disabled-outline-color: #e0e0e0;
|
||||||
|
--md-outlined-button-disabled-outline-opacity: 1;
|
||||||
|
--md-outlined-button-disabled-label-text-color: #9e9e9e;
|
||||||
|
--md-outlined-button-disabled-label-text-opacity: 1;
|
||||||
|
|
||||||
|
// ========== ELEVATED BUTTON ==========
|
||||||
|
// Normal state
|
||||||
|
--md-elevated-button-container-color: #f5f5f5;
|
||||||
|
--md-elevated-button-label-text-color: #1074b9;
|
||||||
|
--md-elevated-button-container-elevation: 1;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-elevated-button-hover-state-layer-color: #1074b9;
|
||||||
|
--md-elevated-button-hover-state-layer-opacity: 8%;
|
||||||
|
--md-elevated-button-hover-container-elevation: 2;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-elevated-button-focus-state-layer-color: #1074b9;
|
||||||
|
--md-elevated-button-focus-state-layer-opacity: 12%;
|
||||||
|
--md-elevated-button-focus-container-elevation: 1;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-elevated-button-pressed-state-layer-color: #1074b9;
|
||||||
|
--md-elevated-button-pressed-state-layer-opacity: 12%;
|
||||||
|
--md-elevated-button-pressed-container-elevation: 1;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-elevated-button-disabled-container-color: #e0e0e0;
|
||||||
|
--md-elevated-button-disabled-container-opacity: 1;
|
||||||
|
--md-elevated-button-disabled-container-elevation: 0;
|
||||||
|
--md-elevated-button-disabled-label-text-color: #9e9e9e;
|
||||||
|
--md-elevated-button-disabled-label-text-opacity: 1;
|
||||||
|
|
||||||
|
// ========== TEXT BUTTON ==========
|
||||||
|
// Normal state
|
||||||
|
--md-text-button-label-text-color: #1074b9;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-text-button-hover-state-layer-color: #1074b9;
|
||||||
|
--md-text-button-hover-state-layer-opacity: 8%;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-text-button-focus-state-layer-color: #1074b9;
|
||||||
|
--md-text-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-text-button-pressed-state-layer-color: #1074b9;
|
||||||
|
--md-text-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-text-button-disabled-label-text-color: #9e9e9e;
|
||||||
|
--md-text-button-disabled-label-text-opacity: 1;
|
||||||
|
|
||||||
|
// ========== TONAL BUTTON ==========
|
||||||
|
// Normal state
|
||||||
|
--md-filled-tonal-button-container-color: #d1e4ff;
|
||||||
|
--md-filled-tonal-button-label-text-color: #001d36;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-filled-tonal-button-hover-state-layer-color: #001d36;
|
||||||
|
--md-filled-tonal-button-hover-state-layer-opacity: 8%;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-filled-tonal-button-focus-state-layer-color: #001d36;
|
||||||
|
--md-filled-tonal-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-filled-tonal-button-pressed-state-layer-color: #001d36;
|
||||||
|
--md-filled-tonal-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-filled-tonal-button-disabled-container-color: #e0e0e0;
|
||||||
|
--md-filled-tonal-button-disabled-container-opacity: 1;
|
||||||
|
--md-filled-tonal-button-disabled-label-text-color: #9e9e9e;
|
||||||
|
--md-filled-tonal-button-disabled-label-text-opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark theme support
|
||||||
|
.dark-theme {
|
||||||
|
--mat-app-text-color: #e2e2e5;
|
||||||
|
--md-sys-color-surface: #424242;
|
||||||
|
--md-sys-color-surface-container-low: #424242;
|
||||||
|
--md-sys-color-on-surface: #e2e2e5;
|
||||||
|
|
||||||
|
// Icons Prefix - Global definition for dark theme
|
||||||
|
--md-icon-prefix-label-text-color: #e3e2e6;
|
||||||
|
|
||||||
|
md-filled-button,
|
||||||
|
md-outlined-button,
|
||||||
|
md-elevated-button,
|
||||||
|
md-text-button,
|
||||||
|
md-filled-tonal-button {
|
||||||
|
--md-sys-color-primary: #5ba2ec;
|
||||||
|
--md-sys-color-on-primary: #001d36;
|
||||||
|
--md-sys-color-surface-container-highest: #42464e;
|
||||||
|
--md-sys-color-on-surface: #424242;
|
||||||
|
--md-sys-color-on-surface-variant: #c4c6d0;
|
||||||
|
// ========== FILLED BUTTON DARK ==========
|
||||||
|
// Normal state
|
||||||
|
--md-filled-button-container-color: #5ba2ec;
|
||||||
|
--md-filled-button-label-text-color: #fff;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-filled-button-hover-state-layer-color: #001d36;
|
||||||
|
--md-filled-button-hover-state-layer-opacity: 8%;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-filled-button-focus-state-layer-color: #001d36;
|
||||||
|
--md-filled-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-filled-button-pressed-state-layer-color: #001d36;
|
||||||
|
--md-filled-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-filled-button-disabled-container-color: #e3e2e6;
|
||||||
|
--md-filled-button-disabled-container-opacity: 1;
|
||||||
|
--md-filled-button-disabled-label-text-color: #44474e;
|
||||||
|
--md-filled-button-disabled-label-text-opacity: 1;
|
||||||
|
|
||||||
|
// ========== OUTLINED BUTTON DARK ==========
|
||||||
|
// Normal state
|
||||||
|
--md-outlined-button-outline-color: #5ba2ec;
|
||||||
|
--md-outlined-button-label-text-color: #5ba2ec;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-outlined-button-hover-state-layer-color: #5ba2ec;
|
||||||
|
--md-outlined-button-hover-state-layer-opacity: 8%;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-outlined-button-focus-state-layer-color: #5ba2ec;
|
||||||
|
--md-outlined-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-outlined-button-pressed-state-layer-color: #5ba2ec;
|
||||||
|
--md-outlined-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-outlined-button-disabled-outline-color: #e3e2e6;
|
||||||
|
--md-outlined-button-disabled-outline-opacity: 1;
|
||||||
|
--md-outlined-button-disabled-label-text-color: #44474e;
|
||||||
|
--md-outlined-button-disabled-label-text-opacity: 1;
|
||||||
|
|
||||||
|
// ========== ELEVATED BUTTON DARK ==========
|
||||||
|
// Normal state
|
||||||
|
--md-elevated-button-container-color: #2c2f36;
|
||||||
|
--md-elevated-button-label-text-color: #5ba2ec;
|
||||||
|
--md-elevated-button-container-elevation: 1;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-elevated-button-hover-state-layer-color: #5ba2ec;
|
||||||
|
--md-elevated-button-hover-state-layer-opacity: 8%;
|
||||||
|
--md-elevated-button-hover-container-elevation: 2;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-elevated-button-focus-state-layer-color: #5ba2ec;
|
||||||
|
--md-elevated-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-elevated-button-pressed-state-layer-color: #5ba2ec;
|
||||||
|
--md-elevated-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-elevated-button-disabled-container-color: #e3e2e6;
|
||||||
|
--md-elevated-button-disabled-container-opacity: 1;
|
||||||
|
--md-elevated-button-disabled-container-elevation: 0;
|
||||||
|
--md-elevated-button-disabled-label-text-color: #44474e;
|
||||||
|
--md-elevated-button-disabled-label-text-opacity: 1;
|
||||||
|
|
||||||
|
// ========== TEXT BUTTON DARK ==========
|
||||||
|
// Normal state
|
||||||
|
--md-text-button-label-text-color: #5ba2ec;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-text-button-hover-state-layer-color: #5ba2ec;
|
||||||
|
--md-text-button-hover-state-layer-opacity: 8%;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-text-button-focus-state-layer-color: #5ba2ec;
|
||||||
|
--md-text-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-text-button-pressed-state-layer-color: #5ba2ec;
|
||||||
|
--md-text-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-text-button-disabled-label-text-color: #e3e2e6;
|
||||||
|
--md-text-button-disabled-label-text-opacity: 1;
|
||||||
|
|
||||||
|
// ========== TONAL BUTTON DARK ==========
|
||||||
|
// Normal state
|
||||||
|
--md-filled-tonal-button-container-color: #003a6b;
|
||||||
|
--md-filled-tonal-button-label-text-color: #d1e4ff;
|
||||||
|
|
||||||
|
// Hover state
|
||||||
|
--md-filled-tonal-button-hover-state-layer-color: #d1e4ff;
|
||||||
|
--md-filled-tonal-button-hover-state-layer-opacity: 8%;
|
||||||
|
|
||||||
|
// Focus state
|
||||||
|
--md-filled-tonal-button-focus-state-layer-color: #d1e4ff;
|
||||||
|
--md-filled-tonal-button-focus-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Pressed state
|
||||||
|
--md-filled-tonal-button-pressed-state-layer-color: #d1e4ff;
|
||||||
|
--md-filled-tonal-button-pressed-state-layer-opacity: 12%;
|
||||||
|
|
||||||
|
// Disabled state
|
||||||
|
--md-filled-tonal-button-disabled-container-color: #e3e2e6;
|
||||||
|
--md-filled-tonal-button-disabled-container-opacity: 1;
|
||||||
|
--md-filled-tonal-button-disabled-label-text-color: #e3e2e6;
|
||||||
|
--md-filled-tonal-button-disabled-label-text-opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user