Skip to content

Commit b8175c3

Browse files
author
Brian Genisio
committed
adding a configuration UX
1 parent 0e6eb2e commit b8175c3

File tree

8 files changed

+659
-1
lines changed

8 files changed

+659
-1
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "client/design-system"]
2+
path = client/design-system
3+
url = https://github.com/CodeSignal/learn_bespoke-design-system.git

client/app.js

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// Configuration Editor Application
2+
let config = {
3+
spreadsheetURL: '',
4+
cellsToVerify: []
5+
};
6+
7+
// Load configuration on startup
8+
async function loadConfig() {
9+
try {
10+
const response = await fetch('/api/config');
11+
if (!response.ok) {
12+
throw new Error(`Failed to load config: ${response.statusText}`);
13+
}
14+
config = await response.json();
15+
16+
// Ensure each cell has a verificationType
17+
if (config.cellsToVerify) {
18+
config.cellsToVerify.forEach(cell => {
19+
if (!cell.verificationType) {
20+
// Infer from existing data: if function exists, use function, otherwise value
21+
cell.verificationType = cell.expectedFunction ? 'function' : 'value';
22+
}
23+
});
24+
}
25+
26+
render();
27+
} catch (error) {
28+
console.error('Error loading config:', error);
29+
updateStatus('Error loading configuration', 'error');
30+
}
31+
}
32+
33+
// Save configuration to server
34+
async function saveConfig() {
35+
try {
36+
// Create a cleaned version of config for serialization
37+
const cleanedConfig = {
38+
spreadsheetURL: config.spreadsheetURL,
39+
cellsToVerify: config.cellsToVerify.map(cell => {
40+
const cleanedCell = {
41+
cellName: cell.cellName
42+
};
43+
44+
// Only include expectedValue OR expectedFunction based on verificationType
45+
const verificationType = cell.verificationType || (cell.expectedFunction ? 'function' : 'value');
46+
if (verificationType === 'function') {
47+
cleanedCell.expectedFunction = cell.expectedFunction;
48+
} else {
49+
cleanedCell.expectedValue = cell.expectedValue;
50+
}
51+
52+
return cleanedCell;
53+
})
54+
};
55+
56+
const response = await fetch('/api/config', {
57+
method: 'POST',
58+
headers: {
59+
'Content-Type': 'application/json'
60+
},
61+
body: JSON.stringify(cleanedConfig)
62+
});
63+
64+
if (!response.ok) {
65+
throw new Error(`Failed to save config: ${response.statusText}`);
66+
}
67+
68+
updateStatus('Configuration saved successfully', 'success');
69+
} catch (error) {
70+
console.error('Error saving config:', error);
71+
updateStatus('Error saving configuration', 'error');
72+
}
73+
}
74+
75+
// Update status message
76+
function updateStatus(message, type = 'info') {
77+
const statusEl = document.getElementById('status');
78+
if (statusEl) {
79+
statusEl.textContent = message;
80+
statusEl.className = `status ${type}`;
81+
setTimeout(() => {
82+
statusEl.textContent = 'Ready';
83+
statusEl.className = 'status';
84+
}, 3000);
85+
}
86+
}
87+
88+
// Handle spreadsheet URL change
89+
function handleSpreadsheetURLChange(event) {
90+
config.spreadsheetURL = event.target.value;
91+
saveConfig();
92+
}
93+
94+
// Handle cell verification change
95+
function handleCellChange(index, field, value) {
96+
if (!config.cellsToVerify[index]) {
97+
config.cellsToVerify[index] = {};
98+
}
99+
config.cellsToVerify[index][field] = value;
100+
saveConfig();
101+
}
102+
103+
// Handle verification type change (value/function radio)
104+
function handleVerificationTypeChange(index, type) {
105+
if (!config.cellsToVerify[index]) {
106+
config.cellsToVerify[index] = {};
107+
}
108+
config.cellsToVerify[index].verificationType = type;
109+
render();
110+
saveConfig();
111+
}
112+
113+
// Add new cell verification
114+
function addCellVerification() {
115+
config.cellsToVerify.push({
116+
cellName: '',
117+
expectedValue: '',
118+
expectedFunction: '',
119+
verificationType: 'value' // 'value' or 'function'
120+
});
121+
render();
122+
saveConfig();
123+
}
124+
125+
// Remove cell verification
126+
function removeCellVerification(index) {
127+
config.cellsToVerify.splice(index, 1);
128+
render();
129+
saveConfig();
130+
}
131+
132+
// Render the configuration editor
133+
function render() {
134+
const app = document.getElementById('app');
135+
if (!app) return;
136+
137+
app.innerHTML = `
138+
<div class="config-editor">
139+
<div class="box box-elevated" style="margin-bottom: var(--UI-Spacing-spacing-xl);">
140+
<div style="width: 100%;">
141+
<label for="spreadsheet-url" class="label-medium" style="display: block; margin-bottom: var(--UI-Spacing-spacing-xs); color: var(--Colors-Text-Body-Medium); text-align: left;">
142+
Spreadsheet URL
143+
</label>
144+
<input
145+
type="text"
146+
id="spreadsheet-url"
147+
class="input"
148+
style="width: 100%; max-width: 100%; box-sizing: border-box;"
149+
value="${config.spreadsheetURL || ''}"
150+
placeholder="https://docs.google.com/spreadsheets/d/..."
151+
onchange="handleSpreadsheetURLChange(event)"
152+
/>
153+
</div>
154+
</div>
155+
156+
<div class="box box-elevated" style="display: flex; flex-direction: column; align-items: flex-start;">
157+
<h2 class="heading-medium" style="margin-top: 0; margin-bottom: var(--UI-Spacing-spacing-xl); color: var(--Colors-Text-Body-Strong); width: 100%;">Cells to Verify</h2>
158+
159+
<div id="cells-list" style="width: 100%;">
160+
${config.cellsToVerify.map((cell, index) => {
161+
const verificationType = cell.verificationType || (cell.expectedFunction ? 'function' : 'value');
162+
return `
163+
<div class="cell-item" style="border: 1px solid var(--Colors-Box-Stroke); border-radius: var(--UI-Radius-radius-s); padding: var(--UI-Spacing-spacing-l); margin-bottom: var(--UI-Spacing-spacing-m); background: var(--Colors-Box-Background);">
164+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--UI-Spacing-spacing-m);">
165+
<div style="display: flex; align-items: center; gap: var(--UI-Spacing-spacing-l);">
166+
<label class="input-radio input-radio-small" style="margin: 0;">
167+
<input
168+
type="radio"
169+
name="verification-type-${index}"
170+
value="value"
171+
${verificationType === 'value' ? 'checked' : ''}
172+
onchange="handleVerificationTypeChange(${index}, 'value')"
173+
/>
174+
<span class="input-radio-circle">
175+
<span class="input-radio-dot"></span>
176+
</span>
177+
<span class="input-radio-label">Value</span>
178+
</label>
179+
180+
<label class="input-radio input-radio-small" style="margin: 0;">
181+
<input
182+
type="radio"
183+
name="verification-type-${index}"
184+
value="function"
185+
${verificationType === 'function' ? 'checked' : ''}
186+
onchange="handleVerificationTypeChange(${index}, 'function')"
187+
/>
188+
<span class="input-radio-circle">
189+
<span class="input-radio-dot"></span>
190+
</span>
191+
<span class="input-radio-label">Function</span>
192+
</label>
193+
</div>
194+
<button class="button button-text" onclick="removeCellVerification(${index})" style="color: var(--Colors-Base-Accent-Red-600);">
195+
Remove
196+
</button>
197+
</div>
198+
199+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--UI-Spacing-spacing-m); margin-bottom: var(--UI-Spacing-spacing-m);">
200+
<div>
201+
<label style="display: block; margin-bottom: var(--UI-Spacing-spacing-xs); font-weight: 500; font-size: var(--Fonts-Body-Default-sm); color: var(--Colors-Text-Body-Medium);">
202+
Cell Name
203+
</label>
204+
<input
205+
type="text"
206+
class="input"
207+
style="width: 100%; box-sizing: border-box;"
208+
value="${cell.cellName || ''}"
209+
placeholder="e.g., A1"
210+
onchange="handleCellChange(${index}, 'cellName', event.target.value)"
211+
/>
212+
</div>
213+
214+
${verificationType === 'value' ? `
215+
<div>
216+
<label style="display: block; margin-bottom: var(--UI-Spacing-spacing-xs); font-weight: 500; font-size: var(--Fonts-Body-Default-sm); color: var(--Colors-Text-Body-Medium);">
217+
Expected Value
218+
</label>
219+
<input
220+
type="text"
221+
class="input"
222+
style="width: 100%; box-sizing: border-box;"
223+
value="${cell.expectedValue || ''}"
224+
placeholder="e.g., 10"
225+
onchange="handleCellChange(${index}, 'expectedValue', event.target.value)"
226+
/>
227+
</div>
228+
` : `
229+
<div>
230+
<label style="display: block; margin-bottom: var(--UI-Spacing-spacing-xs); font-weight: 500; font-size: var(--Fonts-Body-Default-sm); color: var(--Colors-Text-Body-Medium);">
231+
Expected Function
232+
</label>
233+
<input
234+
type="text"
235+
class="input"
236+
style="width: 100%; box-sizing: border-box;"
237+
value="${cell.expectedFunction || ''}"
238+
placeholder="e.g., =SUM(A1:A10)"
239+
onchange="handleCellChange(${index}, 'expectedFunction', event.target.value)"
240+
/>
241+
</div>
242+
`}
243+
</div>
244+
</div>
245+
`;
246+
}).join('')}
247+
248+
${config.cellsToVerify.length === 0 ? `
249+
<div style="text-align: left; padding: var(--UI-Spacing-spacing-xl); color: var(--Colors-Text-Body-Light);">
250+
No cells configured. Click "Add Cell" below to get started.
251+
</div>
252+
` : ''}
253+
</div>
254+
255+
<div style="margin-top: var(--UI-Spacing-spacing-m); width: 100%;">
256+
<button class="button button-primary" onclick="addCellVerification()">
257+
Add Cell
258+
</button>
259+
</div>
260+
</div>
261+
</div>
262+
`;
263+
}
264+
265+
// Make functions globally available for inline handlers
266+
window.handleSpreadsheetURLChange = handleSpreadsheetURLChange;
267+
window.handleCellChange = handleCellChange;
268+
window.handleVerificationTypeChange = handleVerificationTypeChange;
269+
window.addCellVerification = addCellVerification;
270+
window.removeCellVerification = removeCellVerification;
271+
272+
// Initialize app when DOM is ready
273+
if (document.readyState === 'loading') {
274+
document.addEventListener('DOMContentLoaded', loadConfig);
275+
} else {
276+
loadConfig();
277+
}

client/design-system

Submodule design-system added at a298587

client/index.html

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Configuration Editor</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<!-- Fonts (Work Sans) -->
8+
<link rel="preconnect" href="https://fonts.googleapis.com">
9+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10+
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
11+
12+
<!-- Design System Foundations -->
13+
<link rel="stylesheet" href="./design-system/colors/colors.css">
14+
<link rel="stylesheet" href="./design-system/spacing/spacing.css">
15+
<link rel="stylesheet" href="./design-system/typography/typography.css">
16+
17+
<!-- Design System Components -->
18+
<link rel="stylesheet" href="./design-system/components/button/button.css">
19+
<link rel="stylesheet" href="./design-system/components/boxes/boxes.css">
20+
<link rel="stylesheet" href="./design-system/components/input/input.css">
21+
<link rel="stylesheet" href="./design-system/components/dropdown/dropdown.css">
22+
<link rel="stylesheet" href="./design-system/components/tags/tags.css">
23+
24+
<style>
25+
body {
26+
margin: 0;
27+
padding: 0;
28+
background-color: var(--Colors-Backgrounds-Main-Default);
29+
font-family: var(--body-family);
30+
color: var(--Colors-Text-Body-Default);
31+
min-height: 100vh;
32+
}
33+
34+
.header {
35+
display: flex;
36+
align-items: center;
37+
padding: var(--UI-Spacing-spacing-l) var(--UI-Spacing-spacing-xl);
38+
background: var(--Colors-Box-Background);
39+
border-bottom: 1px solid var(--Colors-Box-Stroke);
40+
gap: var(--UI-Spacing-spacing-m);
41+
}
42+
43+
.header h1 {
44+
margin: 0;
45+
color: var(--Colors-Text-Body-Strong);
46+
}
47+
48+
#app {
49+
max-width: 1200px;
50+
margin: 0 auto;
51+
padding: var(--UI-Spacing-spacing-xl);
52+
}
53+
54+
.config-editor h3 {
55+
font-size: var(--Fonts-Body-Default-md);
56+
font-weight: 600;
57+
color: var(--Colors-Text-Body-Strong);
58+
}
59+
60+
.cell-item {
61+
transition: border-color 0.2s ease;
62+
}
63+
64+
.cell-item:hover {
65+
border-color: var(--Colors-Box-Stroke-Hover);
66+
}
67+
68+
#spreadsheet-url {
69+
min-width: 0;
70+
box-sizing: border-box;
71+
}
72+
</style>
73+
</head>
74+
<body class="bespoke">
75+
<!-- Navigation Header -->
76+
<header class="header">
77+
<h1 class="heading-large">Spreadsheet Configuration</h1>
78+
</header>
79+
80+
<!-- Main Application Content -->
81+
<main id="app">
82+
<div style="text-align: center; padding: var(--UI-Spacing-spacing-xl);">
83+
Loading configuration...
84+
</div>
85+
</main>
86+
87+
<!-- Core Scripts -->
88+
<script src="./app.js"></script>
89+
</body>
90+
</html>

config-example.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
spreadsheetURL: https://docs.google.com/spreadsheets/d/1ouStwWebpGQSREQSGpceyg8HgKHrDLZ6ONTja4FU-ug/
2+
cellsToVerify:
3+
- cellName: C4
4+
expectedValue: iPhone 16
5+
- cellName: D9
6+
expectedValue: "5"
7+
- cellName: B14
8+
expectedValue: Charlie Davis
9+
- cellName: E18
10+
expectedValue: "1820"
11+
- cellName: D53
12+
expectedFunction: =SUM(D2:D51)
13+
- cellName: ""
14+
expectedFunction: ""

0 commit comments

Comments
 (0)