From 00c0e28d651a5632c0dce9b4d30fd96f86147925 Mon Sep 17 00:00:00 2001
From: Maksim Nabokikh <maksim.nabokikh@flant.com>
Date: Mon, 16 Dec 2024 19:39:51 +0100
Subject: [PATCH] Add CSS for example app to make it prettier (#3886)

Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
---
 examples/example-app/templates.go | 371 ++++++++++++++++++++++++++----
 1 file changed, 322 insertions(+), 49 deletions(-)

diff --git a/examples/example-app/templates.go b/examples/example-app/templates.go
index a9425ead..7107eb87 100644
--- a/examples/example-app/templates.go
+++ b/examples/example-app/templates.go
@@ -6,38 +6,225 @@ import (
 	"net/http"
 )
 
+const css = `
+	body {
+		font-family: Arial, sans-serif;
+		background-color: #f2f2f2;
+		margin: 0;
+	}
+
+    .header {
+        text-align: center;
+        margin-bottom: 20px;
+    }
+
+	.dex {
+		font-size: 2em;
+		font-weight: bold;
+		color: #3F9FD8; /* Main color */
+	}
+
+	.example-app {
+		font-size: 1em;
+		color: #EF4B5C; /* Secondary color */
+	}
+
+	.form-instructions {
+		text-align: center;
+		margin-bottom: 15px;
+		font-size: 1em;
+		color: #555;
+	}
+
+	hr {
+		border: none;
+		border-top: 1px solid #ccc;
+		margin-top: 10px;
+		margin-bottom: 20px;
+	}
+
+	label {
+		flex: 1;
+		font-weight: bold;
+		color: #333;
+	}
+
+	p {
+		margin-bottom: 15px;
+		display: flex;
+		align-items: center;
+	}
+
+	input[type="text"] {
+		flex: 2;
+		padding: 8px;
+		border: 1px solid #ccc;
+		border-radius: 4px;
+		outline: none;
+	}
+
+	input[type="checkbox"] {
+		margin-left: 10px;
+		transform: scale(1.2);
+	}
+
+	.back-button {
+		display: inline-block;
+		padding: 8px 16px;
+		background-color: #EF4B5C; /* Secondary color */
+		color: white;
+		border: none;
+		border-radius: 4px;
+		cursor: pointer;
+		font-size: 12px;
+		text-decoration: none;
+		transition: background-color 0.3s ease, transform 0.2s ease;
+		box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+		position: fixed;
+		right: 20px;
+		bottom: 20px;
+	}
+
+	.back-button:hover {
+		background-color: #C43B4B; /* Darker shade of secondary color */
+	}
+
+	.token-block {
+		background-color: #fff;
+		padding: 10px 15px;
+		border-radius: 8px;
+		box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+		margin-bottom: 15px;
+		word-wrap: break-word;
+		display: flex;
+		flex-direction: column;
+		gap: 5px;
+		position: relative;
+	}
+
+	.token-title {
+		font-weight: bold;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.token-title a {
+		font-size: 0.9em;
+		text-decoration: none;
+		color: #3F9FD8; /* Main color */
+	}
+
+	.token-title a:hover {
+		text-decoration: underline;
+	}
+
+    .token-code {
+		overflow-wrap: break-word;
+		word-break: break-all;
+		white-space: normal;
+    }
+
+	pre {
+		white-space: pre-wrap;
+		background-color: #f9f9f9;
+		padding: 8px;
+		border-radius: 4px;
+		border: 1px solid #ddd;
+		margin: 0;
+		font-family: 'Courier New', Courier, monospace;
+		overflow-x: auto;
+		font-size: 0.9em;
+		position: relative;
+		margin-top: 5px;
+	}
+
+	pre .key {
+		color: #c00;
+	}
+
+	pre .string {
+		color: #080;
+	}
+
+	pre .number {
+		color: #00f;
+	}
+`
+
 var indexTmpl = template.Must(template.New("index.html").Parse(`<html>
-  <head>
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Example App - Login</title>
     <style>
-form  { display: table;      }
-p     { display: table-row;  }
-label { display: table-cell; }
-input { display: table-cell; }
+` + css + `
+        body {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            flex-direction: column;
+        }
+
+		form {
+			background-color: #fff;
+			padding: 20px;
+			border-radius: 8px;
+			box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+			width: 100%;
+			max-width: 400px;
+		}
+
+		input[type="submit"] {
+			width: 100%;
+			padding: 10px;
+			background-color: #3F9FD8; /* Main color */
+			color: white;
+			border: none;
+			border-radius: 4px;
+			cursor: pointer;
+			font-size: 16px;
+		}
+
+		input[type="submit"]:hover {
+			background-color: #357FAA; /* Darker shade of main color */
+		}
     </style>
-  </head>
-  <body>
+</head>
+<body>
+    <div class="header">
+        <div class="dex">Dex</div>
+        <div class="example-app">Example App</div>
+    </div>
     <form action="/login" method="post">
-      <p>
-        <label> Authenticate for: </label>
-        <input type="text" name="cross_client" placeholder="list of client-ids">
-      </p>
-      <p>
-        <label>Extra scopes: </label>
-        <input type="text" name="extra_scopes" placeholder="list of scopes">
-      </p>
-      <p>
-        <label>Connector ID: </label>
-        <input type="text" name="connector_id" placeholder="connector id">
-      </p>
-      <p>
-        <label>Request offline access: </label>
-        <input type="checkbox" name="offline_access" value="yes" checked>
-      </p>
-      <p>
-	    <input type="submit" value="Login">
-      </p>
+        <div class="form-instructions">
+            If needed, customize your login settings below, then click <strong>Login</strong> to proceed.
+        </div>
+        <hr/>
+        <p>
+            <label for="cross_client">Authenticate for:</label>
+            <input type="text" id="cross_client" name="cross_client" placeholder="list of client-ids">
+        </p>
+        <p>
+            <label for="extra_scopes">Extra scopes:</label>
+            <input type="text" id="extra_scopes" name="extra_scopes" placeholder="list of scopes">
+        </p>
+        <p>
+            <label for="connector_id">Connector ID:</label>
+            <input type="text" id="connector_id" name="connector_id" placeholder="connector id">
+        </p>
+        <p>
+            <label for="offline_access">Request offline access:</label>
+            <input type="checkbox" id="offline_access" name="offline_access" value="yes" checked>
+        </p>
+        <p>
+            <input type="submit" value="Login">
+        </p>
     </form>
-  </body>
+</body>
 </html>`))
 
 func renderIndex(w http.ResponseWriter) {
@@ -53,30 +240,116 @@ type tokenTmplData struct {
 }
 
 var tokenTmpl = template.Must(template.New("token.html").Parse(`<html>
-  <head>
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Tokens</title>
     <style>
-/* make pre wrap */
-pre {
- white-space: pre-wrap;       /* css-3 */
- white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
- white-space: -pre-wrap;      /* Opera 4-6 */
- white-space: -o-pre-wrap;    /* Opera 7 */
- word-wrap: break-word;       /* Internet Explorer 5.5+ */
-}
+` + css + `
+        body {
+            color: #333;
+            margin: 0;
+            padding: 20px;
+            position: relative;
+        }
+
+        input[type="submit"] {
+            margin-top: 10px;
+            padding: 8px 16px;
+            background-color: #3F9FD8; /* Main color */
+            color: white;
+            border: none;
+            border-radius: 4px;
+            cursor: pointer;
+            font-size: 14px;
+            transition: background-color 0.3s ease;
+        }
+
+        input[type="submit"]:hover {
+            background-color: #357FAA; /* Darker shade of main color */
+        }
     </style>
-  </head>
-  <body>
-    <p> ID Token: <pre><code>{{ .IDToken }}</code></pre></p>
-    <p> Access Token: <pre><code>{{ .AccessToken }}</code></pre></p>
-    <p> Claims: <pre><code>{{ .Claims }}</code></pre></p>
-	{{ if .RefreshToken }}
-    <p> Refresh Token: <pre><code>{{ .RefreshToken }}</code></pre></p>
-	<form action="{{ .RedirectURL }}" method="post">
-	  <input type="hidden" name="refresh_token" value="{{ .RefreshToken }}">
-	  <input type="submit" value="Redeem refresh token">
-    </form>
-	{{ end }}
-  </body>
+</head>
+<body>
+    {{ if .IDToken }}
+    <div class="token-block">
+        <div class="token-title">
+            ID Token:
+            <a href="#" onclick="window.open('https://jwt.io/#debugger-io?token=' + encodeURIComponent('{{ .IDToken }}'), '_blank')">Decode on jwt.io</a>
+        </div>
+        <pre><code class="token-code">{{ .IDToken }}</code></pre>
+    </div>
+    {{ end }}
+
+    {{ if .AccessToken }}
+    <div class="token-block">
+        <div class="token-title">
+            Access Token:
+            <a href="#" onclick="window.open('https://jwt.io/#debugger-io?token=' + encodeURIComponent('{{ .AccessToken }}'), '_blank')">Decode on jwt.io</a>
+        </div>
+        <pre><code class="token-code">{{ .AccessToken }}</code></pre>
+    </div>
+    {{ end }}
+
+    {{ if .Claims }}
+    <div class="token-block">
+        <div class="token-title">Claims:</div>
+        <pre><code id="claims">{{ .Claims }}</code></pre>
+    </div>
+    {{ end }}
+
+    {{ if .RefreshToken }}
+    <div class="token-block">
+        <div class="token-title">Refresh Token:</div>
+        <pre><code class="token-code">{{ .RefreshToken }}</code></pre>
+        <form action="{{ .RedirectURL }}" method="post">
+            <input type="hidden" name="refresh_token" value="{{ .RefreshToken }}">
+            <input type="submit" value="Redeem refresh token">
+        </form>
+    </div>
+    {{ end }}
+
+    <a href="/" class="back-button">Back to Home</a>
+
+    <script>
+        // Simple JSON syntax highlighter
+        document.addEventListener("DOMContentLoaded", function() {
+            const claimsElement = document.getElementById("claims");
+            if (claimsElement) {
+                try {
+                    const json = JSON.parse(claimsElement.textContent);
+                    claimsElement.innerHTML = syntaxHighlight(json);
+                } catch (e) {
+                    console.error("Invalid JSON in claims:", e);
+                }
+            }
+        });
+
+        function syntaxHighlight(json) {
+            if (typeof json != 'string') {
+                json = JSON.stringify(json, undefined, 2);
+            }
+            json = json.replace(/&/g, '&amp;').replace(/</g, '<').replace(/>/g, '>');
+            return json.replace(/("(\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|\b\d+\b)/g, function (match) {
+                let cls = 'number';
+                if (/^"/.test(match)) {
+                    if (/:$/.test(match)) {
+                        cls = 'key';
+                    } else {
+                        cls = 'string';
+                    }
+                } else if (/true|false/.test(match)) {
+                    cls = 'boolean';
+                } else if (/null/.test(match)) {
+                    cls = 'null';
+                }
+                return '<span class="' + cls + '">' + match + '</span>';
+            });
+        }
+    </script>
+</body>
 </html>
 `))
 
-- 
GitLab