Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit711675bunknown_key

polish(repo): 2026 cards for /:user/:repo/pages (re-polish from niche agent)

polish(repo): 2026 cards for /:user/:repo/pages (re-polish from niche agent)

Picked up the actual polish version from the niche-repo agent. Earlier
commit was empty (file matched HEAD at commit time due to the worktree
sharing quirk). This commit has the real polish.

Deployed URL + last-built timestamp in tabular-nums + branch + custom
domain config card + dashed empty state with orb.

Scoped .pages-* CSS. All data fetches and form actions preserved.
Claude committed on May 25, 2026Parent: b434a1e
1 file changed+631118711675beffa26ee3cd0ea03c67d386ba810e8303
1 changed file+631−118
Modifiedsrc/routes/pages.tsx+631−118View fileUnifiedSplit
1111 * The serving endpoint reads blobs directly out of the bare git repo at the
1212 * commit sha of the most recent pages_deployments row for that repo. There is
1313 * no on-disk export — the git store IS the CDN.
14 *
15 * 2026 polish: settings UI uses a scoped `.pages-*` class system mirroring
16 * `admin-ops.tsx` — eyebrow + display headline, polished settings card with
17 * gradient hairline, deployment table with tabular-nums + status pills, and
18 * an orb-lit dashed empty state for "no deployments yet".
1419 */
1520
1621import { Hono } from "hono";
9095 };
9196}
9297
98// ─── Scoped CSS (.pages-*) ──────────────────────────────────────────────────
99const pagesStyles = `
100 .pages-wrap { max-width: 1100px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-8); }
101
102 .pages-head {
103 display: flex;
104 align-items: flex-end;
105 justify-content: space-between;
106 gap: var(--space-4);
107 flex-wrap: wrap;
108 margin-bottom: var(--space-5);
109 }
110 .pages-head-text { flex: 1; min-width: 280px; }
111 .pages-eyebrow {
112 display: inline-flex;
113 align-items: center;
114 gap: 8px;
115 text-transform: uppercase;
116 font-family: var(--font-mono);
117 font-size: 11px;
118 letter-spacing: 0.16em;
119 color: var(--text-muted);
120 font-weight: 600;
121 margin-bottom: 10px;
122 }
123 .pages-eyebrow-dot {
124 width: 8px; height: 8px;
125 border-radius: 9999px;
126 background: linear-gradient(135deg, #8c6dff, #36c5d6);
127 box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
128 }
129 .pages-title {
130 font-family: var(--font-display);
131 font-size: clamp(24px, 3.4vw, 36px);
132 font-weight: 800;
133 letter-spacing: -0.028em;
134 line-height: 1.1;
135 margin: 0 0 6px;
136 color: var(--text-strong);
137 }
138 .pages-title-grad {
139 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
140 -webkit-background-clip: text;
141 background-clip: text;
142 -webkit-text-fill-color: transparent;
143 color: transparent;
144 }
145 .pages-sub {
146 margin: 0;
147 font-size: 14px;
148 color: var(--text-muted);
149 line-height: 1.5;
150 max-width: 640px;
151 }
152
153 /* Banners */
154 .pages-banner {
155 margin-bottom: var(--space-4);
156 padding: 10px 14px;
157 border-radius: 10px;
158 font-size: 13.5px;
159 border: 1px solid var(--border);
160 background: rgba(255,255,255,0.025);
161 display: flex;
162 align-items: center;
163 gap: 10px;
164 }
165 .pages-banner.is-ok {
166 border-color: rgba(52,211,153,0.40);
167 background: rgba(52,211,153,0.08);
168 color: #bbf7d0;
169 }
170 .pages-banner.is-info {
171 border-color: rgba(54,197,214,0.40);
172 background: rgba(54,197,214,0.08);
173 color: #a5f3fc;
174 }
175 .pages-banner.is-error {
176 border-color: rgba(248,113,113,0.40);
177 background: rgba(248,113,113,0.08);
178 color: #fecaca;
179 }
180 .pages-banner-dot { width: 8px; height: 8px; border-radius: 9999px; background: currentColor; flex-shrink: 0; }
181
182 /* Section cards */
183 .pages-section {
184 background: var(--bg-elevated);
185 border: 1px solid var(--border);
186 border-radius: 14px;
187 overflow: hidden;
188 margin-bottom: var(--space-5);
189 position: relative;
190 }
191 .pages-section::before {
192 content: '';
193 position: absolute;
194 top: 0; left: 0; right: 0;
195 height: 2px;
196 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
197 opacity: 0.55;
198 pointer-events: none;
199 }
200 .pages-section-head {
201 padding: var(--space-4) var(--space-5) var(--space-3);
202 border-bottom: 1px solid var(--border);
203 display: flex;
204 align-items: flex-start;
205 justify-content: space-between;
206 gap: var(--space-3);
207 flex-wrap: wrap;
208 }
209 .pages-section-head-text { flex: 1; min-width: 240px; }
210 .pages-section-title {
211 margin: 0;
212 font-family: var(--font-display);
213 font-size: 16px;
214 font-weight: 700;
215 letter-spacing: -0.018em;
216 color: var(--text-strong);
217 display: flex;
218 align-items: center;
219 gap: 10px;
220 }
221 .pages-section-title-icon {
222 display: inline-flex;
223 align-items: center;
224 justify-content: center;
225 width: 26px; height: 26px;
226 border-radius: 8px;
227 background: rgba(140,109,255,0.12);
228 color: #b69dff;
229 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.28);
230 flex-shrink: 0;
231 }
232 .pages-section-sub {
233 margin: 6px 0 0 36px;
234 font-size: 12.5px;
235 color: var(--text-muted);
236 line-height: 1.45;
237 }
238 .pages-section-body { padding: var(--space-4) var(--space-5); }
239
240 /* Site URL card */
241 .pages-url-card {
242 display: flex;
243 align-items: center;
244 justify-content: space-between;
245 gap: 14px;
246 flex-wrap: wrap;
247 padding: 14px 16px;
248 background: rgba(255,255,255,0.02);
249 border: 1px solid var(--border);
250 border-radius: 12px;
251 }
252 .pages-url-label {
253 font-family: var(--font-mono);
254 font-size: 10.5px;
255 text-transform: uppercase;
256 letter-spacing: 0.14em;
257 color: var(--text-muted);
258 margin: 0 0 4px;
259 font-weight: 600;
260 }
261 .pages-url-value {
262 font-family: var(--font-mono);
263 font-size: 13px;
264 word-break: break-all;
265 }
266 .pages-url-value a {
267 color: #c4b5fd;
268 text-decoration: none;
269 }
270 .pages-url-value a:hover { color: #a48bff; text-decoration: underline; }
271
272 /* Fields */
273 .pages-field { margin-bottom: 16px; }
274 .pages-field-label {
275 display: block;
276 font-size: 11.5px;
277 color: var(--text-muted);
278 font-weight: 600;
279 text-transform: uppercase;
280 letter-spacing: 0.06em;
281 margin-bottom: 6px;
282 }
283 .pages-input {
284 width: 100%;
285 box-sizing: border-box;
286 padding: 9px 12px;
287 font: inherit;
288 font-size: 13.5px;
289 color: var(--text);
290 background: rgba(255,255,255,0.03);
291 border: 1px solid var(--border-strong);
292 border-radius: 10px;
293 transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease;
294 }
295 .pages-input:focus {
296 outline: none;
297 border-color: rgba(140,109,255,0.55);
298 background: rgba(255,255,255,0.05);
299 box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
300 }
301 .pages-help {
302 font-size: 12px;
303 color: var(--text-muted);
304 margin-top: 4px;
305 line-height: 1.45;
306 }
307 .pages-check { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; cursor: pointer; }
308
309 /* Buttons */
310 .pages-btn {
311 display: inline-flex;
312 align-items: center;
313 justify-content: center;
314 gap: 6px;
315 padding: 9px 16px;
316 border-radius: 10px;
317 font-size: 13px;
318 font-weight: 600;
319 text-decoration: none;
320 border: 1px solid transparent;
321 cursor: pointer;
322 font: inherit;
323 line-height: 1;
324 white-space: nowrap;
325 transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease, border-color 120ms ease, color 120ms ease;
326 }
327 .pages-btn-primary {
328 background: linear-gradient(135deg, #8c6dff 0%, #36c5d6 100%);
329 color: #ffffff;
330 box-shadow: 0 6px 18px -6px rgba(140,109,255,0.50), inset 0 1px 0 rgba(255,255,255,0.16);
331 }
332 .pages-btn-primary:hover {
333 transform: translateY(-1px);
334 box-shadow: 0 10px 24px -8px rgba(140,109,255,0.60), inset 0 1px 0 rgba(255,255,255,0.20);
335 text-decoration: none;
336 color: #ffffff;
337 }
338 .pages-btn-ghost {
339 background: transparent;
340 color: var(--text);
341 border-color: var(--border-strong);
342 }
343 .pages-btn-ghost:hover {
344 background: rgba(140,109,255,0.06);
345 border-color: rgba(140,109,255,0.45);
346 color: var(--text-strong);
347 text-decoration: none;
348 }
349
350 /* Deploy list */
351 .pages-deploys {
352 background: rgba(255,255,255,0.02);
353 border: 1px solid var(--border);
354 border-radius: 12px;
355 overflow: hidden;
356 }
357 .pages-deploy-row {
358 display: grid;
359 grid-template-columns: 1.4fr 1fr 1fr auto;
360 gap: 12px;
361 align-items: center;
362 padding: 10px 14px;
363 border-bottom: 1px solid var(--border);
364 font-size: 12.5px;
365 font-variant-numeric: tabular-nums;
366 }
367 .pages-deploy-row:last-child { border-bottom: 0; }
368 .pages-deploy-row.is-head {
369 font-family: var(--font-mono);
370 font-size: 10.5px;
371 text-transform: uppercase;
372 letter-spacing: 0.12em;
373 color: var(--text-muted);
374 font-weight: 600;
375 background: rgba(255,255,255,0.02);
376 }
377 .pages-deploy-row code {
378 font-family: var(--font-mono);
379 font-size: 11.5px;
380 padding: 1px 6px;
381 border-radius: 6px;
382 background: rgba(255,255,255,0.04);
383 border: 1px solid var(--border);
384 color: var(--text);
385 }
386 .pages-status {
387 display: inline-flex;
388 align-items: center;
389 gap: 5px;
390 padding: 2px 9px;
391 border-radius: 9999px;
392 font-size: 11px;
393 font-weight: 600;
394 letter-spacing: 0.02em;
395 text-transform: capitalize;
396 justify-self: end;
397 }
398 .pages-status .dot { width: 6px; height: 6px; border-radius: 9999px; background: currentColor; }
399 .pages-status.is-success {
400 background: rgba(52,211,153,0.14);
401 color: #6ee7b7;
402 box-shadow: inset 0 0 0 1px rgba(52,211,153,0.32);
403 }
404 .pages-status.is-failed {
405 background: rgba(248,113,113,0.14);
406 color: #fca5a5;
407 box-shadow: inset 0 0 0 1px rgba(248,113,113,0.32);
408 }
409 .pages-status.is-pending {
410 background: rgba(251,191,36,0.12);
411 color: #fde68a;
412 box-shadow: inset 0 0 0 1px rgba(251,191,36,0.32);
413 }
414
415 /* Empty */
416 .pages-empty {
417 position: relative;
418 overflow: hidden;
419 padding: clamp(28px, 5vw, 48px) clamp(20px, 4vw, 36px);
420 text-align: center;
421 background: var(--bg-elevated);
422 border: 1px dashed var(--border-strong);
423 border-radius: 16px;
424 }
425 .pages-empty-orb {
426 position: absolute;
427 inset: -40% 30% auto 30%;
428 height: 280px;
429 background: radial-gradient(circle, rgba(140,109,255,0.18), rgba(54,197,214,0.10) 45%, transparent 70%);
430 filter: blur(70px);
431 opacity: 0.7;
432 pointer-events: none;
433 z-index: 0;
434 }
435 .pages-empty-inner { position: relative; z-index: 1; }
436 .pages-empty-icon {
437 width: 56px; height: 56px;
438 border-radius: 9999px;
439 background: linear-gradient(135deg, rgba(140,109,255,0.25), rgba(54,197,214,0.20));
440 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.40);
441 display: inline-flex;
442 align-items: center;
443 justify-content: center;
444 color: #c4b5fd;
445 margin-bottom: 14px;
446 }
447 .pages-empty-title {
448 font-family: var(--font-display);
449 font-size: 18px;
450 font-weight: 700;
451 margin: 0 0 6px;
452 color: var(--text-strong);
453 }
454 .pages-empty-sub {
455 margin: 0 auto;
456 font-size: 13.5px;
457 color: var(--text-muted);
458 max-width: 420px;
459 line-height: 1.5;
460 }
461`;
462
463function IconGlobe() {
464 return (
465 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
466 <circle cx="12" cy="12" r="10" />
467 <line x1="2" y1="12" x2="22" y2="12" />
468 <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
469 </svg>
470 );
471}
472function IconRocket() {
473 return (
474 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
475 <path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z" />
476 <path d="M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" />
477 <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" />
478 <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" />
479 </svg>
480 );
481}
482function IconSettings() {
483 return (
484 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
485 <circle cx="12" cy="12" r="3" />
486 <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
487 </svg>
488 );
489}
490
491function statusPillClass(s: string): string {
492 if (s === "success") return "pages-status is-success";
493 if (s === "failed" || s === "error") return "pages-status is-failed";
494 return "pages-status is-pending";
495}
496
497function shortDate(d: Date | string | null | undefined): string {
498 if (!d) return "—";
499 const dt = d instanceof Date ? d : new Date(d);
500 if (Number.isNaN(dt.getTime())) return "—";
501 return dt.toISOString().slice(0, 16).replace("T", " ");
502}
503
93504// ---------------------------------------------------------------------------
94505// Serve: GET /:owner/:repo/pages/*
95506// ---------------------------------------------------------------------------
196607 if (repoRow.ownerId !== user.id) {
197608 return c.html(
198609 <Layout title="Unauthorized" user={user}>
199 <div class="empty-state">
200 <h2>Unauthorized</h2>
201 <p>Only the repository owner can configure Pages.</p>
610 <div class="pages-wrap">
611 <div class="pages-empty">
612 <div class="pages-empty-orb" aria-hidden="true" />
613 <div class="pages-empty-inner">
614 <h2 class="pages-empty-title">Unauthorized</h2>
615 <p class="pages-empty-sub">Only the repository owner can configure Pages.</p>
616 </div>
617 </div>
202618 </div>
619 <style dangerouslySetInnerHTML={{ __html: pagesStyles }} />
203620 </Layout>,
204621 403
205622 );
242659 >
243660 <RepoHeader owner={ownerName} repo={repoName} />
244661 <RepoNav owner={ownerName} repo={repoName} active="code" />
245 <div style="max-width: 720px">
246 <h2 style="margin-bottom: 16px">Pages</h2>
662 <div class="pages-wrap">
663 <header class="pages-head">
664 <div class="pages-head-text">
665 <div class="pages-eyebrow">
666 <span class="pages-eyebrow-dot" aria-hidden="true" />
667 Repository · Pages
668 </div>
669 <h1 class="pages-title">
670 <span class="pages-title-grad">Static hosting.</span>
671 </h1>
672 <p class="pages-sub">
673 Publish a static site from {ownerName}/{repoName}. Every push
674 to the source branch becomes a fresh deployment served straight
675 from the git object store.
676 </p>
677 </div>
678 </header>
679
247680 {success && (
248 <div class="auth-success">{decodeURIComponent(success)}</div>
681 <div class="pages-banner is-ok" role="status">
682 <span class="pages-banner-dot" aria-hidden="true" />
683 {decodeURIComponent(success)}
684 </div>
685 )}
686 {info && (
687 <div class="pages-banner is-info" role="status">
688 <span class="pages-banner-dot" aria-hidden="true" />
689 {decodeURIComponent(info)}
690 </div>
249691 )}
250 {info && <div class="auth-success">{decodeURIComponent(info)}</div>}
251692 {error && (
252 <div class="auth-error">{decodeURIComponent(error)}</div>
693 <div class="pages-banner is-error" role="alert">
694 <span class="pages-banner-dot" aria-hidden="true" />
695 {decodeURIComponent(error)}
696 </div>
253697 )}
254698
255 <p style="color: var(--text-muted); margin-bottom: 20px">
256 Publish a static site from this repository. Push to the source
257 branch and every successful push becomes a new deployment.
258 </p>
259
260 <div
261 style="padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 24px; background: var(--bg-muted)"
262 >
263 <div style="font-size: 13px; color: var(--text-muted)">
264 Your site is published at:
265 </div>
266 <div style="margin-top: 4px">
267 <a href={siteUrl}>{siteUrl}</a>
699 {/* Site URL */}
700 <section class="pages-section">
701 <header class="pages-section-head">
702 <div class="pages-section-head-text">
703 <h2 class="pages-section-title">
704 <span class="pages-section-title-icon" aria-hidden="true">
705 <IconGlobe />
706 </span>
707 Deployed URL
708 </h2>
709 <p class="pages-section-sub">
710 The public address of your site. Configure a custom domain
711 below to override this default.
712 </p>
713 </div>
714 </header>
715 <div class="pages-section-body">
716 <div class="pages-url-card">
717 <div>
718 <p class="pages-url-label">Site URL</p>
719 <div class="pages-url-value">
720 <a href={siteUrl}>{siteUrl}</a>
721 </div>
722 </div>
723 {settings.customDomain && (
724 <div>
725 <p class="pages-url-label">Custom domain</p>
726 <div class="pages-url-value">
727 <a href={`https://${settings.customDomain}/`}>
728 https://{settings.customDomain}/
729 </a>
730 </div>
731 </div>
732 )}
733 </div>
268734 </div>
269 </div>
735 </section>
270736
271 <form
272 method="post"
273 action={`/${ownerName}/${repoName}/settings/pages`}
274 >
275 <div class="form-group">
276 <label>
277 <input
278 type="checkbox"
279 name="enabled"
280 value="1"
281 checked={settings.enabled}
282 aria-label="Enable GitHub Pages"
283 />
284 {" "}Enable GitHub Pages
285 </label>
286 </div>
287 <div class="form-group">
288 <label for="source_branch">Source branch</label>
289 <input
290 type="text"
291 id="source_branch"
292 name="source_branch"
293 value={settings.sourceBranch}
294 placeholder="gh-pages"
295 />
296 </div>
297 <div class="form-group">
298 <label for="source_dir">Source directory</label>
299 <input
300 type="text"
301 id="source_dir"
302 name="source_dir"
303 value={settings.sourceDir}
304 placeholder="/"
305 />
306 <div style="font-size: 12px; color: var(--text-muted); margin-top: 4px">
307 Use "/" to serve from the repo root, or e.g. "/docs".
737 {/* Settings form */}
738 <section class="pages-section">
739 <header class="pages-section-head">
740 <div class="pages-section-head-text">
741 <h2 class="pages-section-title">
742 <span class="pages-section-title-icon" aria-hidden="true">
743 <IconSettings />
744 </span>
745 Settings
746 </h2>
747 <p class="pages-section-sub">
748 Choose which branch and folder to publish from, and optionally
749 point a custom domain at this site.
750 </p>
308751 </div>
309 </div>
310 <div class="form-group">
311 <label for="custom_domain">Custom domain (optional)</label>
312 <input
313 type="text"
314 id="custom_domain"
315 name="custom_domain"
316 value={settings.customDomain || ""}
317 placeholder="example.com"
318 />
319 </div>
320 <button type="submit" class="btn btn-primary">
321 Save
322 </button>
323 </form>
324
325 <div style="margin-top: 32px">
326 <div
327 style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px"
328 >
329 <h3>Recent deployments</h3>
752 </header>
753 <div class="pages-section-body">
330754 <form
331755 method="post"
332 action={`/${ownerName}/${repoName}/settings/pages/redeploy`}
333 style="display: inline"
756 action={`/${ownerName}/${repoName}/settings/pages`}
334757 >
335 <button type="submit" class="btn">
336 Redeploy from HEAD
758 <div class="pages-field">
759 <label class="pages-check">
760 <input
761 type="checkbox"
762 name="enabled"
763 value="1"
764 checked={settings.enabled}
765 aria-label="Enable Pages"
766 />
767 Enable Pages
768 </label>
769 </div>
770 <div class="pages-field">
771 <label class="pages-field-label" for="source_branch">
772 Source branch
773 </label>
774 <input
775 class="pages-input"
776 type="text"
777 id="source_branch"
778 name="source_branch"
779 value={settings.sourceBranch}
780 placeholder="gh-pages"
781 />
782 </div>
783 <div class="pages-field">
784 <label class="pages-field-label" for="source_dir">
785 Source directory
786 </label>
787 <input
788 class="pages-input"
789 type="text"
790 id="source_dir"
791 name="source_dir"
792 value={settings.sourceDir}
793 placeholder="/"
794 />
795 <div class="pages-help">
796 Use <code>/</code> to serve from the repo root, or e.g.{" "}
797 <code>/docs</code>.
798 </div>
799 </div>
800 <div class="pages-field">
801 <label class="pages-field-label" for="custom_domain">
802 Custom domain (optional)
803 </label>
804 <input
805 class="pages-input"
806 type="text"
807 id="custom_domain"
808 name="custom_domain"
809 value={settings.customDomain || ""}
810 placeholder="example.com"
811 />
812 <div class="pages-help">
813 Point a CNAME at this repo's pages host to terminate at your
814 own domain.
815 </div>
816 </div>
817 <button type="submit" class="pages-btn pages-btn-primary">
818 Save settings
337819 </button>
338820 </form>
339821 </div>
340 {recent.length === 0 ? (
341 <div class="empty-state">
342 <p>
343 No deployments yet — push to{" "}
344 <code>{settings.sourceBranch}</code> to publish.
822 </section>
823
824 {/* Deployments */}
825 <section class="pages-section">
826 <header class="pages-section-head">
827 <div class="pages-section-head-text">
828 <h2 class="pages-section-title">
829 <span class="pages-section-title-icon" aria-hidden="true">
830 <IconRocket />
831 </span>
832 Recent deployments
833 <span style="font-family:var(--font-mono);font-size:12px;color:var(--text-muted);font-weight:500;font-variant-numeric:tabular-nums">
834 {" "}({recent.length})
835 </span>
836 </h2>
837 <p class="pages-section-sub">
838 Latest 10 build attempts on this site. Trigger a manual
839 redeploy from <code>HEAD</code> of the source branch any time.
345840 </p>
346841 </div>
347 ) : (
348 <table
349 style="width: 100%; border-collapse: collapse; font-size: 13px"
842 <form
843 method="post"
844 action={`/${ownerName}/${repoName}/settings/pages/redeploy`}
350845 >
351 <thead>
352 <tr style="text-align: left; color: var(--text-muted)">
353 <th style="padding: 6px 0">When</th>
354 <th>Ref</th>
355 <th>Commit</th>
356 <th>Status</th>
357 </tr>
358 </thead>
359 <tbody>
846 <button type="submit" class="pages-btn pages-btn-ghost">
847 Redeploy from HEAD
848 </button>
849 </form>
850 </header>
851 <div class="pages-section-body">
852 {recent.length === 0 ? (
853 <div class="pages-empty">
854 <div class="pages-empty-orb" aria-hidden="true" />
855 <div class="pages-empty-inner">
856 <div class="pages-empty-icon" aria-hidden="true">
857 <IconRocket />
858 </div>
859 <h3 class="pages-empty-title">Ship your first deploy</h3>
860 <p class="pages-empty-sub">
861 No deployments yet — push to{" "}
862 <code>{settings.sourceBranch}</code> or hit "Redeploy from
863 HEAD" to publish.
864 </p>
865 </div>
866 </div>
867 ) : (
868 <div class="pages-deploys">
869 <div class="pages-deploy-row is-head">
870 <div>When</div>
871 <div>Ref</div>
872 <div>Commit</div>
873 <div style="text-align:right">Status</div>
874 </div>
360875 {recent.map((d) => (
361 <tr style="border-top: 1px solid var(--border)">
362 <td style="padding: 6px 0">
363 {new Date(d.createdAt).toISOString()}
364 </td>
365 <td>
876 <div class="pages-deploy-row">
877 <div>{shortDate(d.createdAt)}</div>
878 <div>
366879 <code>{d.ref}</code>
367 </td>
368 <td>
880 </div>
881 <div>
369882 <code>{d.commitSha.slice(0, 7)}</code>
370 </td>
371 <td
372 style={`color: ${d.status === "success" ? "var(--green)" : "var(--red)"}`}
373 >
883 </div>
884 <span class={statusPillClass(d.status)}>
885 <span class="dot" aria-hidden="true" />
374886 {d.status}
375 </td>
376 </tr>
887 </span>
888 </div>
377889 ))}
378 </tbody>
379 </table>
380 )}
381 </div>
890 </div>
891 )}
892 </div>
893 </section>
382894 </div>
895 <style dangerouslySetInnerHTML={{ __html: pagesStyles }} />
383896 </Layout>
384897 );
385898 }
386899