+381
-309
client/index.html
+381
-309
client/index.html
···
1
-
<!DOCTYPE html>
1
+
<!doctype html>
2
2
<html lang="en">
3
-
<head>
4
-
<meta charset="UTF-8">
5
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
-
<title>Autonomy Declaration - ATProtocol</title>
7
-
<script type="importmap">
8
-
{
9
-
"imports": {
10
-
"@atproto/api": "https://esm.sh/@atproto/api@0.13.11"
3
+
<head>
4
+
<meta charset="UTF-8" />
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+
<title>Autonomy Declaration - ATProtocol</title>
7
+
<link rel="stylesheet" href="styles.css" />
8
+
<script type="importmap">
9
+
{
10
+
"imports": {
11
+
"@atproto/api": "https://esm.sh/@atproto/api@0.13.11"
12
+
}
11
13
}
12
-
}
13
-
</script>
14
-
</head>
15
-
<body>
16
-
<header>
17
-
<h1>Autonomy Declaration</h1>
18
-
<p>Declare automation and AI usage for transparency and accountability on Bluesky</p>
19
-
</header>
14
+
</script>
15
+
</head>
16
+
<body>
17
+
<header class="l:root">
18
+
<h1>Autonomy Declaration</h1>
19
+
<p>
20
+
Declare automation and AI usage for transparency and accountability on
21
+
Bluesky
22
+
</p>
23
+
</header>
20
24
21
-
<main>
22
-
<form id="declaration-form" novalidate>
23
-
<section>
24
-
<h2>Authentication</h2>
25
+
<main class="l:root">
26
+
<form id="declaration-form" class="l:waterfall" novalidate>
27
+
<section>
28
+
<h2>Authentication</h2>
25
29
26
-
<div>
27
-
<label for="handle">Bluesky Handle (required)</label>
28
-
<input
29
-
type="text"
30
-
id="handle"
31
-
name="handle"
32
-
placeholder="username.bsky.social"
33
-
required
34
-
autocomplete="username"
35
-
/>
36
-
<small>Your Bluesky handle or DID</small>
37
-
<p id="handle-error" role="alert" hidden></p>
38
-
</div>
30
+
<div>
31
+
<label for="handle">Bluesky Handle (required)</label>
32
+
<input
33
+
type="text"
34
+
id="handle"
35
+
name="handle"
36
+
placeholder="username.bsky.social"
37
+
required
38
+
autocomplete="username"
39
+
/>
40
+
<small>Your Bluesky handle or DID</small>
41
+
<p id="handle-error" role="alert" hidden></p>
42
+
</div>
39
43
40
-
<div>
41
-
<label for="password">App Password (required)</label>
42
-
<input
43
-
type="password"
44
-
id="password"
45
-
name="password"
46
-
placeholder="xxxx-xxxx-xxxx-xxxx"
47
-
required
48
-
autocomplete="current-password"
49
-
pattern=".{19,19}"
50
-
/>
51
-
<small>19 characters with 3 dashes (generate in Bluesky settings)</small>
52
-
<p id="password-error" role="alert" hidden></p>
53
-
</div>
54
-
</section>
44
+
<div>
45
+
<label for="password">App Password (required)</label>
46
+
<input
47
+
type="password"
48
+
id="password"
49
+
name="password"
50
+
placeholder="xxxx-xxxx-xxxx-xxxx"
51
+
required
52
+
autocomplete="current-password"
53
+
pattern=".{19,19}"
54
+
/>
55
+
<small
56
+
>19 characters with 3 dashes (generate in Bluesky settings)</small
57
+
>
58
+
<p id="password-error" role="alert" hidden></p>
59
+
</div>
60
+
</section>
55
61
56
-
<section>
57
-
<h2>Declaration Details</h2>
62
+
<section>
63
+
<h2>Declaration Details</h2>
58
64
59
-
<div>
60
-
<label for="automationLevel">Automation Level</label>
61
-
<select id="automationLevel" name="automationLevel">
62
-
<option value="">Select level (optional)</option>
63
-
<option value="human">Human - No automation</option>
64
-
<option value="assisted">Assisted - Tools help but human decides</option>
65
-
<option value="collaborative">Collaborative - Human and AI work together</option>
66
-
<option value="automated">Automated - Primarily AI-driven</option>
67
-
</select>
68
-
<small>Level of automation in account management and content creation</small>
69
-
</div>
65
+
<div>
66
+
<label for="automationLevel">Automation Level</label>
67
+
<select id="automationLevel" name="automationLevel">
68
+
<option value="">Select level (optional)</option>
69
+
<option value="human">Human - No automation</option>
70
+
<option value="assisted">
71
+
Assisted - Tools help but human decides
72
+
</option>
73
+
<option value="collaborative">
74
+
Collaborative - Human and AI work together
75
+
</option>
76
+
<option value="automated">Automated - Primarily AI-driven</option>
77
+
</select>
78
+
<small
79
+
>Level of automation in account management and content
80
+
creation</small
81
+
>
82
+
</div>
70
83
71
-
<div>
72
-
<label>
73
-
<input type="checkbox" id="usesGenerativeAI" name="usesGenerativeAI" />
74
-
Uses Generative AI
75
-
</label>
76
-
<small>Check if this account uses LLMs, image generation, etc.</small>
77
-
</div>
84
+
<div>
85
+
<label>
86
+
<input
87
+
type="checkbox"
88
+
id="usesGenerativeAI"
89
+
name="usesGenerativeAI"
90
+
/>
91
+
Uses Generative AI
92
+
</label>
93
+
<small
94
+
>Check if this account uses LLMs, image generation, etc.</small
95
+
>
96
+
</div>
78
97
79
-
<div>
80
-
<label for="description">Description</label>
81
-
<textarea
82
-
id="description"
83
-
name="description"
84
-
rows="4"
85
-
maxlength="300"
86
-
placeholder="Explain how this account is automated and what it does..."
87
-
></textarea>
88
-
<small>Plain language explanation (max 300 characters, optional)</small>
89
-
<p id="description-error" role="alert" hidden></p>
90
-
</div>
91
-
</section>
98
+
<div>
99
+
<label for="description">Description</label>
100
+
<textarea
101
+
id="description"
102
+
name="description"
103
+
rows="4"
104
+
maxlength="300"
105
+
placeholder="Explain how this account is automated and what it does..."
106
+
></textarea>
107
+
<small
108
+
>Plain language explanation (max 300 characters, optional)</small
109
+
>
110
+
<p id="description-error" role="alert" hidden></p>
111
+
</div>
112
+
</section>
92
113
93
-
<fieldset>
94
-
<legend>Responsible Party (optional)</legend>
114
+
<fieldset>
115
+
<legend>Responsible Party (optional)</legend>
95
116
96
-
<div>
97
-
<label for="responsibleType">Type</label>
98
-
<select id="responsibleType" name="responsibleType">
99
-
<option value="">Select type (optional)</option>
100
-
<option value="person">Person</option>
101
-
<option value="organization">Organization</option>
102
-
</select>
103
-
</div>
104
-
105
-
<div>
106
-
<label for="responsibleName">Name</label>
107
-
<input
108
-
type="text"
109
-
id="responsibleName"
110
-
name="responsibleName"
111
-
maxlength="100"
112
-
placeholder="Name of person or organization"
113
-
/>
114
-
<p id="responsibleName-error" role="alert" hidden></p>
115
-
</div>
117
+
<div>
118
+
<label for="responsibleType">Type</label>
119
+
<select id="responsibleType" name="responsibleType">
120
+
<option value="">Select type (optional)</option>
121
+
<option value="person">Person</option>
122
+
<option value="organization">Organization</option>
123
+
</select>
124
+
</div>
116
125
117
-
<div>
118
-
<label for="responsibleContact">Contact</label>
119
-
<input
120
-
type="text"
121
-
id="responsibleContact"
122
-
name="responsibleContact"
123
-
maxlength="300"
124
-
placeholder="Email, URL, handle, or DID"
125
-
/>
126
-
<p id="responsibleContact-error" role="alert" hidden></p>
127
-
</div>
126
+
<div>
127
+
<label for="responsibleName">Name</label>
128
+
<input
129
+
type="text"
130
+
id="responsibleName"
131
+
name="responsibleName"
132
+
maxlength="100"
133
+
placeholder="Name of person or organization"
134
+
/>
135
+
<p id="responsibleName-error" role="alert" hidden></p>
136
+
</div>
128
137
129
-
<div>
130
-
<label for="responsibleDid">DID</label>
131
-
<input
132
-
type="text"
133
-
id="responsibleDid"
134
-
name="responsibleDid"
135
-
placeholder="did:plc:..."
136
-
/>
137
-
<small>ATProto DID if they have an identity</small>
138
-
<p id="responsibleDid-error" role="alert" hidden></p>
139
-
</div>
140
-
</fieldset>
138
+
<div>
139
+
<label for="responsibleContact">Contact</label>
140
+
<input
141
+
type="text"
142
+
id="responsibleContact"
143
+
name="responsibleContact"
144
+
maxlength="300"
145
+
placeholder="Email, URL, handle, or DID"
146
+
/>
147
+
<p id="responsibleContact-error" role="alert" hidden></p>
148
+
</div>
141
149
142
-
<section>
143
-
<h2>Additional Information</h2>
150
+
<div>
151
+
<label for="responsibleDid">DID</label>
152
+
<input
153
+
type="text"
154
+
id="responsibleDid"
155
+
name="responsibleDid"
156
+
placeholder="did:plc:..."
157
+
/>
158
+
<small>ATProto DID if they have an identity</small>
159
+
<p id="responsibleDid-error" role="alert" hidden></p>
160
+
</div>
161
+
</fieldset>
144
162
145
-
<div>
146
-
<label for="disclosureUrl">Disclosure URL</label>
147
-
<input
148
-
type="url"
149
-
id="disclosureUrl"
150
-
name="disclosureUrl"
151
-
placeholder="https://..."
152
-
/>
153
-
<small>URL with additional information about automation (optional)</small>
154
-
<p id="disclosureUrl-error" role="alert" hidden></p>
155
-
</div>
163
+
<section>
164
+
<h2>Additional Information</h2>
156
165
157
-
<div>
158
-
<label for="externalServices">External Services</label>
159
-
<textarea
160
-
id="externalServices"
161
-
name="externalServices"
162
-
rows="5"
163
-
placeholder="One service per line, e.g.: Letta Railway Google Gemini 2.5-pro"
164
-
></textarea>
165
-
<small>External tools and services used (one per line, max 20 services, 200 chars each)</small>
166
-
<p id="externalServices-error" role="alert" hidden></p>
167
-
</div>
168
-
</section>
166
+
<div>
167
+
<label for="disclosureUrl">Disclosure URL</label>
168
+
<input
169
+
type="url"
170
+
id="disclosureUrl"
171
+
name="disclosureUrl"
172
+
placeholder="https://..."
173
+
/>
174
+
<small
175
+
>URL with additional information about automation
176
+
(optional)</small
177
+
>
178
+
<p id="disclosureUrl-error" role="alert" hidden></p>
179
+
</div>
169
180
170
-
<button type="submit" id="submit-btn">Submit Declaration</button>
171
-
</form>
181
+
<div>
182
+
<label for="externalServices">External Services</label>
183
+
<textarea
184
+
id="externalServices"
185
+
name="externalServices"
186
+
rows="5"
187
+
placeholder="One service per line, e.g.: Letta Railway Google Gemini 2.5-pro"
188
+
></textarea>
189
+
<small
190
+
>External tools and services used (one per line, max 20 services,
191
+
200 chars each)</small
192
+
>
193
+
<p id="externalServices-error" role="alert" hidden></p>
194
+
</div>
195
+
</section>
172
196
173
-
<div id="result" role="status" aria-live="polite" hidden></div>
174
-
</main>
197
+
<button type="submit" id="submit-btn">Submit Declaration</button>
198
+
</form>
175
199
176
-
<footer>
177
-
<p>
178
-
This form creates a record in your Personal Data Server (PDS) using the
179
-
<code>studio.voyager.account.autonomy</code> lexicon.
180
-
</p>
181
-
</footer>
200
+
<div id="result" role="status" aria-live="polite" hidden></div>
201
+
</main>
182
202
183
-
<script type="module">
184
-
import { BskyAgent } from '@atproto/api';
203
+
<footer class="l:root">
204
+
<p>
205
+
This form creates a record in your Personal Data Server (PDS) using the
206
+
<code>studio.voyager.account.autonomy</code> lexicon.
207
+
</p>
208
+
</footer>
185
209
186
-
const form = document.getElementById('declaration-form');
187
-
const submitBtn = document.getElementById('submit-btn');
188
-
const resultDiv = document.getElementById('result');
210
+
<script type="module">
211
+
import { BskyAgent } from "@atproto/api";
189
212
190
-
const validators = {
191
-
handle: (value) => {
192
-
if (!value) return 'Handle is required';
193
-
if (!value.includes('.') && !value.startsWith('did:')) {
194
-
return 'Handle must be a valid Bluesky handle (e.g., username.bsky.social) or DID';
195
-
}
196
-
return null;
197
-
},
198
-
password: (value) => {
199
-
if (!value) return 'App password is required';
200
-
if (value.length !== 19) return 'App password must be exactly 19 characters';
201
-
const dashCount = (value.match(/-/g) || []).length;
202
-
if (dashCount !== 3) return 'App password must contain exactly 3 dashes';
203
-
return null;
204
-
},
205
-
description: (value) => value && value.length > 300 ? 'Description must be 300 characters or less' : null,
206
-
responsibleName: (value) => value && value.length > 100 ? 'Name must be 100 characters or less' : null,
207
-
responsibleContact: (value) => value && value.length > 300 ? 'Contact must be 300 characters or less' : null,
208
-
responsibleDid: (value) => value && !value.startsWith('did:') ? 'DID must start with "did:"' : null,
209
-
disclosureUrl: (value) => {
210
-
if (value) {
211
-
try { new URL(value); } catch { return 'Must be a valid URL'; }
212
-
}
213
-
return null;
214
-
},
215
-
externalServices: (value) => {
216
-
if (value) {
217
-
const services = value.split('\n').map(s => s.trim()).filter(s => s.length > 0);
218
-
if (services.length > 20) return 'Maximum 20 external services allowed';
219
-
for (const service of services) {
220
-
if (service.length > 200) return `Service name too long (max 200 chars): ${service.substring(0, 50)}...`;
221
-
}
222
-
}
223
-
return null;
224
-
}
225
-
};
213
+
const form = document.getElementById("declaration-form");
214
+
const submitBtn = document.getElementById("submit-btn");
215
+
const resultDiv = document.getElementById("result");
226
216
227
-
function showError(fieldId, message) {
228
-
const errorElement = document.getElementById(`${fieldId}-error`);
229
-
if (errorElement) {
230
-
errorElement.textContent = message;
231
-
errorElement.hidden = false;
232
-
}
233
-
}
217
+
const validators = {
218
+
handle: (value) => {
219
+
if (!value) return "Handle is required";
220
+
if (!value.includes(".") && !value.startsWith("did:")) {
221
+
return "Handle must be a valid Bluesky handle (e.g., username.bsky.social) or DID";
222
+
}
223
+
return null;
224
+
},
225
+
password: (value) => {
226
+
if (!value) return "App password is required";
227
+
if (value.length !== 19)
228
+
return "App password must be exactly 19 characters";
229
+
const dashCount = (value.match(/-/g) || []).length;
230
+
if (dashCount !== 3)
231
+
return "App password must contain exactly 3 dashes";
232
+
return null;
233
+
},
234
+
description: (value) =>
235
+
value && value.length > 300
236
+
? "Description must be 300 characters or less"
237
+
: null,
238
+
responsibleName: (value) =>
239
+
value && value.length > 100
240
+
? "Name must be 100 characters or less"
241
+
: null,
242
+
responsibleContact: (value) =>
243
+
value && value.length > 300
244
+
? "Contact must be 300 characters or less"
245
+
: null,
246
+
responsibleDid: (value) =>
247
+
value && !value.startsWith("did:")
248
+
? 'DID must start with "did:"'
249
+
: null,
250
+
disclosureUrl: (value) => {
251
+
if (value) {
252
+
try {
253
+
new URL(value);
254
+
} catch {
255
+
return "Must be a valid URL";
256
+
}
257
+
}
258
+
return null;
259
+
},
260
+
externalServices: (value) => {
261
+
if (value) {
262
+
const services = value
263
+
.split("\n")
264
+
.map((s) => s.trim())
265
+
.filter((s) => s.length > 0);
266
+
if (services.length > 20)
267
+
return "Maximum 20 external services allowed";
268
+
for (const service of services) {
269
+
if (service.length > 200)
270
+
return `Service name too long (max 200 chars): ${service.substring(0, 50)}...`;
271
+
}
272
+
}
273
+
return null;
274
+
},
275
+
};
234
276
235
-
function clearError(fieldId) {
236
-
const errorElement = document.getElementById(`${fieldId}-error`);
237
-
if (errorElement) {
238
-
errorElement.textContent = '';
239
-
errorElement.hidden = true;
240
-
}
241
-
}
277
+
function showError(fieldId, message) {
278
+
const errorElement = document.getElementById(`${fieldId}-error`);
279
+
if (errorElement) {
280
+
errorElement.textContent = message;
281
+
errorElement.hidden = false;
282
+
}
283
+
}
242
284
243
-
function clearAllErrors() {
244
-
['handle', 'password', 'description', 'responsibleName', 'responsibleContact', 'responsibleDid', 'disclosureUrl', 'externalServices'].forEach(clearError);
245
-
}
285
+
function clearError(fieldId) {
286
+
const errorElement = document.getElementById(`${fieldId}-error`);
287
+
if (errorElement) {
288
+
errorElement.textContent = "";
289
+
errorElement.hidden = true;
290
+
}
291
+
}
246
292
247
-
function validateForm(formData) {
248
-
clearAllErrors();
249
-
let isValid = true;
250
-
for (const [fieldId, validator] of Object.entries(validators)) {
251
-
const value = formData.get(fieldId) || '';
252
-
const error = validator(value);
253
-
if (error) {
254
-
console.log('Validation error for', fieldId, ':', error);
255
-
showError(fieldId, error);
256
-
isValid = false;
257
-
}
258
-
}
259
-
return isValid;
260
-
}
293
+
function clearAllErrors() {
294
+
[
295
+
"handle",
296
+
"password",
297
+
"description",
298
+
"responsibleName",
299
+
"responsibleContact",
300
+
"responsibleDid",
301
+
"disclosureUrl",
302
+
"externalServices",
303
+
].forEach(clearError);
304
+
}
261
305
262
-
form.addEventListener('submit', async (e) => {
263
-
e.preventDefault();
264
-
const formData = new FormData(form);
306
+
function validateForm(formData) {
307
+
clearAllErrors();
308
+
let isValid = true;
309
+
for (const [fieldId, validator] of Object.entries(validators)) {
310
+
const value = formData.get(fieldId) || "";
311
+
const error = validator(value);
312
+
if (error) {
313
+
console.log("Validation error for", fieldId, ":", error);
314
+
showError(fieldId, error);
315
+
isValid = false;
316
+
}
317
+
}
318
+
return isValid;
319
+
}
265
320
266
-
if (!validateForm(formData)) {
267
-
resultDiv.textContent = 'Please fix the errors above before submitting.';
268
-
resultDiv.hidden = false;
269
-
return;
270
-
}
321
+
form.addEventListener("submit", async (e) => {
322
+
e.preventDefault();
323
+
const formData = new FormData(form);
271
324
272
-
submitBtn.disabled = true;
273
-
submitBtn.textContent = 'Submitting...';
274
-
resultDiv.hidden = true;
325
+
if (!validateForm(formData)) {
326
+
resultDiv.textContent =
327
+
"Please fix the errors above before submitting.";
328
+
resultDiv.hidden = false;
329
+
return;
330
+
}
275
331
276
-
try {
277
-
const agent = new BskyAgent({ service: 'https://bsky.social' });
278
-
await agent.login({
279
-
identifier: formData.get('handle').trim(),
280
-
password: formData.get('password')
281
-
});
332
+
submitBtn.disabled = true;
333
+
submitBtn.textContent = "Submitting...";
334
+
resultDiv.hidden = true;
282
335
283
-
const record = {
284
-
$type: 'studio.voyager.account.autonomy',
285
-
createdAt: new Date().toISOString()
286
-
};
336
+
try {
337
+
const agent = new BskyAgent({ service: "https://bsky.social" });
338
+
await agent.login({
339
+
identifier: formData.get("handle").trim(),
340
+
password: formData.get("password"),
341
+
});
287
342
288
-
const automationLevel = formData.get('automationLevel');
289
-
const usesGenerativeAI = formData.get('usesGenerativeAI') === 'on';
290
-
const description = formData.get('description').trim();
291
-
const responsibleType = formData.get('responsibleType');
292
-
const responsibleName = formData.get('responsibleName').trim();
293
-
const responsibleContact = formData.get('responsibleContact').trim();
294
-
const responsibleDid = formData.get('responsibleDid').trim();
295
-
const disclosureUrl = formData.get('disclosureUrl').trim();
296
-
const externalServicesText = formData.get('externalServices').trim();
343
+
const record = {
344
+
$type: "studio.voyager.account.autonomy",
345
+
createdAt: new Date().toISOString(),
346
+
};
297
347
298
-
if (automationLevel) record.automationLevel = automationLevel;
299
-
if (usesGenerativeAI) record.usesGenerativeAI = true;
300
-
if (description) record.description = description;
348
+
const automationLevel = formData.get("automationLevel");
349
+
const usesGenerativeAI = formData.get("usesGenerativeAI") === "on";
350
+
const description = formData.get("description").trim();
351
+
const responsibleType = formData.get("responsibleType");
352
+
const responsibleName = formData.get("responsibleName").trim();
353
+
const responsibleContact = formData.get("responsibleContact").trim();
354
+
const responsibleDid = formData.get("responsibleDid").trim();
355
+
const disclosureUrl = formData.get("disclosureUrl").trim();
356
+
const externalServicesText = formData.get("externalServices").trim();
301
357
302
-
if (responsibleType || responsibleName || responsibleContact || responsibleDid) {
303
-
record.responsibleParty = {};
304
-
if (responsibleType) record.responsibleParty.type = responsibleType;
305
-
if (responsibleName) record.responsibleParty.name = responsibleName;
306
-
if (responsibleContact) record.responsibleParty.contact = responsibleContact;
307
-
if (responsibleDid) record.responsibleParty.did = responsibleDid;
308
-
}
358
+
if (automationLevel) record.automationLevel = automationLevel;
359
+
if (usesGenerativeAI) record.usesGenerativeAI = true;
360
+
if (description) record.description = description;
309
361
310
-
if (disclosureUrl) record.disclosureUrl = disclosureUrl;
362
+
if (
363
+
responsibleType ||
364
+
responsibleName ||
365
+
responsibleContact ||
366
+
responsibleDid
367
+
) {
368
+
record.responsibleParty = {};
369
+
if (responsibleType) record.responsibleParty.type = responsibleType;
370
+
if (responsibleName) record.responsibleParty.name = responsibleName;
371
+
if (responsibleContact)
372
+
record.responsibleParty.contact = responsibleContact;
373
+
if (responsibleDid) record.responsibleParty.did = responsibleDid;
374
+
}
311
375
312
-
if (externalServicesText) {
313
-
const services = externalServicesText.split('\n').map(s => s.trim()).filter(s => s.length > 0).slice(0, 20);
314
-
if (services.length > 0) record.externalServices = services;
315
-
}
376
+
if (disclosureUrl) record.disclosureUrl = disclosureUrl;
316
377
317
-
const response = await agent.api.com.atproto.repo.putRecord({
318
-
repo: agent.session?.did || '',
319
-
collection: 'studio.voyager.account.autonomy',
320
-
rkey: 'self',
321
-
record: record
322
-
});
378
+
if (externalServicesText) {
379
+
const services = externalServicesText
380
+
.split("\n")
381
+
.map((s) => s.trim())
382
+
.filter((s) => s.length > 0)
383
+
.slice(0, 20);
384
+
if (services.length > 0) record.externalServices = services;
385
+
}
323
386
324
-
resultDiv.textContent = `Declaration submitted successfully! Record URI: ${response.data.uri}`;
325
-
resultDiv.hidden = false;
326
-
form.reset();
387
+
const response = await agent.api.com.atproto.repo.putRecord({
388
+
repo: agent.session?.did || "",
389
+
collection: "studio.voyager.account.autonomy",
390
+
rkey: "self",
391
+
record: record,
392
+
});
327
393
328
-
} catch (error) {
329
-
let errorMessage = 'Error: ';
330
-
if (error.message.includes('Invalid identifier or password')) {
331
-
errorMessage += 'Invalid handle or app password. Please check your credentials.';
332
-
} else if (error.message.includes('Network')) {
333
-
errorMessage += 'Network error. Please check your connection and try again.';
334
-
} else if (error.message.includes('AuthenticationRequired')) {
335
-
errorMessage += 'Authentication failed. Please verify your app password.';
336
-
} else {
337
-
errorMessage += error.message;
338
-
}
339
-
resultDiv.textContent = errorMessage;
340
-
resultDiv.hidden = false;
341
-
console.error('Submission error:', error);
342
-
} finally {
343
-
submitBtn.disabled = false;
344
-
submitBtn.textContent = 'Submit Declaration';
345
-
}
346
-
});
347
-
</script>
348
-
</body>
394
+
resultDiv.textContent = `Declaration submitted successfully! Record URI: ${response.data.uri}`;
395
+
resultDiv.hidden = false;
396
+
form.reset();
397
+
} catch (error) {
398
+
let errorMessage = "Error: ";
399
+
if (error.message.includes("Invalid identifier or password")) {
400
+
errorMessage +=
401
+
"Invalid handle or app password. Please check your credentials.";
402
+
} else if (error.message.includes("Network")) {
403
+
errorMessage +=
404
+
"Network error. Please check your connection and try again.";
405
+
} else if (error.message.includes("AuthenticationRequired")) {
406
+
errorMessage +=
407
+
"Authentication failed. Please verify your app password.";
408
+
} else {
409
+
errorMessage += error.message;
410
+
}
411
+
resultDiv.textContent = errorMessage;
412
+
resultDiv.hidden = false;
413
+
console.error("Submission error:", error);
414
+
} finally {
415
+
submitBtn.disabled = false;
416
+
submitBtn.textContent = "Submit Declaration";
417
+
}
418
+
});
419
+
</script>
420
+
</body>
349
421
</html>
+1
client/styles.css
+1
client/styles.css
···
1
+
@import "https://unpkg.com/@taurean/stylebase@0.11.0/dist/stylebase.min.css";
+1
deno.json
+1
deno.json
+11
worker.js
+11
worker.js
···
1
1
import htmlContent from './client/index.html';
2
+
import cssContent from './client/styles.css';
2
3
3
4
export default {
4
5
async fetch(request) {
6
+
const url = new URL(request.url);
7
+
8
+
if (url.pathname === '/styles.css') {
9
+
return new Response(cssContent, {
10
+
headers: {
11
+
'content-type': 'text/css;charset=UTF-8',
12
+
},
13
+
});
14
+
}
15
+
5
16
return new Response(htmlContent, {
6
17
headers: {
7
18
'content-type': 'text/html;charset=UTF-8',