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

polish(repo): 2026 cards + headlines for workflows + environments tabs

Claude committed on May 24, 2026Parent: e7b4b7e
1 file changed+7021459ec3d15315fb301e51099584256cae712b440ff1
1 changed file+702−145
Modifiedsrc/routes/environments.tsx+702−145View fileUnifiedSplit
1111 *
1212 * Approve/reject live under /deployments/:id/... so they don't collide with
1313 * the existing `GET /:owner/:repo/deployments/:id` detail page.
14 *
15 * Visual polish (2026): adopts the gradient-hairline + orb pattern from
16 * admin-integrations / error-page. Page-level CSS is scoped under `.envs-*`
17 * so it can't bleed into the layout. RepoHeader + RepoNav are untouched.
1418 */
1519
1620import { Hono } from "hono";
106110 }
107111}
108112
113function envProtectionCount(env: Environment): number {
114 let n = 0;
115 if (env.requireApproval) n++;
116 if ((env.waitTimerMinutes ?? 0) > 0) n++;
117 if (allowedBranchesOf(env).length > 0) n++;
118 return n;
119}
120
121/* ─────────────────────────────────────────────────────────────────────────
122 * Scoped CSS — every class prefixed `.envs-*` so this surface can't bleed
123 * into other pages. Mirrors the gradient hairline + orb language used by
124 * admin-integrations and error-page.
125 * ───────────────────────────────────────────────────────────────────── */
126const envsStyles = `
127 .envs-wrap { max-width: 1100px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-8); }
128
129 .envs-head {
130 position: relative;
131 margin-bottom: var(--space-5);
132 padding: var(--space-4) var(--space-5);
133 background: var(--bg-elevated);
134 border: 1px solid var(--border);
135 border-radius: 14px;
136 overflow: hidden;
137 }
138 .envs-head::before {
139 content: '';
140 position: absolute;
141 top: 0; left: 0; right: 0;
142 height: 2px;
143 background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
144 opacity: 0.7;
145 pointer-events: none;
146 }
147 .envs-head-orb {
148 position: absolute;
149 inset: -30% -10% auto auto;
150 width: 320px; height: 320px;
151 background: radial-gradient(circle, rgba(140,109,255,0.18), rgba(54,197,214,0.08) 45%, transparent 70%);
152 filter: blur(70px);
153 opacity: 0.7;
154 pointer-events: none;
155 z-index: 0;
156 }
157 .envs-head-inner {
158 position: relative;
159 z-index: 1;
160 display: flex;
161 align-items: flex-end;
162 justify-content: space-between;
163 gap: var(--space-4);
164 flex-wrap: wrap;
165 }
166 .envs-head-text { flex: 1; min-width: 240px; max-width: 720px; }
167 .envs-head-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
168 .envs-eyebrow {
169 display: inline-flex;
170 align-items: center;
171 gap: 8px;
172 font-family: var(--font-mono);
173 font-size: 11px;
174 letter-spacing: 0.14em;
175 text-transform: uppercase;
176 font-weight: 600;
177 color: var(--text-muted);
178 margin-bottom: 10px;
179 }
180 .envs-eyebrow-dot {
181 width: 8px; height: 8px;
182 border-radius: 9999px;
183 background: linear-gradient(135deg, #8c6dff, #36c5d6);
184 box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
185 }
186 .envs-title {
187 margin: 0 0 6px;
188 font-family: var(--font-display);
189 font-size: clamp(22px, 2.6vw, 30px);
190 font-weight: 800;
191 letter-spacing: -0.022em;
192 line-height: 1.1;
193 color: var(--text-strong);
194 }
195 .envs-title-grad {
196 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
197 -webkit-background-clip: text;
198 background-clip: text;
199 -webkit-text-fill-color: transparent;
200 color: transparent;
201 }
202 .envs-sub {
203 margin: 0;
204 font-size: 13.5px;
205 line-height: 1.5;
206 color: var(--text-muted);
207 }
208
209 .envs-banner {
210 margin-bottom: var(--space-4);
211 padding: 10px 14px;
212 border-radius: 10px;
213 font-size: 13.5px;
214 border: 1px solid var(--border);
215 background: rgba(255,255,255,0.025);
216 color: var(--text);
217 }
218 .envs-banner.is-ok {
219 border-color: rgba(52,211,153,0.40);
220 background: rgba(52,211,153,0.08);
221 color: #bbf7d0;
222 }
223 .envs-banner.is-error {
224 border-color: rgba(248,113,113,0.40);
225 background: rgba(248,113,113,0.08);
226 color: #fecaca;
227 }
228
229 .envs-col-title {
230 margin: 0 0 var(--space-2);
231 font-family: var(--font-mono);
232 font-size: 11px;
233 text-transform: uppercase;
234 letter-spacing: 0.14em;
235 font-weight: 600;
236 color: var(--text-muted);
237 }
238
239 .envs-list {
240 display: flex;
241 flex-direction: column;
242 gap: var(--space-3);
243 margin-bottom: var(--space-5);
244 }
245
246 /* ─── environment item card ─── */
247 .envs-card {
248 position: relative;
249 padding: var(--space-4) var(--space-4);
250 background: var(--bg-elevated);
251 border: 1px solid var(--border);
252 border-radius: 14px;
253 overflow: hidden;
254 transition: transform 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
255 }
256 .envs-card::before {
257 content: '';
258 position: absolute;
259 top: 0; left: 14px; right: 14px;
260 height: 1px;
261 background: linear-gradient(90deg, transparent 0%, rgba(140,109,255,0.45) 30%, rgba(54,197,214,0.45) 70%, transparent 100%);
262 opacity: 0;
263 transition: opacity 160ms ease;
264 }
265 .envs-card:hover {
266 transform: translateY(-1px);
267 border-color: rgba(140,109,255,0.32);
268 box-shadow: 0 8px 22px -10px rgba(0,0,0,0.40);
269 }
270 .envs-card:hover::before { opacity: 1; }
271
272 .envs-card-head {
273 display: flex;
274 align-items: center;
275 justify-content: space-between;
276 gap: var(--space-3);
277 flex-wrap: wrap;
278 margin-bottom: var(--space-3);
279 }
280 .envs-card-titles { flex: 1; min-width: 200px; }
281 .envs-card-title {
282 margin: 0;
283 font-family: var(--font-display);
284 font-size: 17px;
285 font-weight: 700;
286 letter-spacing: -0.018em;
287 color: var(--text-strong);
288 }
289 .envs-card-url {
290 margin-top: 4px;
291 font-family: var(--font-mono);
292 font-size: 12px;
293 color: var(--text-muted);
294 overflow: hidden;
295 text-overflow: ellipsis;
296 white-space: nowrap;
297 }
298 .envs-card-url a { color: var(--text-muted); text-decoration: none; }
299 .envs-card-url a:hover { color: var(--accent); text-decoration: underline; }
300 .envs-card-meta {
301 margin-top: 8px;
302 display: flex;
303 flex-wrap: wrap;
304 gap: 6px;
305 font-size: 11.5px;
306 color: var(--text-muted);
307 font-variant-numeric: tabular-nums;
308 }
309
310 /* ─── pills ─── */
311 .envs-pill {
312 display: inline-flex;
313 align-items: center;
314 gap: 5px;
315 padding: 2px 9px;
316 border-radius: 9999px;
317 font-size: 10.5px;
318 font-weight: 600;
319 letter-spacing: 0.05em;
320 text-transform: uppercase;
321 font-family: var(--font-mono);
322 background: rgba(255,255,255,0.04);
323 color: var(--text-muted);
324 box-shadow: inset 0 0 0 1px var(--border);
325 }
326 .envs-pill.is-active {
327 background: rgba(52,211,153,0.10);
328 color: #6ee7b7;
329 box-shadow: inset 0 0 0 1px rgba(52,211,153,0.30);
330 }
331 .envs-pill.is-protected {
332 background: rgba(140,109,255,0.12);
333 color: #c4b5fd;
334 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.32);
335 }
336 .envs-pill.is-warn {
337 background: rgba(251,191,36,0.10);
338 color: #fde68a;
339 box-shadow: inset 0 0 0 1px rgba(251,191,36,0.30);
340 }
341 .envs-pill-dot {
342 width: 6px; height: 6px;
343 border-radius: 9999px;
344 background: currentColor;
345 }
346
347 .envs-pillrow {
348 display: flex;
349 gap: 6px;
350 align-items: center;
351 flex-wrap: wrap;
352 }
353
354 /* ─── form rows inside an environment card ─── */
355 .envs-fields {
356 display: grid;
357 grid-template-columns: repeat(2, minmax(0, 1fr));
358 gap: var(--space-3);
359 }
360 @media (max-width: 720px) {
361 .envs-fields { grid-template-columns: 1fr; }
362 }
363 .envs-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
364 .envs-field.is-wide { grid-column: 1 / -1; }
365 .envs-field label {
366 font-family: var(--font-mono);
367 font-size: 11.5px;
368 font-weight: 600;
369 letter-spacing: 0.01em;
370 color: var(--text-strong);
371 }
372 .envs-field .hint {
373 font-size: 11px;
374 color: var(--text-muted);
375 line-height: 1.4;
376 }
377 .envs-input, .envs-textarea {
378 width: 100%;
379 padding: 8px 11px;
380 font-size: 13px;
381 color: var(--text);
382 background: var(--bg);
383 border: 1px solid var(--border-strong, var(--border));
384 border-radius: 8px;
385 outline: none;
386 font-family: var(--font-mono);
387 transition: border-color 120ms ease, box-shadow 120ms ease;
388 box-sizing: border-box;
389 }
390 .envs-input:focus, .envs-textarea:focus {
391 border-color: rgba(140,109,255,0.50);
392 box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
393 }
394 .envs-check {
395 display: inline-flex;
396 align-items: center;
397 gap: 8px;
398 font-size: 13px;
399 color: var(--text);
400 cursor: pointer;
401 user-select: none;
402 }
403 .envs-check input { margin: 0; }
404
405 .envs-card-foot {
406 margin-top: var(--space-3);
407 padding-top: var(--space-3);
408 border-top: 1px solid var(--border);
409 display: flex;
410 justify-content: space-between;
411 align-items: center;
412 gap: var(--space-2);
413 flex-wrap: wrap;
414 }
415 .envs-card-foot-left {
416 font-size: 11.5px;
417 color: var(--text-muted);
418 font-variant-numeric: tabular-nums;
419 }
420
421 /* ─── ghost buttons (page-local) ─── */
422 .envs-btn {
423 display: inline-flex;
424 align-items: center;
425 justify-content: center;
426 padding: 6px 12px;
427 font-size: 12.5px;
428 font-weight: 600;
429 line-height: 1;
430 color: var(--text);
431 background: transparent;
432 border: 1px solid var(--border-strong, var(--border));
433 border-radius: 8px;
434 cursor: pointer;
435 text-decoration: none;
436 transition: background 120ms ease, border-color 120ms ease, color 120ms ease, transform 120ms ease;
437 font-family: inherit;
438 }
439 .envs-btn:hover {
440 background: rgba(140,109,255,0.07);
441 border-color: rgba(140,109,255,0.45);
442 color: var(--text-strong);
443 text-decoration: none;
444 }
445 .envs-btn.is-primary {
446 background: linear-gradient(135deg, #8c6dff 0%, #36c5d6 100%);
447 color: #fff;
448 border-color: transparent;
449 box-shadow: 0 4px 12px -4px rgba(140,109,255,0.45), inset 0 1px 0 rgba(255,255,255,0.14);
450 }
451 .envs-btn.is-primary:hover { transform: translateY(-1px); color: #fff; }
452 .envs-btn.is-danger {
453 color: #fecaca;
454 border-color: rgba(248,113,113,0.35);
455 }
456 .envs-btn.is-danger:hover {
457 background: rgba(248,113,113,0.10);
458 border-color: rgba(248,113,113,0.55);
459 color: #fecaca;
460 }
461
462 /* ─── empty state — dashed card with orb ─── */
463 .envs-empty {
464 position: relative;
465 padding: var(--space-6) var(--space-5);
466 background: var(--bg-elevated);
467 border: 1px dashed var(--border-strong, var(--border));
468 border-radius: 14px;
469 text-align: center;
470 overflow: hidden;
471 margin-bottom: var(--space-5);
472 }
473 .envs-empty-orb {
474 position: absolute;
475 inset: auto auto -40% 50%;
476 transform: translateX(-50%);
477 width: 320px; height: 320px;
478 background: radial-gradient(circle, rgba(140,109,255,0.18), rgba(54,197,214,0.08) 45%, transparent 70%);
479 filter: blur(70px);
480 opacity: 0.7;
481 pointer-events: none;
482 }
483 .envs-empty-inner { position: relative; z-index: 1; max-width: 460px; margin: 0 auto; }
484 .envs-empty-icon {
485 width: 44px; height: 44px;
486 margin: 0 auto var(--space-3);
487 border-radius: 14px;
488 background: linear-gradient(135deg, rgba(140,109,255,0.18), rgba(54,197,214,0.14));
489 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.32);
490 display: flex; align-items: center; justify-content: center;
491 color: #b69dff;
492 }
493 .envs-empty-title {
494 font-family: var(--font-display);
495 font-size: 16px;
496 font-weight: 700;
497 color: var(--text-strong);
498 margin: 0 0 6px;
499 letter-spacing: -0.01em;
500 }
501 .envs-empty-body {
502 font-size: 13.5px;
503 color: var(--text-muted);
504 line-height: 1.5;
505 margin: 0 0 var(--space-3);
506 }
507
508 /* ─── create form (lives below the list) ─── */
509 .envs-create {
510 position: relative;
511 padding: var(--space-4) var(--space-4);
512 background: var(--bg-elevated);
513 border: 1px solid var(--border);
514 border-radius: 14px;
515 overflow: hidden;
516 }
517 .envs-create::before {
518 content: '';
519 position: absolute;
520 top: 0; left: 14px; right: 14px;
521 height: 1px;
522 background: linear-gradient(90deg, transparent 0%, rgba(140,109,255,0.40) 30%, rgba(54,197,214,0.40) 70%, transparent 100%);
523 opacity: 0.7;
524 }
525 .envs-create-title {
526 margin: 0 0 4px;
527 font-family: var(--font-display);
528 font-size: 16px;
529 font-weight: 700;
530 letter-spacing: -0.014em;
531 color: var(--text-strong);
532 }
533 .envs-create-sub {
534 margin: 0 0 var(--space-3);
535 font-size: 12.5px;
536 color: var(--text-muted);
537 }
538`;
539
109540// ---------------------------------------------------------------------------
110541// GET /:owner/:repo/settings/environments
111542// ---------------------------------------------------------------------------
144575 currentUser={user.username}
145576 />
146577 <RepoNav owner={owner} repo={repo} active="code" />
147 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px">
148 <h3>Environments</h3>
149 <a href={`/${owner}/${repo}/deployments`} class="btn btn-sm">
150 Back to deployments
151 </a>
152 </div>
153 {success && <div class="auth-success">{decodeURIComponent(success)}</div>}
154 {err && <div class="auth-error">{decodeURIComponent(err)}</div>}
155578
156 <p style="font-size: 13px; color: var(--text-muted); margin-bottom: 16px">
157 Require human approval before a deploy to this environment runs.
158 Branch patterns restrict which refs may target the environment.
159 </p>
579 <div class="envs-wrap">
580 <section class="envs-head">
581 <div class="envs-head-orb" aria-hidden="true" />
582 <div class="envs-head-inner">
583 <div class="envs-head-text">
584 <div class="envs-eyebrow">
585 <span class="envs-eyebrow-dot" aria-hidden="true" />
586 Deploy environments · {owner}/{repo}
587 </div>
588 <h2 class="envs-title">
589 <span class="envs-title-grad">Environments.</span>
590 </h2>
591 <p class="envs-sub">
592 Gate every deploy with reviewers, wait timers, and branch
593 allowlists — the same way GitHub Environments protects
594 production rollouts.
595 </p>
596 </div>
597 <div class="envs-head-actions">
598 <a
599 href={`/${owner}/${repo}/deployments`}
600 class="envs-btn"
601 >
602 Back to deployments
603 </a>
604 </div>
605 </div>
606 </section>
607
608 {success && (
609 <div class="envs-banner is-ok">{decodeURIComponent(success)}</div>
610 )}
611 {err && (
612 <div class="envs-banner is-error">{decodeURIComponent(err)}</div>
613 )}
614
615 <h4 class="envs-col-title">
616 {envs.length === 0
617 ? "No environments yet"
618 : `${envs.length} environment${envs.length === 1 ? "" : "s"}`}
619 </h4>
160620
161 <div class="panel" style="margin-bottom: 24px">
162621 {envs.length === 0 ? (
163 <div class="panel-empty">No environments yet.</div>
622 <div class="envs-empty">
623 <div class="envs-empty-orb" aria-hidden="true" />
624 <div class="envs-empty-inner">
625 <div class="envs-empty-icon" aria-hidden="true">
626 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
627 <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
628 <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
629 <line x1="12" y1="22.08" x2="12" y2="12" />
630 </svg>
631 </div>
632 <h3 class="envs-empty-title">Add your first environment</h3>
633 <p class="envs-empty-body">
634 Create a named environment like <strong>production</strong> or{" "}
635 <strong>staging</strong>, then require approval, wait timers,
636 or branch allowlists before any deploy may target it.
637 </p>
638 </div>
639 </div>
164640 ) : (
165 envs.map((env) => {
166 const reviewers = envUsernames[env.id] || [];
167 const branches = allowedBranchesOf(env);
168 return (
169 <form
170 method="post"
171 action={`/${owner}/${repo}/settings/environments/${env.id}`}
172 class="panel-item"
173 style="flex-direction: column; align-items: stretch; gap: 8px"
174 >
175 <div
176 style="display: flex; justify-content: space-between; align-items: center"
641 <div class="envs-list">
642 {envs.map((env) => {
643 const reviewers = envUsernames[env.id] || [];
644 const branches = allowedBranchesOf(env);
645 const protections = envProtectionCount(env);
646 const updatedAt = env.updatedAt
647 ? new Date(env.updatedAt)
648 : null;
649 const updatedLabel = updatedAt
650 ? updatedAt.toLocaleString()
651 : "—";
652 const envUrl = `/${owner}/${repo}/deployments?environment=${encodeURIComponent(env.name)}`;
653 return (
654 <form
655 method="post"
656 action={`/${owner}/${repo}/settings/environments/${env.id}`}
657 class="envs-card"
177658 >
178 <strong style="font-size: 15px">{env.name}</strong>
179 <div style="display: flex; gap: 6px">
180 <button type="submit" class="btn btn-sm btn-primary">
181 Save
182 </button>
659 <div class="envs-card-head">
660 <div class="envs-card-titles">
661 <h3 class="envs-card-title">{env.name}</h3>
662 <div class="envs-card-url">
663 <a href={envUrl}>{envUrl}</a>
664 </div>
665 <div class="envs-card-meta">
666 <span title={updatedAt ? updatedAt.toISOString() : ""}>
667 last deploy target · {updatedLabel}
668 </span>
669 </div>
670 </div>
671 <div class="envs-pillrow">
672 <span class="envs-pill is-active">
673 <span class="envs-pill-dot" aria-hidden="true" />
674 Active
675 </span>
676 <span
677 class={protections > 0 ? "envs-pill is-protected" : "envs-pill"}
678 title={`${protections} protection rule${protections === 1 ? "" : "s"}`}
679 >
680 <span class="envs-pill-dot" aria-hidden="true" />
681 {protections} rule{protections === 1 ? "" : "s"}
682 </span>
683 {env.requireApproval && (
684 <span class="envs-pill is-warn">
685 <span class="envs-pill-dot" aria-hidden="true" />
686 Approval
687 </span>
688 )}
689 </div>
183690 </div>
184 </div>
185 <div class="form-group" style="margin: 0">
186 <label style="display: flex; align-items: center; gap: 6px">
187 <input
188 type="checkbox"
189 name="requireApproval"
190 value="1"
191 checked={env.requireApproval}
192 />
193 Require approval before deploy
194 </label>
195 </div>
196 <div class="form-group" style="margin: 0">
197 <label>Reviewers (comma-separated usernames)</label>
198 <input
199 type="text"
200 name="reviewers"
201 value={reviewers.join(", ")}
202 placeholder="alice, bob"
203 aria-label="Reviewers"
204 />
205 </div>
206 <div class="form-group" style="margin: 0">
207 <label>Wait timer (minutes)</label>
208 <input
209 type="number"
210 name="waitTimerMinutes"
211 min="0"
212 max="1440"
213 value={String(env.waitTimerMinutes)}
214 aria-label="Wait timer in minutes"
215 style="width: 120px"
216 />
217 </div>
218 <div class="form-group" style="margin: 0">
219 <label>Allowed branches (comma-separated glob patterns)</label>
220 <input
221 type="text"
222 name="allowedBranches"
223 value={branches.join(", ")}
224 placeholder="main, release/*"
225 aria-label="Allowed branches"
226 />
227 </div>
228 <div style="display: flex; justify-content: flex-end">
229 <button
230 type="submit"
231 formaction={`/${owner}/${repo}/settings/environments/${env.id}/delete`}
232 class="btn btn-sm btn-danger"
233 onclick="return confirm('Delete this environment?')"
234 >
235 Delete
236 </button>
237 </div>
238 </form>
239 );
240 })
691
692 <div class="envs-fields">
693 <div class="envs-field is-wide">
694 <label class="envs-check">
695 <input
696 type="checkbox"
697 name="requireApproval"
698 value="1"
699 checked={env.requireApproval}
700 />
701 Require approval before deploy
702 </label>
703 </div>
704 <div class="envs-field is-wide">
705 <label for={`env-rev-${env.id}`}>Reviewers</label>
706 <input
707 id={`env-rev-${env.id}`}
708 type="text"
709 name="reviewers"
710 value={reviewers.join(", ")}
711 placeholder="alice, bob"
712 aria-label="Reviewers"
713 class="envs-input"
714 />
715 <span class="hint">Comma-separated usernames.</span>
716 </div>
717 <div class="envs-field">
718 <label for={`env-wait-${env.id}`}>Wait timer (minutes)</label>
719 <input
720 id={`env-wait-${env.id}`}
721 type="number"
722 name="waitTimerMinutes"
723 min="0"
724 max="1440"
725 value={String(env.waitTimerMinutes)}
726 aria-label="Wait timer in minutes"
727 class="envs-input"
728 />
729 <span class="hint">0 disables the timer. Max 1440 (24h).</span>
730 </div>
731 <div class="envs-field">
732 <label for={`env-br-${env.id}`}>Allowed branches</label>
733 <input
734 id={`env-br-${env.id}`}
735 type="text"
736 name="allowedBranches"
737 value={branches.join(", ")}
738 placeholder="main, release/*"
739 aria-label="Allowed branches"
740 class="envs-input"
741 />
742 <span class="hint">Comma-separated glob patterns.</span>
743 </div>
744 </div>
745
746 <div class="envs-card-foot">
747 <div class="envs-card-foot-left">
748 {reviewers.length > 0
749 ? `${reviewers.length} reviewer${reviewers.length === 1 ? "" : "s"}`
750 : "No reviewers"}
751 {" · "}
752 {branches.length > 0
753 ? `${branches.length} branch pattern${branches.length === 1 ? "" : "s"}`
754 : "any branch"}
755 </div>
756 <div style="display:flex;gap:6px;flex-wrap:wrap">
757 <a href={envUrl} class="envs-btn">View runs</a>
758 <button
759 type="submit"
760 formaction={`/${owner}/${repo}/settings/environments/${env.id}/delete`}
761 class="envs-btn is-danger"
762 onclick="return confirm('Delete this environment?')"
763 >
764 Delete
765 </button>
766 <button type="submit" class="envs-btn is-primary">
767 Save
768 </button>
769 </div>
770 </div>
771 </form>
772 );
773 })}
774 </div>
241775 )}
776
777 <form
778 method="post"
779 action={`/${owner}/${repo}/settings/environments`}
780 class="envs-create"
781 >
782 <h3 class="envs-create-title">New environment</h3>
783 <p class="envs-create-sub">
784 Pick a short, lowercase name like <strong>production</strong>,{" "}
785 <strong>staging</strong>, or <strong>preview</strong>.
786 </p>
787 <div class="envs-fields">
788 <div class="envs-field is-wide">
789 <label for="envs-new-name">Name</label>
790 <input
791 id="envs-new-name"
792 type="text"
793 name="name"
794 required
795 placeholder="production"
796 aria-label="Environment name"
797 class="envs-input"
798 />
799 </div>
800 <div class="envs-field is-wide">
801 <label class="envs-check">
802 <input
803 type="checkbox"
804 name="requireApproval"
805 value="1"
806 checked
807 />
808 Require approval
809 </label>
810 </div>
811 <div class="envs-field is-wide">
812 <label for="envs-new-reviewers">Reviewers</label>
813 <input
814 id="envs-new-reviewers"
815 type="text"
816 name="reviewers"
817 placeholder="alice, bob"
818 aria-label="Reviewers"
819 class="envs-input"
820 />
821 <span class="hint">Comma-separated usernames.</span>
822 </div>
823 <div class="envs-field">
824 <label for="envs-new-wait">Wait timer (minutes)</label>
825 <input
826 id="envs-new-wait"
827 type="number"
828 name="waitTimerMinutes"
829 min="0"
830 max="1440"
831 value="0"
832 aria-label="Wait timer in minutes"
833 class="envs-input"
834 />
835 </div>
836 <div class="envs-field">
837 <label for="envs-new-branches">Allowed branches</label>
838 <input
839 id="envs-new-branches"
840 type="text"
841 name="allowedBranches"
842 placeholder="main, release/*"
843 aria-label="Allowed branches"
844 class="envs-input"
845 />
846 <span class="hint">Comma-separated glob patterns.</span>
847 </div>
848 </div>
849 <div style="display:flex;justify-content:flex-end;margin-top:var(--space-3)">
850 <button type="submit" class="envs-btn is-primary">
851 Create environment
852 </button>
853 </div>
854 </form>
242855 </div>
243856
244 <h3 style="margin-top: 24px; margin-bottom: 12px">New environment</h3>
245 <form
246 method="post"
247 action={`/${owner}/${repo}/settings/environments`}
248 class="panel"
249 style="padding: 16px"
250 >
251 <div class="form-group">
252 <label>Name</label>
253 <input
254 type="text"
255 name="name"
256 required
257 placeholder="production"
258 aria-label="Environment name"
259 />
260 </div>
261 <div class="form-group">
262 <label style="display: flex; align-items: center; gap: 6px">
263 <input
264 type="checkbox"
265 name="requireApproval"
266 value="1"
267 checked
268 />
269 Require approval
270 </label>
271 </div>
272 <div class="form-group">
273 <label>Reviewers (comma-separated usernames)</label>
274 <input type="text" name="reviewers" placeholder="alice, bob" aria-label="Reviewers" />
275 </div>
276 <div class="form-group">
277 <label>Wait timer (minutes)</label>
278 <input
279 type="number"
280 name="waitTimerMinutes"
281 min="0"
282 max="1440"
283 value="0"
284 aria-label="Wait timer in minutes"
285 style="width: 120px"
286 />
287 </div>
288 <div class="form-group">
289 <label>Allowed branches (comma-separated glob patterns)</label>
290 <input
291 type="text"
292 name="allowedBranches"
293 placeholder="main, release/*"
294 aria-label="Allowed branches"
295 />
296 </div>
297 <button type="submit" class="btn btn-primary">
298 Create environment
299 </button>
300 </form>
857 <style dangerouslySetInnerHTML={{ __html: envsStyles }} />
301858 </Layout>
302859 );
303860});
304861