diff --git a/development/docker-compose.yaml b/development/docker-compose.yaml
index 520fe75..2f988f6 100644
--- a/development/docker-compose.yaml
+++ b/development/docker-compose.yaml
@@ -1,12 +1,19 @@
services:
# Lightweight Keycloak server for local development.
keycloak:
- image: ghcr.io/healthcare-monitoring-system/d4-platform-devops:latest
+ image: quay.io/keycloak/keycloak:26.6.1
+ command: start-dev --import-realm
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
ports:
- "8080:8080"
+ volumes:
+ - ./keycloak/themes:/opt/keycloak/themes:ro
+ - ./keycloak/realm-config.json:/opt/keycloak/data/import/realm-config.json:ro
+ depends_on:
+ postgres:
+ condition: service_healthy
# Your Backend Service
backend:
@@ -25,3 +32,6 @@ services:
- "3000:3000"
depends_on:
- backend
+
+volumes:
+ postgres_data:
\ No newline at end of file
diff --git a/development/keycloak/realm-config.json b/development/keycloak/realm-config.json
index d91cb9e..97bc2f3 100644
--- a/development/keycloak/realm-config.json
+++ b/development/keycloak/realm-config.json
@@ -1,89 +1,102 @@
{
- "realm": "HealthCare",
- "displayNameHtml": "
+
+
+
+ <#if messageHeader??>
+ ${kcSanitize(messageHeader)?no_esc}
+ #if>
+ <#if message?has_content>
+ ${kcSanitize(message.summary)?no_esc}
+ #if>
+
+ <#if actionUri??>
+ ${msg("proceedWithAction")}
+ <#else>
+ Back to sign in
+ #if>
+
+
+
+
diff --git a/development/keycloak/themes/medicare/login/login-reset-password.ftl b/development/keycloak/themes/medicare/login/login-reset-password.ftl
new file mode 100644
index 0000000..44ac461
--- /dev/null
+++ b/development/keycloak/themes/medicare/login/login-reset-password.ftl
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+ <#if message?has_content>
+ ${kcSanitize(message.summary)?no_esc}
+ #if>
+
+
+
+
+
+
+
+
diff --git a/development/keycloak/themes/medicare/login/login.ftl b/development/keycloak/themes/medicare/login/login.ftl
new file mode 100644
index 0000000..3d27d82
--- /dev/null
+++ b/development/keycloak/themes/medicare/login/login.ftl
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+ <#if message?has_content>
+ ${kcSanitize(message.summary)?no_esc}
+ #if>
+
+
+
+ <#if realm.registrationAllowed && !registrationDisabled??>
+
+ #if>
+
+
+
+
diff --git a/development/keycloak/themes/medicare/login/register.ftl b/development/keycloak/themes/medicare/login/register.ftl
new file mode 100644
index 0000000..45ada1b
--- /dev/null
+++ b/development/keycloak/themes/medicare/login/register.ftl
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+ <#if message?has_content>
+ ${kcSanitize(message.summary)?no_esc}
+ #if>
+
+
+
+
+
+
+
+
diff --git a/development/keycloak/themes/medicare/login/resources/css/login.css b/development/keycloak/themes/medicare/login/resources/css/login.css
new file mode 100644
index 0000000..feb7601
--- /dev/null
+++ b/development/keycloak/themes/medicare/login/resources/css/login.css
@@ -0,0 +1,215 @@
+:root {
+ --mc-sky: #00a6f4;
+ --mc-sky-dark: #0089c9;
+ --mc-sky-pale: #eef6fc;
+ --mc-text: #16324f;
+ --mc-text-secondary: #405b78;
+ --mc-muted: #566d86;
+ --mc-border: rgba(24, 59, 99, 0.1);
+ --mc-surface: rgba(255, 255, 255, 0.9);
+ --mc-danger: #dc4b4b;
+ --mc-success: #2f9e61;
+ --mc-focus: rgba(95, 152, 208, 0.35);
+ --mc-shadow: 0 20px 40px rgba(22, 50, 79, 0.14);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ margin: 0;
+ color: var(--mc-text);
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ background:
+ radial-gradient(circle at 8% 8%, rgba(0, 166, 244, 0.18), transparent 32rem),
+ radial-gradient(circle at 88% 15%, rgba(47, 158, 97, 0.14), transparent 28rem),
+ linear-gradient(151deg, #b9d4ee 0%, #d8eaf7 35%, #fdf6ed 65%, #ffffff 100%);
+}
+
+.mc-page {
+ display: flex;
+ min-height: 100vh;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem 1rem;
+}
+
+.mc-card {
+ width: min(100%, 34rem);
+ border: 1px solid var(--mc-border);
+ border-radius: 1.5rem;
+ background: var(--mc-surface);
+ box-shadow: var(--mc-shadow);
+ backdrop-filter: blur(18px);
+ padding: clamp(1.5rem, 4vw, 2.5rem);
+}
+
+.mc-header {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.75rem;
+ text-align: center;
+}
+
+.mc-badge {
+ display: inline-flex;
+ width: 3.25rem;
+ height: 3.25rem;
+ align-items: center;
+ justify-content: center;
+ border-radius: 1.25rem;
+ background: linear-gradient(180deg, var(--mc-sky) 0%, var(--mc-sky-dark) 100%);
+ color: #fff;
+ box-shadow: 0 10px 24px rgba(22, 50, 79, 0.12);
+}
+
+.mc-brand {
+ margin: 0;
+ color: var(--mc-sky);
+ font-size: 1.25rem;
+ font-weight: 700;
+ letter-spacing: 0;
+}
+
+.mc-title {
+ margin: 0.25rem 0 0;
+ color: var(--mc-text);
+ font-size: clamp(2rem, 7vw, 2.5rem);
+ line-height: 1.1;
+ font-weight: 800;
+ letter-spacing: 0;
+}
+
+.mc-copy {
+ margin: 0.5rem auto 0;
+ max-width: 26rem;
+ color: var(--mc-text-secondary);
+ font-size: 1rem;
+ line-height: 1.6;
+}
+
+.mc-form {
+ margin-top: 2rem;
+}
+
+.mc-field {
+ margin-top: 1rem;
+}
+
+.mc-label {
+ display: block;
+ margin-bottom: 0.4rem;
+ color: var(--mc-text-secondary);
+ font-size: 0.875rem;
+ font-weight: 700;
+}
+
+.mc-input {
+ width: 100%;
+ min-height: 2.85rem;
+ border: 1px solid var(--mc-border);
+ border-radius: 0.9rem;
+ background: #fff;
+ color: var(--mc-text);
+ font: inherit;
+ padding: 0 0.9rem;
+ outline: none;
+ transition: border-color 160ms ease, box-shadow 160ms ease;
+}
+
+.mc-input:focus {
+ border-color: var(--mc-sky);
+ box-shadow: 0 0 0 4px var(--mc-focus);
+}
+
+.mc-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-top: 1rem;
+ color: var(--mc-text-secondary);
+ font-size: 0.875rem;
+}
+
+.mc-check {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.mc-check input {
+ width: 1.1rem;
+ height: 1.1rem;
+ accent-color: var(--mc-sky);
+}
+
+.mc-link {
+ color: var(--mc-sky-dark);
+ font-weight: 700;
+ text-decoration: none;
+}
+
+.mc-link:hover {
+ color: var(--mc-text);
+}
+
+.mc-button {
+ display: inline-flex;
+ width: 100%;
+ min-height: 3rem;
+ align-items: center;
+ justify-content: center;
+ margin-top: 1.25rem;
+ border: 0;
+ border-radius: 1rem;
+ background: var(--mc-sky);
+ color: #fff;
+ cursor: pointer;
+ font: inherit;
+ font-weight: 800;
+ transition: background-color 160ms ease, transform 160ms ease;
+}
+
+.mc-button:hover {
+ background: var(--mc-sky-dark);
+ transform: translateY(-1px);
+}
+
+.mc-message {
+ margin-top: 1.25rem;
+ border-radius: 1rem;
+ padding: 0.85rem 1rem;
+ font-size: 0.9rem;
+ line-height: 1.5;
+}
+
+.mc-message-error {
+ border: 1px solid rgba(220, 75, 75, 0.24);
+ background: #fdf1f1;
+ color: var(--mc-danger);
+}
+
+.mc-message-success {
+ border: 1px solid rgba(47, 158, 97, 0.24);
+ background: #eef9f2;
+ color: var(--mc-success);
+}
+
+.mc-footer {
+ margin-top: 1.5rem;
+ text-align: center;
+ color: var(--mc-muted);
+ font-size: 0.9rem;
+ line-height: 1.5;
+}
+
+@media (max-width: 420px) {
+ .mc-row {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+}
diff --git a/development/keycloak/themes/medicare/login/theme.properties b/development/keycloak/themes/medicare/login/theme.properties
new file mode 100644
index 0000000..bac7d8b
--- /dev/null
+++ b/development/keycloak/themes/medicare/login/theme.properties
@@ -0,0 +1,2 @@
+parent=base
+styles=css/login.css