Skip to content

Commit b3c6789

Browse files
committed
webui: update design, package with Go binary
1 parent 3af1346 commit b3c6789

File tree

4 files changed

+145
-71
lines changed

4 files changed

+145
-71
lines changed

Dockerfile-openshift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ COPY ./cmd/server ./cmd/server
88
COPY ./client ./client
99
COPY ./data ./data
1010
COPY ./pkg ./pkg
11+
COPY ./webui ./webui
1112
COPY makefile makefile
1213
RUN make build
1314

cmd/server/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/moov-io/base/http/bind"
2424
"github.com/moov-io/base/log"
2525
"github.com/moov-io/fed"
26+
"github.com/moov-io/fed/webui"
2627

2728
"github.com/gorilla/mux"
2829
)
@@ -137,6 +138,10 @@ func main() {
137138
// Add searcher for HTTP routes
138139
addSearchRoutes(logger, router, searcher)
139140

141+
// Add webui routes
142+
webuiController := webui.NewController(logger)
143+
webuiController.AppendRoutes(router)
144+
140145
// Start business logic HTTP server
141146
go func() {
142147
if certFile, keyFile := os.Getenv("HTTPS_CERT_FILE"), os.Getenv("HTTPS_KEY_FILE"); certFile != "" && keyFile != "" {

web/search.html renamed to webui/index.html

Lines changed: 102 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,108 +2,147 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8">
5-
<title>Search Page</title>
5+
<title>Routing Number Search</title>
66
<style>
77
body {
8-
font-family: 'Segoe UI', Arial, sans-serif;
9-
background: linear-gradient(135deg, #e3f0ff 0%, #f7f7f7 100%);
8+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
9+
background: linear-gradient(145deg, #e6f0fa 0%, #f8fafc 100%);
1010
display: flex;
1111
justify-content: center;
1212
align-items: center;
13-
height: 100vh;
13+
min-height: 100vh;
14+
margin: 0;
15+
padding: 20px;
16+
box-sizing: border-box;
1417
}
1518
.search-container {
16-
background: #fff;
17-
padding: 32px 24px 24px 24px;
18-
border-radius: 12px;
19-
box-shadow: 0 4px 16px rgba(0,0,0,0.10);
20-
min-width: 350px;
19+
background: #ffffff;
20+
padding: 2rem;
21+
border-radius: 16px;
22+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
23+
width: 100%;
24+
max-width: 400px;
25+
transition: transform 0.2s ease;
26+
}
27+
.search-container:hover {
28+
transform: translateY(-2px);
2129
}
2230
.search-title {
23-
font-size: 1.4rem;
24-
font-weight: 600;
25-
color: #2d3a4b;
26-
margin-bottom: 18px;
31+
font-size: 1.5rem;
32+
font-weight: 700;
33+
color: #1e293b;
34+
margin-bottom: 1.5rem;
2735
text-align: center;
36+
letter-spacing: -0.02em;
2837
}
2938
.search-box {
3039
display: flex;
31-
gap: 8px;
32-
margin-bottom: 20px;
40+
flex-direction: column;
41+
gap: 12px;
3342
}
34-
input[type="text"] {
35-
flex: 1;
36-
padding: 12px;
43+
select, input[type="text"] {
44+
padding: 12px 16px;
3745
font-size: 1rem;
38-
border: 1px solid #b0c4de;
39-
border-radius: 6px;
46+
border: 1px solid #d1d5db;
47+
border-radius: 8px;
48+
background: #f8fafc;
4049
outline: none;
41-
transition: border-color 0.2s;
42-
background: #f5faff;
50+
transition: border-color 0.2s, box-shadow 0.2s;
51+
}
52+
select:focus, input[type="text"]:focus {
53+
border-color: #2563eb;
54+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
4355
}
44-
input[type="text"]:focus {
45-
border-color: #0078d4;
56+
select {
57+
appearance: none;
58+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
59+
background-repeat: no-repeat;
60+
background-position: right 0.75rem center;
61+
background-size: 1.2em;
4662
}
4763
button {
48-
padding: 12px 18px;
49-
background: #0078d4;
50-
color: #fff;
64+
padding: 12px;
65+
background: #2563eb;
66+
color: #ffffff;
5167
border: none;
52-
border-radius: 6px;
68+
border-radius: 8px;
5369
font-size: 1rem;
70+
font-weight: 500;
5471
cursor: pointer;
55-
transition: background 0.2s;
72+
transition: background 0.2s, transform 0.1s;
5673
}
5774
button:hover {
58-
background: #005fa3;
75+
background: #1d4ed8;
76+
transform: translateY(-1px);
77+
}
78+
button:active {
79+
transform: translateY(0);
5980
}
6081
.result-card {
61-
background: #f5faff;
62-
border: 1px solid #e0e7ef;
63-
border-radius: 8px;
64-
padding: 20px;
65-
margin-top: 8px;
66-
box-shadow: 0 2px 8px rgba(0,120,212,0.07);
82+
background: #f8fafc;
83+
border: 1px solid #e5e7eb;
84+
border-radius: 12px;
85+
padding: 1.25rem;
86+
margin-top: 1rem;
87+
transition: box-shadow 0.2s;
88+
}
89+
.result-card:hover {
90+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
6791
}
6892
.result-title {
69-
font-size: 1.1rem;
70-
font-weight: 500;
71-
color: #0078d4;
72-
margin-bottom: 10px;
93+
font-size: 1.125rem;
94+
font-weight: 600;
95+
color: #2563eb;
96+
margin-bottom: 0.75rem;
7397
}
7498
.result-row {
75-
margin-bottom: 7px;
76-
color: #2d3a4b;
99+
margin-bottom: 0.5rem;
100+
color: #1e293b;
101+
font-size: 0.95rem;
77102
}
78103
.result-label {
79104
font-weight: 500;
80-
color: #5a6b7b;
105+
color: #4b5563;
81106
}
82107
.error-msg {
83-
color: #d32f2f;
84-
margin-top: 10px;
108+
color: #dc2626;
109+
margin-top: 1rem;
85110
text-align: center;
111+
font-size: 0.95rem;
112+
}
113+
@media (max-width: 480px) {
114+
.search-container {
115+
padding: 1.5rem;
116+
}
117+
.search-title {
118+
font-size: 1.25rem;
119+
}
86120
}
87121
</style>
88122
</head>
89123
<body>
90124
<div class="search-container">
91-
<div class="search-title">ACH Routing Number Search</div>
125+
<div class="search-title">Routing Number Search</div>
92126
<div class="search-box">
93-
<input id="searchInput" type="text" placeholder="Enter routing number..." maxlength="9" />
127+
<select id="searchType">
128+
<option value="ach">ACH Search</option>
129+
<option value="wire">Wire Search</option>
130+
</select>
131+
<input id="searchInput" type="text" placeholder="Enter 9-digit routing number..." maxlength="9" />
94132
<button id="searchBtn">Search</button>
95133
</div>
96134
<div id="result"></div>
97135
<div id="error" class="error-msg"></div>
98136
</div>
99137
<script>
100138
const input = document.getElementById('searchInput');
139+
const searchType = document.getElementById('searchType');
101140
const btn = document.getElementById('searchBtn');
102141
const resultDiv = document.getElementById('result');
103142
const errorDiv = document.getElementById('error');
104143

105-
function renderResult(data) {
106-
if (data && data.achParticipants && data.achParticipants.length) {
144+
function renderResult(data, type) {
145+
if (type === 'ach' && data?.achParticipants?.length) {
107146
const p = data.achParticipants[0];
108147
resultDiv.innerHTML = `
109148
<div class="result-card">
@@ -120,14 +159,8 @@
120159
<div class="result-row"><span class="result-label">View Code:</span> ${p.viewCode}</div>
121160
</div>
122161
`;
123-
errorDiv.textContent = '';
124162
return true;
125-
}
126-
return false;
127-
}
128-
129-
function renderWireResult(data) {
130-
if (data && data.wireParticipants && data.wireParticipants.length) {
163+
} else if (type === 'wire' && data?.wireParticipants?.length) {
131164
const p = data.wireParticipants[0];
132165
resultDiv.innerHTML = `
133166
<div class="result-card">
@@ -141,42 +174,40 @@
141174
<div class="result-row"><span class="result-label">Date:</span> ${p.date}</div>
142175
</div>
143176
`;
144-
errorDiv.textContent = '';
145177
return true;
146178
}
147179
return false;
148180
}
149181

150182
async function search() {
151183
const routingNumber = input.value.trim();
184+
const type = searchType.value;
152185
resultDiv.innerHTML = '';
153186
errorDiv.textContent = '';
187+
154188
if (!/^\d{9}$/.test(routingNumber)) {
155189
errorDiv.textContent = 'Please enter a valid 9-digit routing number.';
156190
return;
157191
}
192+
158193
try {
159-
let res = await fetch(`/fed/ach/search?routingNumber=${routingNumber}`);
160-
if (!res.ok) throw new Error('Network error');
161-
let data = await res.json();
162-
if (renderResult(data)) return;
163-
// Try wire search if no ACH participant
164-
res = await fetch(`/fed/wire/search?routingNumber=${routingNumber}`);
194+
const endpoint = type === 'ach' ? `/fed/ach/search` : `/fed/wire/search`;
195+
const res = await fetch(`${endpoint}?routingNumber=${routingNumber}`);
165196
if (!res.ok) throw new Error('Network error');
166-
data = await res.json();
167-
if (renderWireResult(data)) return;
168-
errorDiv.textContent = 'No participant found for this routing number.';
197+
const data = await res.json();
198+
if (!renderResult(data, type)) {
199+
errorDiv.textContent = `No ${type.toUpperCase()} participant found for this routing number.`;
200+
} else {
201+
errorDiv.textContent = '';
202+
}
169203
} catch (err) {
170-
resultDiv.innerHTML = '';
171204
errorDiv.textContent = 'Error fetching data. Please try again.';
172205
}
173206
}
174207

175208
btn.addEventListener('click', search);
176-
input.addEventListener('keydown', function(e) {
177-
if (e.key === 'Enter') {
178-
search();
179-
}
209+
input.addEventListener('keydown', (e) => {
210+
if (e.key === 'Enter') search();
180211
});
181212
</script>
182213
</body>

webui/webui.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package webui
2+
3+
import (
4+
"embed"
5+
"net/http"
6+
"os"
7+
8+
"github.com/moov-io/base/log"
9+
10+
"github.com/gorilla/mux"
11+
)
12+
13+
//go:embed *.html
14+
var WebRoot embed.FS
15+
16+
type Controller interface {
17+
AppendRoutes(router *mux.Router) *mux.Router
18+
}
19+
20+
func NewController(logger log.Logger) Controller {
21+
return &controller{
22+
logger: logger,
23+
basePath: os.Getenv("BASE_PATH"),
24+
}
25+
}
26+
27+
type controller struct {
28+
logger log.Logger
29+
basePath string
30+
}
31+
32+
func (c *controller) AppendRoutes(router *mux.Router) *mux.Router {
33+
staticFS := http.FileServer(http.FS(WebRoot))
34+
router.PathPrefix(c.basePath).Handler(http.StripPrefix(c.basePath, staticFS))
35+
36+
return router
37+
}

0 commit comments

Comments
 (0)