Skip to content

Commit 4f67b63

Browse files
refactor(login): address review feedback on PR scope
Significantly reduced the number of changed files based on @ibacher's comments. Removed all framework modifications and simplified the background image handling to use direct URLs. All the configurable login features are still there - just implemented more cleanly now. Should be much easier to review and merge.
1 parent 40b4707 commit 4f67b63

File tree

11 files changed

+1222
-72
lines changed

11 files changed

+1222
-72
lines changed
Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,81 @@
11
# openmrs-esm-login-app
22

3-
openmrs-esm-login-app is responsible for rendering the loading page,
4-
the login page, and the location picker.
3+
openmrs-esm-login-app is responsible for rendering the loading page, the login page, and the location picker.
4+
5+
## Configuration
6+
7+
The login page can be customized through configuration. This allows implementers to:
8+
9+
- Choose from multiple layouts (default or split-screen)
10+
- Customize backgrounds (colors, images, or gradients)
11+
- Brand the login page with custom titles, subtitles, and logos
12+
- Style the login card and buttons
13+
- Add custom links and help text
14+
- Configure the footer with additional logos
15+
16+
See the [configuration schema](src/config-schema.ts) for all available options.
17+
18+
## Configuration Examples
19+
20+
### Split-Screen Layout with Image Background
21+
22+
```json
23+
{
24+
"@openmrs/esm-login-app": {
25+
"layout": {
26+
"type": "split-screen",
27+
"columnPosition": "right"
28+
},
29+
"background": {
30+
"type": "image",
31+
"value": "https://example.com/hospital-bg.jpg"
32+
}
33+
}
34+
}
35+
```
36+
37+
### Color Background
38+
39+
```json
40+
{
41+
"@openmrs/esm-login-app": {
42+
"background": {
43+
"type": "color",
44+
"value": "#0066cc"
45+
}
46+
}
47+
}
48+
```
49+
50+
### Gradient Background
51+
52+
```json
53+
{
54+
"@openmrs/esm-login-app": {
55+
"background": {
56+
"type": "gradient",
57+
"value": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
58+
}
59+
}
60+
}
61+
```
62+
63+
### Custom Branding
64+
65+
```json
66+
{
67+
"@openmrs/esm-login-app": {
68+
"branding": {
69+
"title": "Welcome to My Clinic",
70+
"subtitle": "Electronic Medical Records System"
71+
},
72+
"logo": {
73+
"src": "https://example.com/logo.png",
74+
"alt": "My Clinic"
75+
},
76+
"button": {
77+
"backgroundColor": "#0071c5"
78+
}
79+
}
80+
}
81+
```

packages/apps/esm-login-app/__mocks__/config.mock.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,44 @@ export const mockConfig: ConfigSchema = {
2323
additionalLogos: [],
2424
},
2525
showPasswordOnSeparateScreen: true,
26+
background: {
27+
type: 'default',
28+
value: '',
29+
alt: 'Background Image',
30+
size: 'cover',
31+
position: 'center',
32+
repeat: 'no-repeat',
33+
attachment: 'scroll',
34+
overlay: {
35+
enabled: false,
36+
color: 'rgba(0, 0, 0, 0.3)',
37+
opacity: 0.3,
38+
blendMode: 'normal',
39+
},
40+
},
41+
layout: {
42+
type: 'default' as const,
43+
columnPosition: 'center' as const,
44+
showLogo: true,
45+
showFooter: true,
46+
},
47+
card: {
48+
backgroundColor: '',
49+
borderRadius: '',
50+
width: '',
51+
padding: '',
52+
boxShadow: '',
53+
},
54+
button: {
55+
backgroundColor: '',
56+
textColor: '',
57+
},
58+
branding: {
59+
title: '',
60+
subtitle: '',
61+
customText: '',
62+
helpText: '',
63+
contactEmail: '',
64+
customLinks: [],
65+
},
2666
};
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { useMemo } from 'react';
2+
import { useConfig } from '@openmrs/esm-framework';
3+
import { type ConfigSchema } from '../config-schema';
4+
import styles from './background-wrapper.scss';
5+
6+
interface BackgroundWrapperProps {
7+
children: React.ReactNode;
8+
}
9+
10+
const BackgroundWrapper: React.FC<BackgroundWrapperProps> = ({ children }) => {
11+
const config = useConfig<ConfigSchema>();
12+
const { layout, background } = config;
13+
14+
const backgroundStyles = useMemo(() => {
15+
const style: React.CSSProperties = {};
16+
17+
switch (background.type) {
18+
case 'color':
19+
if (background.value) {
20+
style.backgroundColor = background.value;
21+
}
22+
break;
23+
24+
case 'image':
25+
if (background.value) {
26+
// Simple URL-based approach - let browser handle loading
27+
style.backgroundImage = `url(${background.value})`;
28+
style.backgroundSize = background.size || 'cover';
29+
style.backgroundPosition = background.position || 'center';
30+
style.backgroundRepeat = background.repeat || 'no-repeat';
31+
style.backgroundAttachment = background.attachment || 'scroll';
32+
}
33+
break;
34+
35+
case 'gradient':
36+
if (background.value) {
37+
style.background = background.value;
38+
}
39+
break;
40+
41+
default:
42+
// Use default styling
43+
break;
44+
}
45+
46+
return style;
47+
}, [background]);
48+
49+
const overlayStyles = useMemo(() => {
50+
if (!background.overlay.enabled) {
51+
return {};
52+
}
53+
54+
return {
55+
backgroundColor: background.overlay.color,
56+
opacity: background.overlay.opacity,
57+
mixBlendMode: background.overlay.blendMode || 'normal',
58+
};
59+
}, [background]);
60+
61+
const hasCustomBackground = background.type !== 'default' && background.value;
62+
const hasOverlay = background.overlay.enabled && hasCustomBackground;
63+
64+
// Split-screen layout: Background image on one side, login card on opposite side
65+
if (layout.type === 'split-screen' && background.type === 'image' && background.value) {
66+
// Determine which side gets the background image based on columnPosition
67+
// If card is on left, bg is on right. If card is on right, bg is on left.
68+
// If card is center, bg is on left by default
69+
const bgPosition = layout.columnPosition === 'right' ? 'left' : 'right';
70+
71+
return (
72+
<div className={`${styles.backgroundWrapper} ${styles.splitScreenContainer}`}>
73+
<div
74+
className={`${styles.backgroundPanel} ${styles[`bgPosition-${bgPosition}`]}`}
75+
style={{
76+
backgroundImage: `url(${background.value})`,
77+
backgroundSize: background.size || 'cover',
78+
backgroundPosition: background.position || 'center',
79+
backgroundRepeat: background.repeat || 'no-repeat',
80+
}}
81+
/>
82+
<div className={styles.content}>{children}</div>
83+
</div>
84+
);
85+
}
86+
87+
// Default layout: Simple background with optional overlay
88+
return (
89+
<div
90+
className={`${styles.backgroundWrapper} ${hasCustomBackground ? styles.customBackground : ''}`}
91+
style={backgroundStyles}
92+
>
93+
{hasOverlay && <div className={styles.overlay} style={overlayStyles} />}
94+
<div className={styles.content}>{children}</div>
95+
</div>
96+
);
97+
};
98+
99+
export default BackgroundWrapper;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
@use '@carbon/layout';
2+
@use '@openmrs/esm-styleguide/src/vars' as *;
3+
4+
.backgroundWrapper {
5+
min-height: 100vh;
6+
position: relative;
7+
8+
&.customBackground {
9+
// Ensure proper background attachment
10+
background-attachment: scroll;
11+
12+
@media (min-width: 769px) {
13+
// Enable fixed backgrounds on desktop for better visual effect
14+
&[style*='background-attachment: fixed'] {
15+
background-attachment: fixed;
16+
}
17+
}
18+
19+
@media (max-width: 768px) {
20+
// Always use scroll on mobile for better performance
21+
background-attachment: scroll !important;
22+
}
23+
}
24+
}
25+
26+
.overlay {
27+
position: absolute;
28+
top: 0;
29+
left: 0;
30+
width: 100%;
31+
height: 100%;
32+
z-index: 1;
33+
pointer-events: none;
34+
transition: opacity 0.3s ease-in-out;
35+
}
36+
37+
.content {
38+
position: relative;
39+
z-index: 2;
40+
min-height: 100vh;
41+
display: flex;
42+
flex-direction: column;
43+
44+
.customBackground & {
45+
// Add subtle backdrop for better content readability on custom backgrounds
46+
backdrop-filter: blur(0.5px);
47+
}
48+
}
49+
50+
// Split-screen layout styles
51+
.splitScreenContainer {
52+
display: flex;
53+
min-height: 100vh;
54+
position: relative;
55+
overflow: hidden;
56+
57+
.content {
58+
position: relative;
59+
z-index: 2;
60+
width: 100%;
61+
}
62+
63+
@media (max-width: 768px) {
64+
// On mobile, stack vertically with background on top
65+
flex-direction: column;
66+
67+
.backgroundPanel {
68+
position: relative !important;
69+
width: 100% !important;
70+
height: 30vh !important;
71+
left: 0 !important;
72+
right: 0 !important;
73+
}
74+
}
75+
}
76+
77+
.backgroundPanel {
78+
position: absolute;
79+
top: 0;
80+
bottom: 0;
81+
width: 50%;
82+
z-index: 1;
83+
84+
&.bgPosition-left {
85+
left: 0;
86+
}
87+
88+
&.bgPosition-right {
89+
right: 0;
90+
}
91+
92+
@media (min-width: 769px) and (max-width: 1024px) {
93+
// On tablets, adjust background width
94+
width: 40%;
95+
}
96+
}

0 commit comments

Comments
 (0)