ReviewOS

also looking at this

stacks/postline

UI polish, real queue + scheduling, Bluesky threads, live analytics, blog target

#8
Merged glennmichael123 wants to merge feat/ui-polish into main
54 files +2036 -589

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

resources/assets/scripts/postline-toast.tsmodified+2-2
Changes to resources/assets/scripts/postline-toast.ts
@@ -36,7 +36,7 @@ function host(): HTMLElement {
3636 if (!element) {
3737 element = document.createElement('div')
3838 element.id = 'postline-toast-host'
39 element.className = 'pointer-events-none fixed bottom-6 right-6 z-[100] flex w-[calc(100%-2rem)] max-w-[22rem] flex-col-reverse gap-2.5 sm:right-6'
39 element.className = 'flex bottom-6 fixed right-6 z-[100] sm:right-6 flex-col-reverse gap-2.5 max-w-[22rem] w-[calc(100%-2rem)] pointer-events-none'
4040 element.setAttribute('aria-live', 'polite')
4141 element.setAttribute('aria-relevant', 'additions')
4242 document.body.append(element)
@@ -95,7 +95,7 @@ export function showPostlineToast(options: PostlineToastOptions): void { formatting only
9595
9696 const bodyMarkup = options.url
9797 ? `<p class="postline-toast__message">${escapeHtml(options.message)}</p>
98 <a class="postline-toast__url" href="${escapeHtml(options.url)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(options.url)}">${escapeHtml(formatUrlLabel(options.url))}</a>`
98 <a class="postline-toast__url" href="${escapeHtml(options.url)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(options.url)}">${escapeHtml(formatUrlLabel(options.url))}</a>`
9999 : `<p class="postline-toast__message">${escapeHtml(options.message)}</p>`
100100
101101 toast.innerHTML = `
storage/framework/core/actions/src/blog.tsmodifiedformatting only+6-6
Changes to storage/framework/core/actions/src/blog.ts
@@ -276,9 +276,9 @@ async function postHtml(bp: BunPress, slug: string, origin: string): Promise<str
276276
277277 const authorCard = fm.authorBio
278278 ? `<aside class="blog-author-card">
279 <div class="avatar">${initial}</div>
280 <div><span class="name">${escapeHtml(fm.author || 'The Stacks Team')}</span><p class="bio">${escapeHtml(fm.authorBio)}</p></div>
281 </aside>`
279 <div class="avatar">${initial}</div>
280 <div><span class="name">${escapeHtml(fm.author || 'The Stacks Team')}</span><p class="bio">${escapeHtml(fm.authorBio)}</p></div>
281 </aside>`
282282 : ''
283283
284284 const postUrl = `${origin}/blog/${slug}`
@@ -292,9 +292,9 @@ async function postHtml(bp: BunPress, slug: string, origin: string): Promise<str
292292 const others = listPosts().filter(p => p.slug !== slug).slice(0, 3)
293293 const more = others.length
294294 ? `<nav class="blog-more">
295 <h2>More from the blog</h2>
296 <ul>${others.map(p => `<li><a href="/blog/${escapeHtml(p.slug)}">${escapeHtml(p.fm.title || p.slug)}</a></li>`).join('')}</ul>
297 </nav>`
295 <h2>More from the blog</h2>
296 <ul>${others.map(p => `<li><a href="/blog/${escapeHtml(p.slug)}">${escapeHtml(p.fm.title || p.slug)}</a></li>`).join('')}</ul>
297 </nav>`
298298 : ''
299299
300300 return bp.wrapInLayout(blogChrome() + header + html + authorCard + share + more + blogFooter(), blogConfig(bp, fm), `/blog/${slug}`, 'page')
storage/framework/core/database/src/fk-audit.tsmodifiedformatting only+2-2
Changes to storage/framework/core/database/src/fk-audit.ts
@@ -332,10 +332,10 @@ async function getPostgresLiveFKs(db: any): Promise<LiveFK[]> {
332332 FROM information_schema.referential_constraints rc
333333 JOIN information_schema.key_column_usage kcu
334334 ON kcu.constraint_name = rc.constraint_name
335 AND kcu.constraint_schema = rc.constraint_schema
335 AND kcu.constraint_schema = rc.constraint_schema
336336 JOIN information_schema.constraint_column_usage ccu
337337 ON ccu.constraint_name = rc.constraint_name
338 AND ccu.constraint_schema = rc.constraint_schema
338 AND ccu.constraint_schema = rc.constraint_schema
339339 WHERE rc.constraint_schema = 'public'
340340 `)
341341 return (Array.isArray(rows) ? rows : []).map((row: any) => ({
storage/framework/core/email/README.mdmodified+2-2
Changes to storage/framework/core/email/README.md
@@ -111,7 +111,7 @@ EMAILJS*HOST=example.com
111111EMAILJS*USERNAME=username
112112EMAILJS*PASSWORD=password
113113EMAILJS*PORT=25
114EMAILJS_SECURE=true
114EMAILJS*SECURE=true
115115```
116116
117117Learn more in the docs.
@@ -163,7 +163,7 @@ or ignore `<style>` blocks, so styles have to ride on each element via
163163`style="…"`. Stacks ships a pass-through inliner that runs after
164164`renderEmail()` and before the result hands back:
165165
166- **On by default in production** (`APP_ENV` or `NODE_ENV` is
166- **On by default in production** (`APP*ENV` or `NODE_ENV` is
167167 `production`). Off in dev so previews show the un-mutated stx
168168 output.
169169- **Per-call opt-out** via `template(name, { inline: false })`.
storage/framework/core/email/src/webhook-signatures.tsmodified+1-1
Changes to storage/framework/core/email/src/webhook-signatures.ts
@@ -20,7 +20,7 @@
2020 * - SendGrid: ECDSA over `timestamp + body` with the public key
2121 * from their dashboard. P-256 curve, DER signature.
2222 *
23 * Each function takes the bits it needs (request body, headers,
23 * Each function takes the bits it needs (request body, _headers,
2424 * configured secret) and returns a discriminated `{ ok, reason? }`
2525 * — the route handler maps `ok: false` to a 401/403 without
2626 * touching the rest of the pipeline.
storage/framework/defaults/resources/components/Pagination.stxmodified+5-5
Changes to storage/framework/defaults/resources/components/Pagination.stx
@@ -74,10 +74,10 @@ const inactiveLink = `${baseLink} border-gray-300 dark:border-neutral-600 text-g
7474const disabledLink = `${baseLink} border-gray-200 dark:border-neutral-700 text-gray-400 dark:text-neutral-500 cursor-not-allowed`
7575</script>
7676
77<nav aria-label="Pagination" class="flex items-center justify-between gap-2 py-3 select-none">
77<nav aria-label="Pagination" class="flex gap-2 items-center justify-between py-3 select-none">
7878 @if(isFull)
7979 {{-- Mobile: prev / next only (page numbers hidden) --}}
80 <div class="flex flex-1 justify-between sm:hidden">
80 <div class="flex sm:hidden flex-1 justify-between">
8181 @if(paginator.prev_page_url)
8282 <a href="" class=" rounded-md">Previous</a>
8383 @else
@@ -92,7 +92,7 @@ const disabledLink = `${baseLink} border-gray-200 dark:border-neutral-700 text-g
9292
9393 {{-- Desktop: prev / page numbers / next + "Showing X to Y of Z" --}}
9494 <div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
95 <p class="text-sm text-gray-700 dark:text-neutral-300">
95 <p class="text-gray-700 text-sm dark:text-neutral-300">
9696 @if(paginator.from && paginator.to)
9797 Showing <span class="font-medium">{{ paginator.from }}</span>
9898 to <span class="font-medium">{{ paginator.to }}</span>
@@ -102,7 +102,7 @@ const disabledLink = `${baseLink} border-gray-200 dark:border-neutral-700 text-g
102102 @endif
103103 </p>
104104
105 <div class="isolate inline-flex -space-x-px rounded-md shadow-sm">
105 <div class="inline-flex -space-x-px rounded-md shadow-sm isolate">
106106 @if(paginator.prev_page_url)
107107 <a href="" rel="prev" class=" rounded-l-md" aria-label="Previous page">←</a>
108108 @else
@@ -132,7 +132,7 @@ const disabledLink = `${baseLink} border-gray-200 dark:border-neutral-700 text-g formatting only
132132
133133 @if(isSimple || isCursor)
134134 {{-- Simple / cursor: just prev / next. No page numbers because we
135 don't know last_page (simple) or it doesn't apply (cursor). --}}
135 don't know last_page (simple) or it doesn't apply (cursor). --}}
136136 <div class="flex flex-1 justify-between">
137137 @if(paginator.prev_page_url)
138138 <a href="" rel="prev" class=" rounded-md">← Previous</a>
storage/framework/defaults/views/dashboard/AUDIT.mdmodified+3-3
Changes to storage/framework/defaults/views/dashboard/AUDIT.md
@@ -36,8 +36,8 @@ reassign `const` vars (always threw in strict-mode JS) and called
3636`useRouter()` (a Nuxt-ism, no-op in stx).
3737
3838Remaining work for #1838: the 78 in-page `window.`/`document.` lines
39(mostly pre-existing vanilla event wiring under `content/*` and
40`commerce/*`) and deliverable #4 (component lift to
39(mostly pre-existing vanilla event wiring under `content/_` and
40`commerce/_`) and deliverable #4 (component lift to
4141`@stacksjs/components`) are the open targets.
4242
4343Three new pages landed since the original audit (kanban index, kanban
@@ -47,7 +47,7 @@ pages outpaced the new arrivals.
4747
4848The remaining 78 DOM-access lines cluster in two patterns: pre-existing
4949`document.getElementById(...)` event wiring in vanilla `<script>` blocks
50(largely under `content/*`, `commerce/*`), and the layout's own
50(largely under `content/_`, `commerce/_`), and the layout's own
5151import-map / role-filter installer where direct DOM is appropriate. The
5252in-page DOM references are the next sweep target.
5353
storage/framework/defaults/views/dashboard/[...all].stxmodified+5-5
Changes to storage/framework/defaults/views/dashboard/[...all].stx
@@ -2,11 +2,11 @@
22
33@section('content')
44{{-- Catch-all 404. Inlined instead of using <PageLayout> because the
5 filename's route-param `...all` leaks into component prop scopes
6 and stx's @if evaluator parses it as a rest parameter, breaking
7 every @if in components that consume it. --}}
8<div class="flex items-center justify-center min-h-[80vh] px-6 pt-6 pb-12">
9 <div class="mx-auto w-full max-w-7xl space-y-6">
5 filename's route-param `...all` leaks into component prop scopes
6 and stx's @if evaluator parses it as a rest parameter, breaking
7 every @if in components that consume it. --}}
8<div class="flex items-center justify-center pb-12 pt-6 px-6 min-h-[80vh]">
9 <div class="mx-auto space-y-6 max-w-7xl w-full">
1010 <div class="text-center">
1111 <div class="flex justify-center">
1212 <div class="mb-6 h-24 w-24 text-gray-400 dark:text-neutral-500 i-hugeicons-alert-circle"></div>
storage/framework/defaults/views/dashboard/ci/index.stxmodified+35-35
Changes to storage/framework/defaults/views/dashboard/ci/index.stx
@@ -181,10 +181,10 @@
181181
182182<div class="mx-auto px-6 py-8 max-w-7xl">
183183 <!-- Role-gated wrapper. Collapses to the "requires dev role" message
184 when an authenticated client-role user lands here. Unauthenticated
185 viewers (dev dashboard on localhost) see the surface as if they
186 were a dev. -->
187 <div :if="!canView" class="rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 px-5 py-8 text-center">
184 when an authenticated client-role user lands here. Unauthenticated
185 viewers (dev dashboard on localhost) see the surface as if they
186 were a dev. -->
187 <div :if="!canView" class="px-5 py-8 text-center bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
188188 <p class="text-sm text-zinc-600 dark:text-zinc-400">This surface requires the dev role.</p>
189189 </div>
190190
@@ -195,7 +195,7 @@
195195 </header>
196196
197197 <!-- Disabled state — the surface is off in config/dashboard.ts -->
198 <div :if="disabled" class="rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 px-5 py-8 text-center">
198 <div :if="disabled" class="px-5 py-8 text-center bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
199199 <p class="text-sm text-zinc-600 dark:text-zinc-400">CI tracking is disabled.</p>
200200 <p class="mt-1 text-xs text-zinc-500">Enable it in <code>config/dashboard.ts</code> by setting <code>ci.enabled = true</code> and listing one or more orgs.</p>
201201 </div>
@@ -211,9 +211,9 @@
211211 </div>
212212
213213 <!-- Error state -->
214 <div :if="error && !loading && !disabled" class="rounded-lg border border-red-200 dark:border-red-900/40 bg-red-50 dark:bg-red-950/30 px-5 py-4">
215 <p class="text-sm font-medium text-red-700 dark:text-red-300">Snapshot failed</p>
216 <p class="mt-1 text-xs text-red-600 dark:text-red-400" :text="error"></p>
214 <div :if="error && !loading && !disabled" class="px-5 py-4 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900/40">
215 <p class="font-medium text-red-700 text-sm dark:text-red-300">Snapshot failed</p>
216 <p class="mt-1 text-red-600 text-xs dark:text-red-400" :text="error"></p>
217217 <p class="mt-2 text-xs text-zinc-500">Most often: <code>GITHUB_TOKEN</code> is missing or lacks <code>public_repo</code> scope.</p>
218218 </div>
219219
@@ -246,11 +246,11 @@
246246 <span class="text-zinc-500 dark:text-zinc-400" :text="runnersText"></span>
247247 </div>
248248 <!-- Sparkline showing recent queue depth for the active org —
249 appears only when alerts are on (history collection is gated
250 on `ci.alerts.enabled`). Drawn inline as a single SVG path
251 so we don't pull a chart library for 20 datapoints. -->
252 <div :if="hasHistory()" class="mb-5 flex items-center gap-2">
253 <span class="text-[10px] uppercase tracking-wide text-zinc-400">Queue depth (last hour)</span>
249 appears only when alerts are on (history collection is gated
250 on `ci.alerts.enabled`). Drawn inline as a single SVG path
251 so we don't pull a chart library for 20 datapoints. -->
252 <div :if="hasHistory()" class="flex gap-2 items-center mb-5">
253 <span class="text-[10px] text-zinc-400 tracking-wide uppercase">Queue depth (last hour)</span>
254254 <svg width="200" height="24" viewBox="0 0 200 24" class="text-violet-400">
255255 <path :d="sparklinePath()" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
256256 </svg>
@@ -281,8 +281,8 @@ formatting only
281281 <div class="flex-1 min-w-0">
282282 <div class="flex gap-2 items-center">
283283 <!-- Inner links stop propagation so they remain "open repo
284 in a new tab" rather than triggering the drilldown
285 drawer on the parent card. -->
284 in a new tab" rather than triggering the drilldown
285 drawer on the parent card. -->
286286 <a :href="repo.url" target="_blank" @click.stop class="font-medium no-underline text-sm text-zinc-900 dark:text-zinc-100 hover:underline" :text="repo.name"></a>
287287 <span :if="repo.commitSha" class="font-mono text-xs text-zinc-400" :text="repo.commitSha"></span>
288288 <span :if="repo.updatedAt" class="text-xs text-zinc-400" :text="timeAgo(repo.updatedAt)"></span>
@@ -337,25 +337,25 @@
337337
338338 <!-- ────────── Drilldown drawer (#1848) ────────── -->
339339 <!-- Slide-in side panel triggered by a repo-card click. Lists the
340 last 20 workflow runs as a vertical timeline; failing runs
341 expand inline to their failed-jobs list. Closes on backdrop
342 click + Escape. -->
340 last 20 workflow runs as a vertical timeline; failing runs
341 expand inline to their failed-jobs list. Closes on backdrop
342 click + Escape. -->
343343 <div
344344 :if="drilldownTarget()"
345 class="fixed inset-0 z-50 flex justify-end"
345 class="flex fixed inset-0 z-50 justify-end"
346346 @click="ci.closeDrilldown()"
347347 @keydown.escape.window="ci.closeDrilldown()"
348348 >
349349 <div class="absolute inset-0 bg-black/40"></div>
350350 <aside
351351 @click.stop
352 class="relative w-full max-w-2xl h-full bg-white dark:bg-zinc-950 border-l border-zinc-200 dark:border-zinc-800 overflow-y-auto shadow-2xl"
352 class="overflow-y-auto relative h-full max-w-2xl w-full bg-white dark:bg-zinc-950 border-l border-zinc-200 dark:border-zinc-800 shadow-2xl"
353353 >
354354 <!-- Header -->
355 <div class="px-5 py-4 border-b border-zinc-200 dark:border-zinc-800 flex items-start gap-3 sticky top-0 bg-white dark:bg-zinc-950 z-10">
355 <div class="flex sticky top-0 z-10 gap-3 items-start px-5 py-4 bg-white dark:bg-zinc-950 border-b border-zinc-200 dark:border-zinc-800">
356356 <div class="flex-1 min-w-0">
357 <p class="text-xs uppercase tracking-wide text-zinc-500">Run history</p>
358 <h3 class="font-semibold text-lg text-zinc-900 dark:text-zinc-100 truncate" :text="drilldownTarget().fullName"></h3>
357 <p class="text-xs text-zinc-500 tracking-wide uppercase">Run history</p>
358 <h3 class="font-semibold text-lg text-zinc-900 truncate dark:text-zinc-100" :text="drilldownTarget().fullName"></h3>
359359 </div>
360360 <button
361361 @click="ci.closeDrilldown()"
@@ -369,8 +369,8 @@
369369 </div>
370370
371371 <!-- Error -->
372 <div :if="drilldownError()" class="mx-5 mt-4 rounded-lg border border-red-200 dark:border-red-900/40 bg-red-50 dark:bg-red-950/30 px-4 py-3">
373 <p class="text-xs text-red-700 dark:text-red-300" :text="drilldownError()"></p>
372 <div :if="drilldownError()" class="mt-4 mx-5 px-4 py-3 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900/40">
373 <p class="text-red-700 text-xs dark:text-red-300" :text="drilldownError()"></p>
374374 </div>
375375
376376 <!-- Loading -->
@@ -391,17 +391,17 @@
391391 @click="ci.toggleRunJobs(run.id)"
392392 :class="runRowClass(run.conclusion, run.status)"
393393 >
394 <div class="flex items-start justify-between gap-3">
394 <div class="flex gap-3 items-start justify-between">
395395 <div class="flex-1 min-w-0">
396 <div class="flex items-center gap-2">
396 <div class="flex gap-2 items-center">
397397 <span
398398 :class="`px-1.5 py-0.5 text-[10px] font-medium rounded ${runStatusBadge(run.conclusion, run.status).cls}`"
399399 :text="runStatusBadge(run.conclusion, run.status).label"
400400 ></span>
401 <span class="text-sm font-medium text-zinc-800 dark:text-zinc-200 truncate" :text="run.name"></span>
401 <span class="font-medium text-sm text-zinc-800 truncate dark:text-zinc-200" :text="run.name"></span>
402402 </div>
403403 <p :if="run.commitMessage" class="mt-1 text-xs text-zinc-500 truncate" :text="truncate(run.commitMessage, 80)"></p>
404 <div class="mt-1 flex items-center gap-2 text-[10px] text-zinc-400">
404 <div class="flex gap-2 items-center mt-1 text-[10px] text-zinc-400">
405405 <span class="font-mono" :text="run.headShaShort"></span>
406406 <span :if="run.headBranch" :text="run.headBranch"></span>
407407 <span :if="run.commitAuthor">·</span>
@@ -416,27 +416,27 @@
416416 :href="run.url"
417417 target="_blank"
418418 @click.stop
419 class="text-xs text-violet-600 dark:text-violet-400 hover:underline no-underline flex-shrink-0"
419 class="flex-shrink-0 no-underline text-violet-600 text-xs dark:text-violet-400 hover:underline"
420420 >view ↗</a>
421421 </div>
422422 </div>
423423
424424 <!-- Expanded: failed job list -->
425425 <div :if="expandedRunId() === run.id" class="px-3 py-2 bg-zinc-50 dark:bg-zinc-900/50 border-t border-zinc-100 dark:border-zinc-900">
426 <div :if="loadingJobsRunId() === run.id" class="text-xs text-zinc-500 py-2">
426 <div :if="loadingJobsRunId() === run.id" class="py-2 text-xs text-zinc-500">
427427 Loading jobs…
428428 </div>
429 <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length === 0" class="text-xs text-zinc-400 italic py-1">
429 <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length === 0" class="py-1 italic text-xs text-zinc-400">
430430 No job detail.
431431 </div>
432432 <div :if="loadingJobsRunId() !== run.id && failedJobs(run.id).length > 0" class="space-y-1.5">
433 <p class="text-[10px] uppercase tracking-wide text-zinc-500">Failed jobs</p>
433 <p class="text-[10px] text-zinc-500 tracking-wide uppercase">Failed jobs</p>
434434 <a
435435 :for="job in failedJobs(run.id)"
436436 :href="job.url"
437437 target="_blank"
438438 @click.stop
439 class="flex items-center gap-1.5 text-xs no-underline hover:underline text-red-700 dark:text-red-400"
439 class="flex gap-1.5 items-center no-underline text-red-700 text-xs dark:text-red-400 hover:underline"
440440 >
441441 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
442442 <circle cx="12" cy="12" r="10" /><line x1="15" y1="9" x2="9" y2="15" /><line x1="9" y1="9" x2="15" y2="15" />
@@ -445,7 +445,7 @@
445445 <span :if="job.durationMs" class="text-zinc-400" :text="'(' + formatDuration(job.durationMs) + ')'"></span>
446446 </a>
447447 </div>
448 <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length > 0 && failedJobs(run.id).length === 0" class="text-xs text-zinc-500 py-1">
448 <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length > 0 && failedJobs(run.id).length === 0" class="py-1 text-xs text-zinc-500">
449449 All <span :text="jobsForRun(run.id).length"></span> jobs passed.
450450 </div>
451451 </div>
storage/framework/defaults/views/dashboard/content/authors/index.stxmodified+33-33
Changes to storage/framework/defaults/views/dashboard/content/authors/index.stx
@@ -1,42 +1,42 @@
11@extends('layouts/default')
22
33@section('content')
4<div class="min-h-screen bg-neutral-50 p-6 text-neutral-950 dark:bg-neutral-950 dark:text-neutral-100">
5 <div class="mx-auto flex max-w-6xl flex-col gap-6">
6 <header class="flex flex-col gap-4 border-b border-neutral-200 pb-5 dark:border-neutral-800 md:flex-row md:items-end md:justify-between">
4<div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
5 <div class="flex flex-col gap-6 mx-auto max-w-6xl">
6 <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
77 <div>
8 <p class="text-xs font-semibold uppercase tracking-wide text-blue-600 dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 text-2xl font-semibold tracking-tight">Authors</h1>
10 <p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Manage the authors available to posts and pages.</p>
8 <p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 font-semibold text-2xl tracking-tight">Authors</h1>
10 <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Manage the authors available to posts and pages.</p>
1111 </div>
12 <button id="new-author-button" type="button" class="inline-flex items-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
13 <span class="i-hugeicons-plus-sign h-4 w-4"></span>
12 <button id="new-author-button" type="button" class="inline-flex gap-2 items-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">
13 <span class="h-4 w-4 i-hugeicons-plus-sign"></span>
1414 New Author
1515 </button>
1616 </header>
1717
1818 <section class="grid gap-4 md:grid-cols-3">
19 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
20 <p class="text-sm text-neutral-500 dark:text-neutral-400">Total Authors</p>
21 <p id="author-total" class="mt-2 text-3xl font-semibold">0</p>
19 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
20 <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Authors</p>
21 <p id="author-total" class="mt-2 font-semibold text-3xl">0</p>
2222 </div>
23 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
24 <p class="text-sm text-neutral-500 dark:text-neutral-400">Assigned Posts</p>
25 <p id="author-posts" class="mt-2 text-3xl font-semibold text-green-600 dark:text-green-400">0</p>
23 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
24 <p class="text-neutral-500 text-sm dark:text-neutral-400">Assigned Posts</p>
25 <p id="author-posts" class="mt-2 font-semibold text-3xl text-green-600 dark:text-green-400">0</p>
2626 </div>
27 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
28 <p class="text-sm text-neutral-500 dark:text-neutral-400">Newest</p>
29 <p id="author-newest" class="mt-2 truncate text-2xl font-semibold">-</p>
27 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
28 <p class="text-neutral-500 text-sm dark:text-neutral-400">Newest</p>
29 <p id="author-newest" class="mt-2 font-semibold text-2xl truncate">-</p>
3030 </div>
3131 </section>
3232
33 <section class="rounded-lg border border-neutral-200 bg-white shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
34 <div class="border-b border-neutral-200 p-4 dark:border-neutral-800">
35 <input id="author-search" type="search" placeholder="Search authors..." class="w-full max-w-md rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950" />
33 <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
34 <div class="p-4 border-b border-neutral-200 dark:border-neutral-800">
35 <input id="author-search" type="search" placeholder="Search authors..." class="px-3 py-2 max-w-md w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
3636 </div>
3737 <div class="overflow-x-auto">
38 <table class="min-w-full divide-y divide-neutral-200 text-sm dark:divide-neutral-800">
39 <thead class="bg-neutral-50 text-left text-xs font-semibold uppercase text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
38 <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
39 <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
4040 <tr>
4141 <th class="px-4 py-3">Name</th>
4242 <th class="px-4 py-3">Email</th>
@@ -45,26 +45,26 @@
4545 <th class="px-4 py-3 text-right">Actions</th>
4646 </tr>
4747 </thead>
48 <tbody id="authors-table" class="divide-y divide-neutral-200 dark:divide-neutral-800"></tbody>
48 <tbody id="authors-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
4949 </table>
5050 </div>
51 <div id="authors-empty" class="hidden p-10 text-center text-sm text-neutral-500 dark:text-neutral-400">No authors found.</div>
51 <div id="authors-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No authors found.</div>
5252 </section>
5353 </div>
5454</div>
5555
56<dialog id="author-dialog" class="w-full max-w-lg rounded-lg border border-neutral-200 bg-white p-0 text-neutral-950 shadow-xl backdrop:bg-black/40 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100">
56<dialog id="author-dialog" class="p-0 max-w-lg w-full text-neutral-950 dark:text-neutral-100 bg-white backdrop:bg-black/40 dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-xl">
5757 <form id="author-form" method="dialog">
58 <div class="border-b border-neutral-200 px-5 py-4 dark:border-neutral-800">
59 <h2 class="text-lg font-semibold">New Author</h2>
58 <div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
59 <h2 class="font-semibold text-lg">New Author</h2>
6060 </div>
6161 <div class="grid gap-4 p-5">
62 <label class="grid gap-1 text-sm font-medium">Name <input id="author-name" required class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950" /></label>
63 <label class="grid gap-1 text-sm font-medium">Email <input id="author-email" type="email" required class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950" /></label>
62 <label class="grid gap-1 font-medium text-sm">Name <input id="author-name" required class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
63 <label class="grid gap-1 font-medium text-sm">Email <input id="author-email" type="email" required class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
6464 </div>
65 <div class="flex justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
66 <button id="author-cancel" type="button" class="rounded-md border border-neutral-300 px-3 py-2 text-sm font-medium dark:border-neutral-700">Cancel</button>
67 <button type="submit" class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500">Create Author</button>
65 <div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
66 <button id="author-cancel" type="button" class="px-3 py-2 font-medium text-sm border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
67 <button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Create Author</button>
6868 </div>
6969 </form>
7070</dialog>
@@ -107,7 +107,7 @@
107107 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(author.email) + '</td>' +
108108 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + postCount(author) + '</td>' +
109109 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + formatDate(author.created_at) + '</td>' +
110 '<td class="px-4 py-3 text-right"><button type="button" data-id="' + author.id + '" class="rounded p-2 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
110 '<td class="px-4 py-3 text-right"><button type="button" data-id="' + author.id + '" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
111111 '</tr>'
112112 }).join('')
113113 }
storage/framework/defaults/views/dashboard/content/categories/index.stxmodified+34-34
Changes to storage/framework/defaults/views/dashboard/content/categories/index.stx
@@ -1,42 +1,42 @@
11@extends('layouts/default')
22
33@section('content')
4<div class="min-h-screen bg-neutral-50 p-6 text-neutral-950 dark:bg-neutral-950 dark:text-neutral-100">
5 <div class="mx-auto flex max-w-6xl flex-col gap-6">
6 <header class="flex flex-col gap-4 border-b border-neutral-200 pb-5 dark:border-neutral-800 md:flex-row md:items-end md:justify-between">
4<div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
5 <div class="flex flex-col gap-6 mx-auto max-w-6xl">
6 <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
77 <div>
8 <p class="text-xs font-semibold uppercase tracking-wide text-blue-600 dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 text-2xl font-semibold tracking-tight">Categories</h1>
10 <p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Organize posts and markdown content into public blog categories.</p>
8 <p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 font-semibold text-2xl tracking-tight">Categories</h1>
10 <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Organize posts and markdown content into public blog categories.</p>
1111 </div>
12 <button id="new-category-button" type="button" class="inline-flex items-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
13 <span class="i-hugeicons-plus-sign h-4 w-4"></span>
12 <button id="new-category-button" type="button" class="inline-flex gap-2 items-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">
13 <span class="h-4 w-4 i-hugeicons-plus-sign"></span>
1414 New Category
1515 </button>
1616 </header>
1717
1818 <section class="grid gap-4 md:grid-cols-3">
19 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
20 <p class="text-sm text-neutral-500 dark:text-neutral-400">Total Categories</p>
21 <p id="category-total" class="mt-2 text-3xl font-semibold">0</p>
19 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
20 <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Categories</p>
21 <p id="category-total" class="mt-2 font-semibold text-3xl">0</p>
2222 </div>
23 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
24 <p class="text-sm text-neutral-500 dark:text-neutral-400">Newest</p>
25 <p id="category-newest" class="mt-2 truncate text-2xl font-semibold">-</p>
23 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
24 <p class="text-neutral-500 text-sm dark:text-neutral-400">Newest</p>
25 <p id="category-newest" class="mt-2 font-semibold text-2xl truncate">-</p>
2626 </div>
27 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
28 <p class="text-sm text-neutral-500 dark:text-neutral-400">Active</p>
29 <p id="category-active" class="mt-2 text-3xl font-semibold text-green-600 dark:text-green-400">0</p>
27 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
28 <p class="text-neutral-500 text-sm dark:text-neutral-400">Active</p>
29 <p id="category-active" class="mt-2 font-semibold text-3xl text-green-600 dark:text-green-400">0</p>
3030 </div>
3131 </section>
3232
33 <section class="rounded-lg border border-neutral-200 bg-white shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
34 <div class="border-b border-neutral-200 p-4 dark:border-neutral-800">
35 <input id="category-search" type="search" placeholder="Search categories..." class="w-full max-w-md rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950" />
33 <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
34 <div class="p-4 border-b border-neutral-200 dark:border-neutral-800">
35 <input id="category-search" type="search" placeholder="Search categories..." class="px-3 py-2 max-w-md w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
3636 </div>
3737 <div class="overflow-x-auto">
38 <table class="min-w-full divide-y divide-neutral-200 text-sm dark:divide-neutral-800">
39 <thead class="bg-neutral-50 text-left text-xs font-semibold uppercase text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
38 <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
39 <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
4040 <tr>
4141 <th class="px-4 py-3">Name</th>
4242 <th class="px-4 py-3">Slug</th>
@@ -44,27 +44,27 @@
4444 <th class="px-4 py-3 text-right">Actions</th>
4545 </tr>
4646 </thead>
47 <tbody id="categories-table" class="divide-y divide-neutral-200 dark:divide-neutral-800"></tbody>
47 <tbody id="categories-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
4848 </table>
4949 </div>
50 <div id="categories-empty" class="hidden p-10 text-center text-sm text-neutral-500 dark:text-neutral-400">No categories found.</div>
50 <div id="categories-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No categories found.</div>
5151 </section>
5252 </div>
5353</div>
5454
55<dialog id="category-dialog" class="w-full max-w-lg rounded-lg border border-neutral-200 bg-white p-0 text-neutral-950 shadow-xl backdrop:bg-black/40 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100">
55<dialog id="category-dialog" class="p-0 max-w-lg w-full text-neutral-950 dark:text-neutral-100 bg-white backdrop:bg-black/40 dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-xl">
5656 <form id="category-form" method="dialog">
57 <div class="border-b border-neutral-200 px-5 py-4 dark:border-neutral-800">
58 <h2 class="text-lg font-semibold">New Category</h2>
57 <div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
58 <h2 class="font-semibold text-lg">New Category</h2>
5959 </div>
6060 <div class="grid gap-4 p-5">
61 <label class="grid gap-1 text-sm font-medium">Name <input id="category-name" required class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950" /></label>
62 <label class="grid gap-1 text-sm font-medium">Slug <input id="category-slug" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950" /></label>
63 <label class="grid gap-1 text-sm font-medium">Description <textarea id="category-description" rows="3" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950"></textarea></label>
61 <label class="grid gap-1 font-medium text-sm">Name <input id="category-name" required class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
62 <label class="grid gap-1 font-medium text-sm">Slug <input id="category-slug" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
63 <label class="grid gap-1 font-medium text-sm">Description <textarea id="category-description" rows="3" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700"></textarea></label>
6464 </div>
65 <div class="flex justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
66 <button id="category-cancel" type="button" class="rounded-md border border-neutral-300 px-3 py-2 text-sm font-medium dark:border-neutral-700">Cancel</button>
67 <button type="submit" class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500">Create Category</button>
65 <div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
66 <button id="category-cancel" type="button" class="px-3 py-2 font-medium text-sm border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
67 <button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Create Category</button>
6868 </div>
6969 </form>
7070</dialog>
@@ -100,7 +100,7 @@
100100 '<td class="px-4 py-3 font-medium">' + escapeHtml(category.name) + '</td>' +
101101 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(category.slug) + '</td>' +
102102 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(category.description) + '</td>' +
103 '<td class="px-4 py-3 text-right"><button type="button" data-id="' + category.id + '" class="rounded p-2 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
103 '<td class="px-4 py-3 text-right"><button type="button" data-id="' + category.id + '" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
104104 '</tr>'
105105 }).join('')
106106 }
storage/framework/defaults/views/dashboard/content/comments/index.stxmodified+32-32
Changes to storage/framework/defaults/views/dashboard/content/comments/index.stx
@@ -1,16 +1,16 @@
11@extends('layouts/default')
22
33@section('content')
4<div class="min-h-screen bg-neutral-50 p-6 text-neutral-950 dark:bg-neutral-950 dark:text-neutral-100">
5 <div class="mx-auto flex max-w-7xl flex-col gap-6">
6 <header class="flex flex-col gap-4 border-b border-neutral-200 pb-5 dark:border-neutral-800 md:flex-row md:items-end md:justify-between">
4<div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
5 <div class="flex flex-col gap-6 mx-auto max-w-7xl">
6 <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
77 <div>
8 <p class="text-xs font-semibold uppercase tracking-wide text-blue-600 dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 text-2xl font-semibold tracking-tight">Comments</h1>
10 <p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Moderate reader comments from the CMS comments table.</p>
8 <p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 font-semibold text-2xl tracking-tight">Comments</h1>
10 <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Moderate reader comments from the CMS comments table.</p>
1111 </div>
1212 <div class="flex flex-wrap gap-2">
13 <select id="comment-status-filter" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950">
13 <select id="comment-status-filter" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700">
1414 <option value="">All statuses</option>
1515 <option value="approved">Approved</option>
1616 <option value="pending">Pending</option>
@@ -21,31 +21,31 @@
2121 </header>
2222
2323 <section class="grid gap-4 md:grid-cols-4">
24 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
25 <p class="text-sm text-neutral-500 dark:text-neutral-400">Total Comments</p>
26 <p id="comment-total" class="mt-2 text-3xl font-semibold">0</p>
24 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
25 <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Comments</p>
26 <p id="comment-total" class="mt-2 font-semibold text-3xl">0</p>
2727 </div>
28 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
29 <p class="text-sm text-neutral-500 dark:text-neutral-400">Approved</p>
30 <p id="comment-approved" class="mt-2 text-3xl font-semibold text-green-600 dark:text-green-400">0</p>
28 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
29 <p class="text-neutral-500 text-sm dark:text-neutral-400">Approved</p>
30 <p id="comment-approved" class="mt-2 font-semibold text-3xl text-green-600 dark:text-green-400">0</p>
3131 </div>
32 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
33 <p class="text-sm text-neutral-500 dark:text-neutral-400">Pending</p>
34 <p id="comment-pending" class="mt-2 text-3xl font-semibold text-amber-600 dark:text-amber-400">0</p>
32 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
33 <p class="text-neutral-500 text-sm dark:text-neutral-400">Pending</p>
34 <p id="comment-pending" class="mt-2 font-semibold text-3xl text-amber-600 dark:text-amber-400">0</p>
3535 </div>
36 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
37 <p class="text-sm text-neutral-500 dark:text-neutral-400">Spam</p>
38 <p id="comment-spam" class="mt-2 text-3xl font-semibold text-red-600 dark:text-red-400">0</p>
36 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
37 <p class="text-neutral-500 text-sm dark:text-neutral-400">Spam</p>
38 <p id="comment-spam" class="mt-2 font-semibold text-3xl text-red-600 dark:text-red-400">0</p>
3939 </div>
4040 </section>
4141
42 <section class="rounded-lg border border-neutral-200 bg-white shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
43 <div class="border-b border-neutral-200 p-4 dark:border-neutral-800">
44 <input id="comment-search" type="search" placeholder="Search comments..." class="w-full max-w-md rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950" />
42 <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
43 <div class="p-4 border-b border-neutral-200 dark:border-neutral-800">
44 <input id="comment-search" type="search" placeholder="Search comments..." class="px-3 py-2 max-w-md w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
4545 </div>
4646 <div class="overflow-x-auto">
47 <table class="min-w-full divide-y divide-neutral-200 text-sm dark:divide-neutral-800">
48 <thead class="bg-neutral-50 text-left text-xs font-semibold uppercase text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
47 <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
48 <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
4949 <tr>
5050 <th class="px-4 py-3">Author</th>
5151 <th class="px-4 py-3">Comment</th>
@@ -55,10 +55,10 @@
5555 <th class="px-4 py-3 text-right">Actions</th>
5656 </tr>
5757 </thead>
58 <tbody id="comments-table" class="divide-y divide-neutral-200 dark:divide-neutral-800"></tbody>
58 <tbody id="comments-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
5959 </table>
6060 </div>
61 <div id="comments-empty" class="hidden p-10 text-center text-sm text-neutral-500 dark:text-neutral-400">No comments found.</div>
61 <div id="comments-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No comments found.</div>
6262 </section>
6363 </div>
6464</div>
@@ -116,15 +116,15 @@
116116 empty.classList.toggle('hidden', rows.length > 0)
117117 table.innerHTML = rows.map(function (comment) {
118118 return '<tr class="hover:bg-neutral-50 dark:hover:bg-neutral-950/70">' +
119 '<td class="px-4 py-3"><div class="font-medium">' + escapeHtml(comment.authorName) + '</div><div class="text-xs text-neutral-500">' + escapeHtml(comment.authorEmail) + '</div></td>' +
119 '<td class="px-4 py-3"><div class="font-medium">' + escapeHtml(comment.authorName) + '</div><div class="text-neutral-500 text-xs">' + escapeHtml(comment.authorEmail) + '</div></td>' +
120120 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(truncate(comment.content, 120)) + '</td>' +
121121 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(comment.postTitle || '-') + '</td>' +
122 '<td class="px-4 py-3"><span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium ' + statusClass(comment.status) + '">' + escapeHtml(comment.status) + '</span></td>' +
122 '<td class="px-4 py-3"><span class="inline-flex px-2 py-0.5 font-medium text-xs rounded-full ' ' + + statusClass(comment.status)">' + escapeHtml(comment.status) + '</span></td>' +
123123 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + formatDate(comment.createdAt) + '</td>' +
124 '<td class="px-4 py-3 text-right"><div class="flex justify-end gap-1">' +
125 '<button type="button" data-action="approved" data-id="' + comment.id + '" class="rounded p-2 text-green-600 hover:bg-green-50 dark:text-green-400 dark:hover:bg-green-950/40"><span class="block h-4 w-4 i-hugeicons-checkmark-circle-02"></span></button>' +
126 '<button type="button" data-action="spam" data-id="' + comment.id + '" class="rounded p-2 text-amber-600 hover:bg-amber-50 dark:text-amber-400 dark:hover:bg-amber-950/40"><span class="block h-4 w-4 i-hugeicons-alert-02"></span></button>' +
127 '<button type="button" data-action="delete" data-id="' + comment.id + '" class="rounded p-2 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40"><span class="block h-4 w-4 i-hugeicons-waste"></span></button>' +
124 '<td class="px-4 py-3 text-right"><div class="flex gap-1 justify-end">' +
125 '<button type="button" data-action="approved" data-id="' + comment.id + '" class="p-2 text-green-600 dark:text-green-400 hover:bg-green-50 dark:hover:bg-green-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-checkmark-circle-02"></span></button>' +
126 '<button type="button" data-action="spam" data-id="' + comment.id + '" class="p-2 text-amber-600 dark:text-amber-400 hover:bg-amber-50 dark:hover:bg-amber-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-alert-02"></span></button>' +
127 '<button type="button" data-action="delete" data-id="' + comment.id + '" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-waste"></span></button>' +
128128 '</div></td>' +
129129 '</tr>'
130130 }).join('')
storage/framework/defaults/views/dashboard/content/pages/index.stxmodified+34-34
Changes to storage/framework/defaults/views/dashboard/content/pages/index.stx
@@ -1,42 +1,42 @@
11@extends('layouts/default')
22
33@section('content')
4<div class="min-h-screen bg-neutral-50 p-6 text-neutral-950 dark:bg-neutral-950 dark:text-neutral-100">
5 <div class="mx-auto flex max-w-6xl flex-col gap-6">
6 <header class="flex flex-col gap-4 border-b border-neutral-200 pb-5 dark:border-neutral-800 md:flex-row md:items-end md:justify-between">
4<div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
5 <div class="flex flex-col gap-6 mx-auto max-w-6xl">
6 <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
77 <div>
8 <p class="text-xs font-semibold uppercase tracking-wide text-blue-600 dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 text-2xl font-semibold tracking-tight">Pages</h1>
10 <p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Manage static CMS pages alongside your blog content.</p>
8 <p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 font-semibold text-2xl tracking-tight">Pages</h1>
10 <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Manage static CMS pages alongside your blog content.</p>
1111 </div>
12 <button id="new-page-button" type="button" class="inline-flex items-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
13 <span class="i-hugeicons-plus-sign h-4 w-4"></span>
12 <button id="new-page-button" type="button" class="inline-flex gap-2 items-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">
13 <span class="h-4 w-4 i-hugeicons-plus-sign"></span>
1414 New Page
1515 </button>
1616 </header>
1717
1818 <section class="grid gap-4 md:grid-cols-3">
19 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
20 <p class="text-sm text-neutral-500 dark:text-neutral-400">Total Pages</p>
21 <p id="page-total" class="mt-2 text-3xl font-semibold">0</p>
19 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
20 <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Pages</p>
21 <p id="page-total" class="mt-2 font-semibold text-3xl">0</p>
2222 </div>
23 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
24 <p class="text-sm text-neutral-500 dark:text-neutral-400">Views</p>
25 <p id="page-views" class="mt-2 text-3xl font-semibold">0</p>
23 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
24 <p class="text-neutral-500 text-sm dark:text-neutral-400">Views</p>
25 <p id="page-views" class="mt-2 font-semibold text-3xl">0</p>
2626 </div>
27 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
28 <p class="text-sm text-neutral-500 dark:text-neutral-400">Newest</p>
29 <p id="page-newest" class="mt-2 truncate text-2xl font-semibold">-</p>
27 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
28 <p class="text-neutral-500 text-sm dark:text-neutral-400">Newest</p>
29 <p id="page-newest" class="mt-2 font-semibold text-2xl truncate">-</p>
3030 </div>
3131 </section>
3232
33 <section class="rounded-lg border border-neutral-200 bg-white shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
34 <div class="border-b border-neutral-200 p-4 dark:border-neutral-800">
35 <input id="page-search" type="search" placeholder="Search pages..." class="w-full max-w-md rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950" />
33 <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
34 <div class="p-4 border-b border-neutral-200 dark:border-neutral-800">
35 <input id="page-search" type="search" placeholder="Search pages..." class="px-3 py-2 max-w-md w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
3636 </div>
3737 <div class="overflow-x-auto">
38 <table class="min-w-full divide-y divide-neutral-200 text-sm dark:divide-neutral-800">
39 <thead class="bg-neutral-50 text-left text-xs font-semibold uppercase text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
38 <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
39 <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
4040 <tr>
4141 <th class="px-4 py-3">Title</th>
4242 <th class="px-4 py-3">Template</th>
@@ -45,23 +45,23 @@
4545 <th class="px-4 py-3 text-right">Actions</th>
4646 </tr>
4747 </thead>
48 <tbody id="pages-table" class="divide-y divide-neutral-200 dark:divide-neutral-800"></tbody>
48 <tbody id="pages-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
4949 </table>
5050 </div>
51 <div id="pages-empty" class="hidden p-10 text-center text-sm text-neutral-500 dark:text-neutral-400">No pages found.</div>
51 <div id="pages-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No pages found.</div>
5252 </section>
5353 </div>
5454</div>
5555
56<dialog id="page-dialog" class="w-full max-w-lg rounded-lg border border-neutral-200 bg-white p-0 text-neutral-950 shadow-xl backdrop:bg-black/40 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100">
56<dialog id="page-dialog" class="p-0 max-w-lg w-full text-neutral-950 dark:text-neutral-100 bg-white backdrop:bg-black/40 dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-xl">
5757 <form id="page-form" method="dialog">
58 <div class="border-b border-neutral-200 px-5 py-4 dark:border-neutral-800">
59 <h2 class="text-lg font-semibold">New Page</h2>
58 <div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
59 <h2 class="font-semibold text-lg">New Page</h2>
6060 </div>
6161 <div class="grid gap-4 p-5">
62 <label class="grid gap-1 text-sm font-medium">Title <input id="page-title" required class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950" /></label>
63 <label class="grid gap-1 text-sm font-medium">Template
64 <select id="page-template" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950">
62 <label class="grid gap-1 font-medium text-sm">Title <input id="page-title" required class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
63 <label class="grid gap-1 font-medium text-sm">Template
64 <select id="page-template" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700">
6565 <option value="default">Default</option>
6666 <option value="landing">Landing</option>
6767 <option value="blog">Blog</option>
@@ -69,9 +69,9 @@
6969 </select>
7070 </label>
7171 </div>
72 <div class="flex justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
73 <button id="page-cancel" type="button" class="rounded-md border border-neutral-300 px-3 py-2 text-sm font-medium dark:border-neutral-700">Cancel</button>
74 <button type="submit" class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500">Create Page</button>
72 <div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
73 <button id="page-cancel" type="button" class="px-3 py-2 font-medium text-sm border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
74 <button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Create Page</button>
7575 </div>
7676 </form>
7777</dialog>
@@ -110,7 +110,7 @@
110110 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(page.template) + '</td>' +
111111 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + Number(page.views || 0).toLocaleString() + '</td>' +
112112 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + fmtDate(page.published_at || page.created_at) + '</td>' +
113 '<td class="px-4 py-3 text-right"><button type="button" data-id="' + page.id + '" class="rounded p-2 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
113 '<td class="px-4 py-3 text-right"><button type="button" data-id="' + page.id + '" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
114114 '</tr>'
115115 }).join('')
116116 }
storage/framework/defaults/views/dashboard/content/posts/index.stxmodified+55-55
Changes to storage/framework/defaults/views/dashboard/content/posts/index.stx
@@ -1,52 +1,52 @@
11@extends('layouts/default')
22
33@section('content')
4<div class="min-h-screen bg-neutral-50 p-6 text-neutral-950 dark:bg-neutral-950 dark:text-neutral-100">
5 <div class="mx-auto flex max-w-7xl flex-col gap-6">
6 <header class="flex flex-col gap-4 border-b border-neutral-200 pb-5 dark:border-neutral-800 md:flex-row md:items-end md:justify-between">
4<div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
5 <div class="flex flex-col gap-6 mx-auto max-w-7xl">
6 <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
77 <div>
8 <p class="text-xs font-semibold uppercase tracking-wide text-blue-600 dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 text-2xl font-semibold tracking-tight">Posts</h1>
10 <p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Create, edit, publish, and remove content backed by the Stacks CMS tables.</p>
8 <p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 font-semibold text-2xl tracking-tight">Posts</h1>
10 <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Create, edit, publish, and remove content backed by the Stacks CMS tables.</p>
1111 </div>
1212 <div class="flex flex-wrap gap-2">
13 <a href="/blog" target="_blank" class="inline-flex items-center gap-2 rounded-md border border-neutral-300 px-3 py-2 text-sm font-medium hover:bg-white dark:border-neutral-700 dark:hover:bg-neutral-900">
14 <span class="i-hugeicons-view h-4 w-4"></span>
13 <a href="/blog" target="_blank" class="inline-flex gap-2 items-center px-3 py-2 font-medium text-sm hover:bg-white dark:hover:bg-neutral-900 border border-neutral-300 rounded-md dark:border-neutral-700">
14 <span class="h-4 w-4 i-hugeicons-view"></span>
1515 View Blog
1616 </a>
17 <button id="new-post-button" type="button" class="inline-flex items-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
18 <span class="i-hugeicons-plus-sign h-4 w-4"></span>
17 <button id="new-post-button" type="button" class="inline-flex gap-2 items-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">
18 <span class="h-4 w-4 i-hugeicons-plus-sign"></span>
1919 New Post
2020 </button>
2121 </div>
2222 </header>
2323
2424 <section class="grid gap-4 md:grid-cols-4">
25 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
26 <p class="text-sm text-neutral-500 dark:text-neutral-400">Total Posts</p>
27 <p id="stat-total" class="mt-2 text-3xl font-semibold">0</p>
25 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
26 <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Posts</p>
27 <p id="stat-total" class="mt-2 font-semibold text-3xl">0</p>
2828 </div>
29 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
30 <p class="text-sm text-neutral-500 dark:text-neutral-400">Published</p>
31 <p id="stat-published" class="mt-2 text-3xl font-semibold text-green-600 dark:text-green-400">0</p>
29 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
30 <p class="text-neutral-500 text-sm dark:text-neutral-400">Published</p>
31 <p id="stat-published" class="mt-2 font-semibold text-3xl text-green-600 dark:text-green-400">0</p>
3232 </div>
33 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
34 <p class="text-sm text-neutral-500 dark:text-neutral-400">Drafts</p>
35 <p id="stat-drafts" class="mt-2 text-3xl font-semibold text-amber-600 dark:text-amber-400">0</p>
33 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
34 <p class="text-neutral-500 text-sm dark:text-neutral-400">Drafts</p>
35 <p id="stat-drafts" class="mt-2 font-semibold text-3xl text-amber-600 dark:text-amber-400">0</p>
3636 </div>
37 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
38 <p class="text-sm text-neutral-500 dark:text-neutral-400">Views</p>
39 <p id="stat-views" class="mt-2 text-3xl font-semibold">0</p>
37 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
38 <p class="text-neutral-500 text-sm dark:text-neutral-400">Views</p>
39 <p id="stat-views" class="mt-2 font-semibold text-3xl">0</p>
4040 </div>
4141 </section>
4242
43 <section class="rounded-lg border border-neutral-200 bg-white shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
44 <div class="flex flex-col gap-3 border-b border-neutral-200 p-4 dark:border-neutral-800 md:flex-row md:items-center md:justify-between">
45 <div class="relative max-w-md flex-1">
46 <span class="i-hugeicons-search-01 pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-neutral-400"></span>
47 <input id="post-search" type="search" placeholder="Search posts..." class="w-full rounded-md border border-neutral-300 bg-white py-2 pl-9 pr-3 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950" />
43 <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
44 <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between p-4 border-b border-neutral-200 dark:border-neutral-800">
45 <div class="relative flex-1 max-w-md">
46 <span class="absolute left-3 top-1/2 h-4 w-4 text-neutral-400 pointer-events-none -translate-y-1/2 i-hugeicons-search-01"></span>
47 <input id="post-search" type="search" placeholder="Search posts..." class="pl-9 pr-3 py-2 w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
4848 </div>
49 <select id="post-status-filter" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950">
49 <select id="post-status-filter" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700">
5050 <option value="">All statuses</option>
5151 <option value="published">Published</option>
5252 <option value="draft">Draft</option>
@@ -55,8 +55,8 @@
5555 </div>
5656
5757 <div class="overflow-x-auto">
58 <table class="min-w-full divide-y divide-neutral-200 text-sm dark:divide-neutral-800">
59 <thead class="bg-neutral-50 text-left text-xs font-semibold uppercase text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
58 <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
59 <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
6060 <tr>
6161 <th class="px-4 py-3">Title</th>
6262 <th class="px-4 py-3">Status</th>
@@ -65,57 +65,57 @@
6565 <th class="px-4 py-3 text-right">Actions</th>
6666 </tr>
6767 </thead>
68 <tbody id="posts-table" class="divide-y divide-neutral-200 dark:divide-neutral-800"></tbody>
68 <tbody id="posts-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
6969 </table>
7070 </div>
7171
7272 <div id="posts-empty" class="hidden p-10 text-center">
7373 <div class="mx-auto h-10 w-10 text-neutral-400 i-hugeicons-quill-write-02"></div>
74 <h2 class="mt-3 text-sm font-semibold">No posts found</h2>
75 <p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Create a draft to get the CMS moving.</p>
74 <h2 class="mt-3 font-semibold text-sm">No posts found</h2>
75 <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Create a draft to get the CMS moving.</p>
7676 </div>
7777 </section>
7878 </div>
7979</div>
8080
81<dialog id="post-dialog" class="w-full max-w-3xl rounded-lg border border-neutral-200 bg-white p-0 text-neutral-950 shadow-xl backdrop:bg-black/40 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100">
81<dialog id="post-dialog" class="p-0 max-w-3xl w-full text-neutral-950 dark:text-neutral-100 bg-white backdrop:bg-black/40 dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-xl">
8282 <form id="post-form" method="dialog" class="flex flex-col">
83 <div class="border-b border-neutral-200 px-5 py-4 dark:border-neutral-800">
84 <h2 id="post-dialog-title" class="text-lg font-semibold">New Post</h2>
85 <p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Markdown works well here; the public blog reads from the same content field.</p>
83 <div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
84 <h2 id="post-dialog-title" class="font-semibold text-lg">New Post</h2>
85 <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Markdown works well here; the public blog reads from the same content field.</p>
8686 </div>
8787 <div class="grid gap-4 p-5">
8888 <input id="post-id" type="hidden" />
89 <label class="grid gap-1 text-sm font-medium">
89 <label class="grid gap-1 font-medium text-sm">
9090 Title
91 <input id="post-title" required class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950" />
91 <input id="post-title" required class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
9292 </label>
93 <label class="grid gap-1 text-sm font-medium">
93 <label class="grid gap-1 font-medium text-sm">
9494 Excerpt
95 <textarea id="post-excerpt" rows="2" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950"></textarea>
95 <textarea id="post-excerpt" rows="2" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20"></textarea>
9696 </label>
9797 <div class="grid gap-4 md:grid-cols-2">
98 <label class="grid gap-1 text-sm font-medium">
98 <label class="grid gap-1 font-medium text-sm">
9999 Status
100 <select id="post-status" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950">
100 <select id="post-status" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700">
101101 <option value="draft">Draft</option>
102102 <option value="published">Published</option>
103103 <option value="archived">Archived</option>
104104 </select>
105105 </label>
106 <label class="grid gap-1 text-sm font-medium">
106 <label class="grid gap-1 font-medium text-sm">
107107 Poster URL
108 <input id="post-poster" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950" />
108 <input id="post-poster" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
109109 </label>
110110 </div>
111 <label class="grid gap-1 text-sm font-medium">
111 <label class="grid gap-1 font-medium text-sm">
112112 Content
113 <textarea id="post-content" rows="12" class="rounded-md border border-neutral-300 bg-white px-3 py-2 font-mono text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950"></textarea>
113 <textarea id="post-content" rows="12" class="px-3 py-2 font-mono text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20"></textarea>
114114 </label>
115115 </div>
116 <div class="flex justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
117 <button id="post-cancel" type="button" class="rounded-md border border-neutral-300 px-3 py-2 text-sm font-medium hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800">Cancel</button>
118 <button type="submit" class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500">Save Post</button>
116 <div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
117 <button id="post-cancel" type="button" class="px-3 py-2 font-medium text-sm hover:bg-neutral-50 dark:hover:bg-neutral-800 border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
118 <button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Save Post</button>
119119 </div>
120120 </form>
121121</dialog>
@@ -172,13 +172,13 @@
172172 empty.classList.toggle('hidden', rows.length > 0)
173173 table.innerHTML = rows.map(function (post) {
174174 return '<tr class="hover:bg-neutral-50 dark:hover:bg-neutral-950/70">' +
175 '<td class="px-4 py-3"><div class="font-medium">' + escapeHtml(post.title) + '</div><div class="mt-1 max-w-xl truncate text-xs text-neutral-500 dark:text-neutral-400">' + escapeHtml(post.excerpt || post.content || '') + '</div></td>' +
176 '<td class="px-4 py-3"><span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium ' + statusClass(post.status) + '">' + escapeHtml(post.status || 'draft') + '</span></td>' +
175 '<td class="px-4 py-3"><div class="font-medium">' + escapeHtml(post.title) + '</div><div class="mt-1 max-w-xl text-neutral-500 text-xs truncate dark:text-neutral-400">' + escapeHtml(post.excerpt || post.content || '') + '</div></td>' +
176 '<td class="px-4 py-3"><span class="inline-flex px-2 py-0.5 font-medium text-xs rounded-full ' ' + + statusClass(post.status)">' + escapeHtml(post.status || 'draft') + '</span></td>' +
177177 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + Number(post.views || 0).toLocaleString() + '</td>' +
178178 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + fmtDate(post.published_at || post.created_at) + '</td>' +
179 '<td class="px-4 py-3"><div class="flex justify-end gap-1">' +
180 '<button type="button" class="rounded p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800" data-action="edit" data-id="' + post.id + '" title="Edit"><span class="block h-4 w-4 i-hugeicons-edit-01"></span></button>' +
181 '<button type="button" class="rounded p-2 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40" data-action="delete" data-id="' + post.id + '" title="Delete"><span class="block h-4 w-4 i-hugeicons-waste"></span></button>' +
179 '<td class="px-4 py-3"><div class="flex gap-1 justify-end">' +
180 '<button type="button" class="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded" data-action="edit" data-id="' + post.id + '" title="Edit"><span class="block h-4 w-4 i-hugeicons-edit-01"></span></button>' +
181 '<button type="button" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded" data-action="delete" data-id="' + post.id + '" title="Delete"><span class="block h-4 w-4 i-hugeicons-waste"></span></button>' +
182182 '</div></td>' +
183183 '</tr>'
184184 }).join('')
storage/framework/defaults/views/dashboard/content/tags/index.stxmodified+34-34
Changes to storage/framework/defaults/views/dashboard/content/tags/index.stx
@@ -1,42 +1,42 @@
11@extends('layouts/default')
22
33@section('content')
4<div class="min-h-screen bg-neutral-50 p-6 text-neutral-950 dark:bg-neutral-950 dark:text-neutral-100">
5 <div class="mx-auto flex max-w-6xl flex-col gap-6">
6 <header class="flex flex-col gap-4 border-b border-neutral-200 pb-5 dark:border-neutral-800 md:flex-row md:items-end md:justify-between">
4<div class="p-6 min-h-screen text-neutral-950 dark:text-neutral-100 bg-neutral-50 dark:bg-neutral-950">
5 <div class="flex flex-col gap-6 mx-auto max-w-6xl">
6 <header class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between pb-5 border-b border-neutral-200 dark:border-neutral-800">
77 <div>
8 <p class="text-xs font-semibold uppercase tracking-wide text-blue-600 dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 text-2xl font-semibold tracking-tight">Tags</h1>
10 <p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Manage the tags used by blog posts and markdown content.</p>
8 <p class="font-semibold text-blue-600 text-xs tracking-wide uppercase dark:text-blue-400">CMS</p>
9 <h1 class="mt-1 font-semibold text-2xl tracking-tight">Tags</h1>
10 <p class="mt-1 text-neutral-500 text-sm dark:text-neutral-400">Manage the tags used by blog posts and markdown content.</p>
1111 </div>
12 <button id="new-tag-button" type="button" class="inline-flex items-center gap-2 rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500">
13 <span class="i-hugeicons-plus-sign h-4 w-4"></span>
12 <button id="new-tag-button" type="button" class="inline-flex gap-2 items-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">
13 <span class="h-4 w-4 i-hugeicons-plus-sign"></span>
1414 New Tag
1515 </button>
1616 </header>
1717
1818 <section class="grid gap-4 md:grid-cols-3">
19 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
20 <p class="text-sm text-neutral-500 dark:text-neutral-400">Total Tags</p>
21 <p id="tag-total" class="mt-2 text-3xl font-semibold">0</p>
19 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
20 <p class="text-neutral-500 text-sm dark:text-neutral-400">Total Tags</p>
21 <p id="tag-total" class="mt-2 font-semibold text-3xl">0</p>
2222 </div>
23 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
24 <p class="text-sm text-neutral-500 dark:text-neutral-400">Newest</p>
25 <p id="tag-newest" class="mt-2 truncate text-2xl font-semibold">-</p>
23 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
24 <p class="text-neutral-500 text-sm dark:text-neutral-400">Newest</p>
25 <p id="tag-newest" class="mt-2 font-semibold text-2xl truncate">-</p>
2626 </div>
27 <div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
28 <p class="text-sm text-neutral-500 dark:text-neutral-400">Active</p>
29 <p id="tag-active" class="mt-2 text-3xl font-semibold text-green-600 dark:text-green-400">0</p>
27 <div class="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
28 <p class="text-neutral-500 text-sm dark:text-neutral-400">Active</p>
29 <p id="tag-active" class="mt-2 font-semibold text-3xl text-green-600 dark:text-green-400">0</p>
3030 </div>
3131 </section>
3232
33 <section class="rounded-lg border border-neutral-200 bg-white shadow-sm dark:border-neutral-800 dark:bg-neutral-900">
34 <div class="border-b border-neutral-200 p-4 dark:border-neutral-800">
35 <input id="tag-search" type="search" placeholder="Search tags..." class="w-full max-w-md rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-neutral-700 dark:bg-neutral-950" />
33 <section class="bg-white dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-sm">
34 <div class="p-4 border-b border-neutral-200 dark:border-neutral-800">
35 <input id="tag-search" type="search" placeholder="Search tags..." class="px-3 py-2 max-w-md w-full text-sm bg-white dark:bg-neutral-950 border border-neutral-300 outline-none rounded-md dark:border-neutral-700 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20" />
3636 </div>
3737 <div class="overflow-x-auto">
38 <table class="min-w-full divide-y divide-neutral-200 text-sm dark:divide-neutral-800">
39 <thead class="bg-neutral-50 text-left text-xs font-semibold uppercase text-neutral-500 dark:bg-neutral-900 dark:text-neutral-400">
38 <table class="min-w-full text-sm divide-neutral-200 divide-y dark:divide-neutral-800">
39 <thead class="font-semibold text-left text-neutral-500 text-xs uppercase dark:text-neutral-400 bg-neutral-50 dark:bg-neutral-900">
4040 <tr>
4141 <th class="px-4 py-3">Name</th>
4242 <th class="px-4 py-3">Slug</th>
@@ -44,27 +44,27 @@
4444 <th class="px-4 py-3 text-right">Actions</th>
4545 </tr>
4646 </thead>
47 <tbody id="tags-table" class="divide-y divide-neutral-200 dark:divide-neutral-800"></tbody>
47 <tbody id="tags-table" class="divide-neutral-200 divide-y dark:divide-neutral-800"></tbody>
4848 </table>
4949 </div>
50 <div id="tags-empty" class="hidden p-10 text-center text-sm text-neutral-500 dark:text-neutral-400">No tags found.</div>
50 <div id="tags-empty" class="hidden p-10 text-center text-neutral-500 text-sm dark:text-neutral-400">No tags found.</div>
5151 </section>
5252 </div>
5353</div>
5454
55<dialog id="tag-dialog" class="w-full max-w-lg rounded-lg border border-neutral-200 bg-white p-0 text-neutral-950 shadow-xl backdrop:bg-black/40 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100">
55<dialog id="tag-dialog" class="p-0 max-w-lg w-full text-neutral-950 dark:text-neutral-100 bg-white backdrop:bg-black/40 dark:bg-neutral-900 border border-neutral-200 rounded-lg dark:border-neutral-800 shadow-xl">
5656 <form id="tag-form" method="dialog">
57 <div class="border-b border-neutral-200 px-5 py-4 dark:border-neutral-800">
58 <h2 class="text-lg font-semibold">New Tag</h2>
57 <div class="px-5 py-4 border-b border-neutral-200 dark:border-neutral-800">
58 <h2 class="font-semibold text-lg">New Tag</h2>
5959 </div>
6060 <div class="grid gap-4 p-5">
61 <label class="grid gap-1 text-sm font-medium">Name <input id="tag-name" required class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950" /></label>
62 <label class="grid gap-1 text-sm font-medium">Slug <input id="tag-slug" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950" /></label>
63 <label class="grid gap-1 text-sm font-medium">Description <textarea id="tag-description" rows="3" class="rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-950"></textarea></label>
61 <label class="grid gap-1 font-medium text-sm">Name <input id="tag-name" required class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
62 <label class="grid gap-1 font-medium text-sm">Slug <input id="tag-slug" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700" /></label>
63 <label class="grid gap-1 font-medium text-sm">Description <textarea id="tag-description" rows="3" class="px-3 py-2 text-sm bg-white dark:bg-neutral-950 border border-neutral-300 rounded-md dark:border-neutral-700"></textarea></label>
6464 </div>
65 <div class="flex justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
66 <button id="tag-cancel" type="button" class="rounded-md border border-neutral-300 px-3 py-2 text-sm font-medium dark:border-neutral-700">Cancel</button>
67 <button type="submit" class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500">Create Tag</button>
65 <div class="flex gap-2 justify-end px-5 py-4 border-neutral-200 border-t dark:border-neutral-800">
66 <button id="tag-cancel" type="button" class="px-3 py-2 font-medium text-sm border border-neutral-300 rounded-md dark:border-neutral-700">Cancel</button>
67 <button type="submit" class="px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md">Create Tag</button>
6868 </div>
6969 </form>
7070</dialog>
@@ -100,7 +100,7 @@
100100 '<td class="px-4 py-3 font-medium">' + escapeHtml(tag.name) + '</td>' +
101101 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(tag.slug) + '</td>' +
102102 '<td class="px-4 py-3 text-neutral-600 dark:text-neutral-300">' + escapeHtml(tag.description) + '</td>' +
103 '<td class="px-4 py-3 text-right"><button type="button" data-id="' + tag.id + '" class="rounded p-2 text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
103 '<td class="px-4 py-3 text-right"><button type="button" data-id="' + tag.id + '" class="p-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/40 rounded"><span class="block h-4 w-4 i-hugeicons-waste"></span></button></td>' +
104104 '</tr>'
105105 }).join('')
106106 }
storage/framework/defaults/views/dashboard/kanban/[id].stxmodified+74-74
Changes to storage/framework/defaults/views/dashboard/kanban/[id].stx
@@ -443,12 +443,12 @@
443443</script>
444444
445445<div class="mx-auto px-6 py-6 max-w-full">
446 <div :if="!canView" class="rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 px-5 py-8 text-center mx-auto max-w-2xl">
446 <div :if="!canView" class="mx-auto px-5 py-8 max-w-2xl text-center bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
447447 <p class="text-sm text-zinc-600 dark:text-zinc-400">Kanban boards require the dev role.</p>
448448 </div>
449449
450450 <div :if="canView">
451 <a href="/kanban" class="inline-flex items-center gap-1 text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 no-underline mb-4" data-mpa="true">
451 <a href="/kanban" class="inline-flex gap-1 items-center mb-4 no-underline text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300" data-mpa="true">
452452 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6" /></svg>
453453 All boards
454454 </a>
@@ -461,9 +461,9 @@
461461 </div>
462462
463463 <!-- Error -->
464 <div :if="error && !loading" class="rounded-lg border border-red-200 dark:border-red-900/40 bg-red-50 dark:bg-red-950/30 px-5 py-4">
465 <p class="text-sm font-medium text-red-700 dark:text-red-300">Board operation failed</p>
466 <p class="mt-1 text-xs text-red-600 dark:text-red-400" :text="error"></p>
464 <div :if="error && !loading" class="px-5 py-4 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900/40">
465 <p class="font-medium text-red-700 text-sm dark:text-red-300">Board operation failed</p>
466 <p class="mt-1 text-red-600 text-xs dark:text-red-400" :text="error"></p>
467467 </div>
468468
469469 <!-- Loaded -->
@@ -481,23 +481,23 @@
481481 </header>
482482
483483 <!-- The board itself. Horizontal scroll for wide boards;
484 each column is a vertical card stack. -->
485 <div data-kanban-root class="flex gap-3 overflow-x-auto pb-4" style="min-height: 60vh">
484 each column is a vertical card stack. -->
485 <div data-kanban-root class="flex overflow-x-auto gap-3 pb-4" style="min-height: 60vh">
486486 <div
487487 :for="col in columns"
488488 :data-column-id="col.id"
489 class="flex-shrink-0 w-72 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 flex flex-col"
489 class="flex flex-col flex-shrink-0 w-72 bg-white dark:bg-zinc-950 border border-zinc-200 rounded-lg dark:border-zinc-800"
490490 style="max-height: calc(100vh - 220px)"
491491 >
492492 <!-- Column header -->
493493 <div :class="`px-3 py-2 border-b border-zinc-100 dark:border-zinc-900 ${columnAccentClass(col.color)}`">
494 <div class="flex items-center justify-between gap-2">
495 <p class="text-sm font-medium text-zinc-700 dark:text-zinc-200 truncate" :text="col.name"></p>
496 <div class="flex items-center gap-1">
497 <span class="text-xs text-zinc-500 font-mono" :text="col.cards.length"></span>
494 <div class="flex gap-2 items-center justify-between">
495 <p class="font-medium text-sm text-zinc-700 truncate dark:text-zinc-200" :text="col.name"></p>
496 <div class="flex gap-1 items-center">
497 <span class="font-mono text-xs text-zinc-500" :text="col.cards.length"></span>
498498 <button
499499 @click="deleteColumnClicked(col.id, col.cards.length > 0)"
500 class="text-zinc-400 hover:text-red-500 p-1 -m-1"
500 class="-m-1 p-1 text-zinc-400 hover:text-red-500"
501501 title="Delete column"
502502 >
503503 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -510,13 +510,13 @@
510510 </div>
511511
512512 <!-- Cards (scrollable) -->
513 <div class="p-2 space-y-1 overflow-y-auto flex-1" style="min-height: 40px">
513 <div class="overflow-y-auto flex-1 p-2 space-y-1" style="min-height: 40px">
514514 <div
515515 :for="card in col.cards"
516516 :data-card-id="card.id"
517517 :data-card-column-id="col.id"
518518 @click="openDetail(card.id)"
519 class="rounded border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 px-3 py-2 cursor-grab hover:border-violet-300 dark:hover:border-violet-700 transition-colors group"
519 class="px-3 py-2 bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 rounded dark:border-zinc-800 hover:border-violet-300 dark:hover:border-violet-700 transition-colors cursor-grab group"
520520 >
521521 <!-- Label chips strip -->
522522 <div :if="card.labels && card.labels.length > 0" class="flex flex-wrap gap-1 mb-1">
@@ -527,8 +527,8 @@
527527 ></span>
528528 </div>
529529
530 <div class="flex items-start justify-between gap-2">
531 <p class="text-xs text-zinc-800 dark:text-zinc-200 leading-snug flex-1" :text="card.title"></p>
530 <div class="flex gap-2 items-start justify-between">
531 <p class="flex-1 leading-snug text-xs text-zinc-800 dark:text-zinc-200" :text="card.title"></p>
532532 <button
533533 @click.stop="deleteCardClicked(card.id)"
534534 class="text-zinc-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
@@ -542,8 +542,8 @@
542542 </div>
543543
544544 <!-- Footer: due date + assignee avatars -->
545 <div :if="card.dueDate || (card.assignees && card.assignees.length > 0)" class="flex items-center justify-between gap-2 mt-1.5 pt-1.5 border-t border-zinc-200/50 dark:border-zinc-800/50">
546 <div :if="card.dueDate" class="flex items-center gap-1 text-[10px] text-zinc-500">
545 <div :if="card.dueDate || (card.assignees && card.assignees.length > 0)" class="flex gap-2 items-center justify-between mt-1.5 pt-1.5 border-t border-zinc-200/50 dark:border-zinc-800/50">
546 <div :if="card.dueDate" class="flex gap-1 items-center text-[10px] text-zinc-500">
547547 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
548548 <rect x="3" y="4" width="18" height="18" rx="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" />
549549 </svg>
@@ -553,7 +553,7 @@
553553 <div :if="card.assignees && card.assignees.length > 0" class="flex -space-x-1">
554554 <span
555555 :for="asg in card.assignees"
556 class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-violet-500 text-white text-[9px] font-medium ring-1 ring-white dark:ring-zinc-950"
556 class="inline-flex items-center justify-center h-5 w-5 font-medium text-[9px] text-white bg-violet-500 ring-1 ring-white rounded-full dark:ring-zinc-950"
557557 :title="asg.name || asg.email || 'Assignee'"
558558 :text="initialsOf(asg.name, asg.email)"
559559 ></span>
@@ -572,18 +572,18 @@
572572 @keydown.escape="closeCardComposer()"
573573 placeholder="Card title…"
574574 rows="2"
575 class="w-full px-2 py-1 text-xs rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-100 resize-none focus:outline-none focus:border-violet-500"
575 class="px-2 py-1 w-full text-xs text-zinc-800 dark:text-zinc-100 bg-white dark:bg-zinc-900 border border-zinc-300 rounded dark:border-zinc-700 focus:border-violet-500 focus:outline-none resize-none"
576576 ></textarea>
577577 <div class="flex gap-1">
578578 <button
579579 @click="submitCard(col.id)"
580 class="px-2 py-1 text-xs rounded bg-violet-600 hover:bg-violet-700 text-white"
580 class="px-2 py-1 text-white text-xs bg-violet-600 hover:bg-violet-700 rounded"
581581 >
582582 Add
583583 </button>
584584 <button
585585 @click="closeCardComposer()"
586 class="px-2 py-1 text-xs rounded text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"
586 class="px-2 py-1 text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 rounded"
587587 >
588588 Cancel
589589 </button>
@@ -592,7 +592,7 @@
592592 <button
593593 :if="!isComposingFor(col.id)"
594594 @click="openCardComposer(col.id)"
595 class="w-full px-2 py-1 text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded text-left"
595 class="px-2 py-1 w-full text-left text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-900 rounded"
596596 >
597597 + Add card
598598 </button>
@@ -601,26 +601,26 @@
601601
602602 <!-- Add column trailing slot -->
603603 <div class="flex-shrink-0 w-72">
604 <div :if="composingNewColumn()" class="rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-950 p-3 space-y-2">
604 <div :if="composingNewColumn()" class="p-3 space-y-2 bg-white dark:bg-zinc-950 border border-zinc-300 rounded-lg dark:border-zinc-700">
605605 <input
606606 :value="draftColumnName()"
607607 @input="(e) => draftColumnName.set(e.target.value)"
608608 @keydown.enter="submitColumn()"
609609 @keydown.escape="closeColumnComposer()"
610610 placeholder="Column name…"
611 class="w-full px-2 py-1 text-sm rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-100 focus:outline-none focus:border-violet-500"
611 class="px-2 py-1 w-full text-sm text-zinc-800 dark:text-zinc-100 bg-white dark:bg-zinc-900 border border-zinc-300 rounded dark:border-zinc-700 focus:border-violet-500 focus:outline-none"
612612 autofocus
613613 />
614614 <div class="flex gap-1">
615615 <button
616616 @click="submitColumn()"
617 class="px-2 py-1 text-xs rounded bg-violet-600 hover:bg-violet-700 text-white"
617 class="px-2 py-1 text-white text-xs bg-violet-600 hover:bg-violet-700 rounded"
618618 >
619619 Add column
620620 </button>
621621 <button
622622 @click="closeColumnComposer()"
623 class="px-2 py-1 text-xs rounded text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"
623 class="px-2 py-1 text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 rounded"
624624 >
625625 Cancel
626626 </button>
@@ -629,7 +629,7 @@
629629 <button
630630 :if="!composingNewColumn()"
631631 @click="openColumnComposer()"
632 class="w-full h-12 rounded-lg border border-dashed border-zinc-300 dark:border-zinc-700 text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:border-zinc-400 dark:hover:border-zinc-600 text-sm"
632 class="h-12 w-full text-sm text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 border border-dashed border-zinc-300 rounded-lg dark:border-zinc-700 hover:border-zinc-400 dark:hover:border-zinc-600"
633633 >
634634 + Add column
635635 </button>
@@ -640,31 +640,31 @@
640640
641641 <!-- ───────────── Card detail modal (Phase 3) ───────────── -->
642642 <!-- Opens on card click. Fixed positioned with a backdrop; closes
643 on backdrop click + Escape. Sections (top → bottom):
644 · Title (inline-editable, blurs to commit)
645 · Description (textarea, blurs to commit)
646 · Sidebar: labels picker, assignees picker, due date, archive
647 · Comments thread + composer
648 All mutations route through the store, which fires optimistic
649 updates with rollback on failure. -->
643 on backdrop click + Escape. Sections (top → bottom):
644 · Title (inline-editable, blurs to commit)
645 · Description (textarea, blurs to commit)
646 · Sidebar: labels picker, assignees picker, due date, archive
647 · Comments thread + composer
648 All mutations route through the store, which fires optimistic
649 updates with rollback on failure. -->
650650 <div
651651 :if="openCard()"
652 class="fixed inset-0 z-50 flex items-start justify-center bg-black/50 px-4 py-8 overflow-y-auto"
652 class="flex fixed inset-0 overflow-y-auto z-50 items-start justify-center px-4 py-8 bg-black/50"
653653 @click="closeDetail()"
654654 @keydown.escape.window="closeDetail()"
655655 >
656656 <div
657657 @click.stop
658 class="w-full max-w-3xl rounded-lg bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 shadow-xl my-8"
658 class="my-8 max-w-3xl w-full bg-white dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800 shadow-xl"
659659 >
660660 <!-- Header: title + close -->
661 <div class="px-6 py-4 border-b border-zinc-200 dark:border-zinc-800 flex items-start gap-3">
661 <div class="flex gap-3 items-start px-6 py-4 border-b border-zinc-200 dark:border-zinc-800">
662662 <input
663663 :value="titleDraft()"
664664 @input="(e) => titleDraft.set(e.target.value)"
665665 @blur="commitTitle()"
666666 @keydown.enter.prevent="(e) => e.target.blur()"
667 class="flex-1 text-lg font-semibold text-zinc-900 dark:text-zinc-100 bg-transparent focus:outline-none focus:bg-zinc-50 dark:focus:bg-zinc-800 rounded px-2 -mx-2 py-1 -my-1"
667 class="flex-1 -mx-2 -my-1 px-2 py-1 font-semibold text-lg text-zinc-900 dark:text-zinc-100 bg-transparent focus:bg-zinc-50 dark:focus:bg-zinc-800 rounded focus:outline-none"
668668 />
669669 <button
670670 @click="closeDetail()"
@@ -682,28 +682,28 @@
682682 </div>
683683
684684 <div :if="errorCard()" class="px-6 py-3 bg-red-50 dark:bg-red-950/30 border-b border-red-200 dark:border-red-900/40">
685 <p class="text-xs text-red-700 dark:text-red-300" :text="errorCard()"></p>
685 <p class="text-red-700 text-xs dark:text-red-300" :text="errorCard()"></p>
686686 </div>
687687
688 <div class="grid grid-cols-1 md:grid-cols-3 gap-6 px-6 py-5">
688 <div class="grid gap-6 grid-cols-1 md:grid-cols-3 px-6 py-5">
689689 <!-- Main column: description + comments -->
690690 <div class="md:col-span-2 space-y-6">
691691 <!-- Description -->
692692 <section>
693 <h4 class="text-xs font-medium uppercase tracking-wide text-zinc-500 mb-2">Description</h4>
693 <h4 class="mb-2 font-medium text-xs text-zinc-500 tracking-wide uppercase">Description</h4>
694694 <textarea
695695 :value="descDraft()"
696696 @input="(e) => descDraft.set(e.target.value)"
697697 @blur="commitDescription()"
698698 rows="4"
699699 placeholder="Add a more detailed description…"
700 class="w-full px-3 py-2 text-sm rounded border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 text-zinc-800 dark:text-zinc-200 resize-y focus:outline-none focus:border-violet-400 dark:focus:border-violet-600"
700 class="px-3 py-2 w-full text-sm text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-950 border border-zinc-200 rounded dark:border-zinc-800 focus:border-violet-400 focus:outline-none dark:focus:border-violet-600 resize-y"
701701 ></textarea>
702702 </section>
703703
704704 <!-- Comments -->
705705 <section>
706 <h4 class="text-xs font-medium uppercase tracking-wide text-zinc-500 mb-2">Activity</h4>
706 <h4 class="mb-2 font-medium text-xs text-zinc-500 tracking-wide uppercase">Activity</h4>
707707
708708 <!-- Composer -->
709709 <div class="mb-4">
@@ -713,13 +713,13 @@
713713 @keydown.metaCmd.enter.prevent="submitComment()"
714714 rows="2"
715715 placeholder="Add a comment… (⌘+Enter to send)"
716 class="w-full px-3 py-2 text-sm rounded border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 text-zinc-800 dark:text-zinc-200 resize-none focus:outline-none focus:border-violet-400 dark:focus:border-violet-600"
716 class="px-3 py-2 w-full text-sm text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-950 border border-zinc-200 rounded dark:border-zinc-800 focus:border-violet-400 focus:outline-none dark:focus:border-violet-600 resize-none"
717717 ></textarea>
718718 <div class="flex justify-end mt-2">
719719 <button
720720 @click="submitComment()"
721721 :disabled="!commentDraft().trim()"
722 class="px-3 py-1 text-xs rounded bg-violet-600 hover:bg-violet-700 text-white disabled:opacity-40 disabled:cursor-not-allowed"
722 class="px-3 py-1 text-white text-xs bg-violet-600 hover:bg-violet-700 rounded disabled:opacity-40 disabled:cursor-not-allowed"
723723 >
724724 Comment
725725 </button>
@@ -727,7 +727,7 @@
727727 </div>
728728
729729 <!-- Thread -->
730 <div :if="openCard().comments.length === 0 && !loadingCard()" class="text-xs text-zinc-400 italic">
730 <div :if="openCard().comments.length === 0 && !loadingCard()" class="italic text-xs text-zinc-400">
731731 No comments yet.
732732 </div>
733733 <ul class="space-y-3">
@@ -735,17 +735,17 @@
735735 :for="cm in openCard().comments"
736736 class="flex gap-3"
737737 >
738 <span class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-violet-500 text-white text-[10px] font-medium flex-shrink-0" :text="initialsOf(cm.authorName, cm.authorEmail)"></span>
738 <span class="inline-flex flex-shrink-0 items-center justify-center h-7 w-7 font-medium text-[10px] text-white bg-violet-500 rounded-full" :text="initialsOf(cm.authorName, cm.authorEmail)"></span>
739739 <div class="flex-1 min-w-0">
740 <div class="flex items-baseline gap-2">
741 <span class="text-xs font-medium text-zinc-700 dark:text-zinc-200" :text="cm.authorName || cm.authorEmail || 'Unknown'"></span>
740 <div class="flex gap-2 items-baseline">
741 <span class="font-medium text-xs text-zinc-700 dark:text-zinc-200" :text="cm.authorName || cm.authorEmail || 'Unknown'"></span>
742742 <span class="text-[10px] text-zinc-400" :text="formatTimestamp(cm.createdAt)"></span>
743743 <button
744744 @click="deleteCommentClicked(cm.id)"
745 class="text-[10px] text-zinc-400 hover:text-red-500 ml-auto"
745 class="ml-auto text-[10px] text-zinc-400 hover:text-red-500"
746746 >Delete</button>
747747 </div>
748 <p class="mt-0.5 text-xs text-zinc-700 dark:text-zinc-300 whitespace-pre-wrap" :text="cm.body"></p>
748 <p class="mt-0.5 text-xs text-zinc-700 whitespace-pre-wrap dark:text-zinc-300" :text="cm.body"></p>
749749 </div>
750750 </li>
751751 </ul>
@@ -757,10 +757,10 @@
757757 <!-- Labels -->
758758 <section>
759759 <div class="flex items-center justify-between mb-2">
760 <h4 class="text-xs font-medium uppercase tracking-wide text-zinc-500">Labels</h4>
760 <h4 class="font-medium text-xs text-zinc-500 tracking-wide uppercase">Labels</h4>
761761 <button
762762 @click="showLabelPicker.set(!showLabelPicker())"
763 class="text-xs text-violet-600 dark:text-violet-400 hover:underline"
763 class="text-violet-600 text-xs dark:text-violet-400 hover:underline"
764764 >
765765 <span :if="!showLabelPicker()">Edit</span>
766766 <span :if="showLabelPicker()">Done</span>
@@ -774,7 +774,7 @@
774774 :class="`px-2 py-0.5 text-xs font-medium rounded ${labelChipClass(lbl.color)}`"
775775 :text="lbl.name"
776776 ></span>
777 <span :if="openCard().labels.length === 0" class="text-xs text-zinc-400 italic">None</span>
777 <span :if="openCard().labels.length === 0" class="italic text-xs text-zinc-400">None</span>
778778 </div>
779779
780780 <!-- Picker -->
@@ -785,7 +785,7 @@
785785 :class="`w-full flex items-center justify-between px-2 py-1 rounded text-xs ${labelChipClass(lbl.color)} hover:opacity-90`"
786786 >
787787 <span :text="lbl.name"></span>
788 <span class="flex items-center gap-1">
788 <span class="flex gap-1 items-center">
789789 <svg :if="openCard().labels.some(l => l.id === lbl.id)" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
790790 <polyline points="20 6 9 17 4 12" />
791791 </svg>
@@ -800,15 +800,15 @@
800800 </button>
801801
802802 <!-- New label composer -->
803 <div :if="showNewLabelComposer()" class="space-y-2 pt-2 border-t border-zinc-200 dark:border-zinc-800">
803 <div :if="showNewLabelComposer()" class="pt-2 space-y-2 border-t border-zinc-200 dark:border-zinc-800">
804804 <input
805805 :value="newLabelName()"
806806 @input="(e) => newLabelName.set(e.target.value)"
807807 @keydown.enter.prevent="submitNewLabel()"
808808 placeholder="Label name"
809 class="w-full px-2 py-1 text-xs rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-950 focus:outline-none focus:border-violet-500"
809 class="px-2 py-1 w-full text-xs bg-white dark:bg-zinc-950 border border-zinc-300 rounded dark:border-zinc-700 focus:border-violet-500 focus:outline-none"
810810 />
811 <div class="flex items-center gap-1">
811 <div class="flex gap-1 items-center">
812812 <button
813813 :for="c in labelColors"
814814 @click="newLabelColor.set(c)"
@@ -817,14 +817,14 @@
817817 ></button>
818818 </div>
819819 <div class="flex gap-1">
820 <button @click="submitNewLabel()" class="px-2 py-0.5 text-xs rounded bg-violet-600 hover:bg-violet-700 text-white">Create</button>
820 <button @click="submitNewLabel()" class="px-2 py-0.5 text-white text-xs bg-violet-600 hover:bg-violet-700 rounded">Create</button>
821821 <button @click="showNewLabelComposer.set(false)" class="px-2 py-0.5 text-xs text-zinc-500 hover:text-zinc-700">Cancel</button>
822822 </div>
823823 </div>
824824 <button
825825 :if="!showNewLabelComposer()"
826826 @click="showNewLabelComposer.set(true)"
827 class="w-full px-2 py-1 text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded border border-dashed border-zinc-300 dark:border-zinc-700"
827 class="px-2 py-1 w-full text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 border border-dashed border-zinc-300 rounded dark:border-zinc-700"
828828 >
829829 + New label
830830 </button>
@@ -834,10 +834,10 @@
834834 <!-- Assignees -->
835835 <section>
836836 <div class="flex items-center justify-between mb-2">
837 <h4 class="text-xs font-medium uppercase tracking-wide text-zinc-500">Assignees</h4>
837 <h4 class="font-medium text-xs text-zinc-500 tracking-wide uppercase">Assignees</h4>
838838 <button
839839 @click="showAssigneePicker.set(!showAssigneePicker())"
840 class="text-xs text-violet-600 dark:text-violet-400 hover:underline"
840 class="text-violet-600 text-xs dark:text-violet-400 hover:underline"
841841 >
842842 <span :if="!showAssigneePicker()">Edit</span>
843843 <span :if="showAssigneePicker()">Done</span>
@@ -847,23 +847,23 @@
847847 <div :if="!showAssigneePicker()" class="flex flex-wrap gap-2">
848848 <div
849849 :for="asg in openCard().assignees"
850 class="flex items-center gap-1.5 text-xs text-zinc-700 dark:text-zinc-200"
850 class="flex gap-1.5 items-center text-xs text-zinc-700 dark:text-zinc-200"
851851 >
852 <span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-violet-500 text-white text-[10px] font-medium" :text="initialsOf(asg.name, asg.email)"></span>
852 <span class="inline-flex items-center justify-center h-6 w-6 font-medium text-[10px] text-white bg-violet-500 rounded-full" :text="initialsOf(asg.name, asg.email)"></span>
853853 <span :text="asg.name || asg.email || 'Unknown'"></span>
854854 </div>
855 <span :if="openCard().assignees.length === 0" class="text-xs text-zinc-400 italic">Unassigned</span>
855 <span :if="openCard().assignees.length === 0" class="italic text-xs text-zinc-400">Unassigned</span>
856856 </div>
857857
858 <div :if="showAssigneePicker()" class="space-y-1 max-h-48 overflow-y-auto">
859 <p :if="kanban.users().length === 0" class="text-xs text-zinc-400 italic">No users to assign.</p>
858 <div :if="showAssigneePicker()" class="overflow-y-auto space-y-1 max-h-48">
859 <p :if="kanban.users().length === 0" class="italic text-xs text-zinc-400">No users to assign.</p>
860860 <button
861861 :for="u in kanban.users()"
862862 @click="toggleAssignee(u.id)"
863 class="w-full flex items-center gap-2 px-2 py-1.5 rounded text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800 text-left"
863 class="flex gap-2 items-center px-2 py-1.5 w-full text-left text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded"
864864 >
865 <span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-violet-500 text-white text-[10px] font-medium flex-shrink-0" :text="initialsOf(u.name, u.email)"></span>
866 <span class="flex-1 truncate text-zinc-700 dark:text-zinc-200" :text="u.name || u.email"></span>
865 <span class="inline-flex flex-shrink-0 items-center justify-center h-6 w-6 font-medium text-[10px] text-white bg-violet-500 rounded-full" :text="initialsOf(u.name, u.email)"></span>
866 <span class="flex-1 text-zinc-700 truncate dark:text-zinc-200" :text="u.name || u.email"></span>
867867 <svg :if="openCard().assignees.some(a => a.userId === u.id)" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="text-violet-500">
868868 <polyline points="20 6 9 17 4 12" />
869869 </svg>
@@ -873,12 +873,12 @@
873873
874874 <!-- Due date -->
875875 <section>
876 <h4 class="text-xs font-medium uppercase tracking-wide text-zinc-500 mb-2">Due date</h4>
876 <h4 class="mb-2 font-medium text-xs text-zinc-500 tracking-wide uppercase">Due date</h4>
877877 <input
878878 type="date"
879879 :value="openCard().dueDate ? openCard().dueDate.slice(0, 10) : ''"
880880 @change="(e) => commitDueDate(e.target.value)"
881 class="w-full px-2 py-1 text-xs rounded border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 text-zinc-800 dark:text-zinc-200 focus:outline-none focus:border-violet-400"
881 class="px-2 py-1 w-full text-xs text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-950 border border-zinc-200 rounded dark:border-zinc-800 focus:border-violet-400 focus:outline-none"
882882 />
883883 </section>
884884
storage/framework/defaults/views/dashboard/kanban/index.stxmodified+30-30
Changes to storage/framework/defaults/views/dashboard/kanban/index.stx
@@ -111,9 +111,9 @@
111111
112112<div class="mx-auto px-6 py-8 max-w-7xl">
113113 <!-- Role gate. The full RBAC check via useRole(); unauthenticated
114 dev-dashboard viewers see the surface as if they were a dev per
115 the permissive default. -->
116 <div :if="!canView" class="rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 px-5 py-8 text-center">
114 dev-dashboard viewers see the surface as if they were a dev per
115 the permissive default. -->
116 <div :if="!canView" class="px-5 py-8 text-center bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800">
117117 <p class="text-sm text-zinc-600 dark:text-zinc-400">Kanban boards require the dev role.</p>
118118 </div>
119119
@@ -125,7 +125,7 @@
125125 </div>
126126 <button
127127 @click="openCreate()"
128 class="px-3 py-1.5 text-sm rounded-md bg-violet-600 hover:bg-violet-700 text-white"
128 class="px-3 py-1.5 text-sm text-white bg-violet-600 hover:bg-violet-700 rounded-md"
129129 >
130130 New board
131131 </button>
@@ -139,20 +139,20 @@
139139 </div>
140140
141141 <!-- Error -->
142 <div :if="error && !loading" class="rounded-lg border border-red-200 dark:border-red-900/40 bg-red-50 dark:bg-red-950/30 px-5 py-4">
143 <p class="text-sm font-medium text-red-700 dark:text-red-300">Couldn't load boards</p>
144 <p class="mt-1 text-xs text-red-600 dark:text-red-400" :text="error"></p>
142 <div :if="error && !loading" class="px-5 py-4 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900/40">
143 <p class="font-medium text-red-700 text-sm dark:text-red-300">Couldn't load boards</p>
144 <p class="mt-1 text-red-600 text-xs dark:text-red-400" :text="error"></p>
145145 <p class="mt-2 text-xs text-zinc-500">Most common: migrations haven't run yet. Try <code>./buddy migrate</code>.</p>
146146 </div>
147147
148148 <!-- Empty state — DB reachable but no boards yet -->
149 <div :if="!loading && !error && !hasBoards" class="rounded-lg border border-dashed border-zinc-300 dark:border-zinc-700 px-6 py-12 text-center">
150 <svg class="mx-auto text-zinc-400 mb-3" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
149 <div :if="!loading && !error && !hasBoards" class="px-6 py-12 text-center border border-dashed border-zinc-300 rounded-lg dark:border-zinc-700">
150 <svg class="mb-3 mx-auto text-zinc-400" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
151151 <rect x="3" y="4" width="6" height="16" rx="1" />
152152 <rect x="11" y="4" width="6" height="10" rx="1" />
153153 <rect x="19" y="4" width="2" height="6" rx="1" />
154154 </svg>
155 <p class="text-sm font-medium text-zinc-700 dark:text-zinc-300">No boards yet</p>
155 <p class="font-medium text-sm text-zinc-700 dark:text-zinc-300">No boards yet</p>
156156 <p class="mt-1 text-xs text-zinc-500">Click "New board" to create one.</p>
157157 </div>
158158
@@ -161,17 +161,17 @@
161161 <a
162162 :for="board in boards"
163163 :href="`/kanban/${board.id}`"
164 class="block rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 hover:border-zinc-300 dark:hover:border-zinc-700 transition-colors overflow-hidden no-underline group"
164 class="block overflow-hidden no-underline bg-white dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700 transition-colors group"
165165 data-mpa="true"
166166 >
167167 <div :class="`h-2 ${accentClass(board.color)}`"></div>
168168 <div class="px-4 py-3">
169 <div class="flex items-start gap-2">
170 <h2 class="flex-1 font-medium text-sm text-zinc-900 dark:text-zinc-100 group-hover:text-violet-600 dark:group-hover:text-violet-400 truncate" :text="board.name"></h2>
169 <div class="flex gap-2 items-start">
170 <h2 class="flex-1 font-medium text-sm text-zinc-900 truncate dark:text-zinc-100 group-hover:text-violet-600 dark:group-hover:text-violet-400" :text="board.name"></h2>
171171 </div>
172172 <p
173173 :if="board.description"
174 class="mt-1 text-xs text-zinc-500 line-clamp-2"
174 class="mt-1 line-clamp-2 text-xs text-zinc-500"
175175 :text="board.description"
176176 ></p>
177177 <p class="mt-3 text-xs text-zinc-400" :text="cardCountText(board.cardCount)"></p>
@@ -181,49 +181,49 @@
181181 </div>
182182
183183 <!-- Create-board modal. Fixed positioned with a backdrop; closes on
184 backdrop click + Escape key. Optimistic insert: kanban.createBoard
185 updates the boards list immediately on success and rolls back on
186 failure (with the error surfaced inline below the form). -->
184 backdrop click + Escape key. Optimistic insert: kanban.createBoard
185 updates the boards list immediately on success and rolls back on
186 failure (with the error surfaced inline below the form). -->
187187 <div
188188 :if="isCreating()"
189 class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4"
189 class="flex fixed inset-0 z-50 items-center justify-center px-4 bg-black/50"
190190 @click="closeCreate()"
191191 @keydown.escape.window="closeCreate()"
192192 >
193193 <div
194194 @click.stop
195 class="w-full max-w-md rounded-lg bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 shadow-xl"
195 class="max-w-md w-full bg-white dark:bg-zinc-900 border border-zinc-200 rounded-lg dark:border-zinc-800 shadow-xl"
196196 >
197197 <div class="px-5 py-4 border-b border-zinc-200 dark:border-zinc-800">
198 <h3 class="text-base font-semibold text-zinc-900 dark:text-zinc-100">New board</h3>
198 <h3 class="font-semibold text-base text-zinc-900 dark:text-zinc-100">New board</h3>
199199 </div>
200200 <div class="px-5 py-4 space-y-4">
201201 <div>
202 <label class="block text-xs font-medium text-zinc-700 dark:text-zinc-300 mb-1">Name</label>
202 <label class="block mb-1 font-medium text-xs text-zinc-700 dark:text-zinc-300">Name</label>
203203 <input
204204 :value="draftName()"
205205 @input="(e) => draftName.set(e.target.value)"
206206 @keydown.enter="submitCreate()"
207207 placeholder="e.g. Q3 Roadmap"
208 class="w-full px-3 py-2 text-sm rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-950 text-zinc-900 dark:text-zinc-100 focus:outline-none focus:border-violet-500"
208 class="px-3 py-2 w-full text-sm text-zinc-900 dark:text-zinc-100 bg-white dark:bg-zinc-950 border border-zinc-300 rounded dark:border-zinc-700 focus:border-violet-500 focus:outline-none"
209209 autofocus
210210 />
211211 </div>
212212
213213 <div>
214 <label class="block text-xs font-medium text-zinc-700 dark:text-zinc-300 mb-1">Description <span class="text-zinc-400">(optional)</span></label>
214 <label class="block mb-1 font-medium text-xs text-zinc-700 dark:text-zinc-300">Description <span class="text-zinc-400">(optional)</span></label>
215215 <textarea
216216 :value="draftDescription()"
217217 @input="(e) => draftDescription.set(e.target.value)"
218218 rows="3"
219219 placeholder="A short description of what this board tracks."
220 class="w-full px-3 py-2 text-sm rounded border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-950 text-zinc-900 dark:text-zinc-100 resize-none focus:outline-none focus:border-violet-500"
220 class="px-3 py-2 w-full text-sm text-zinc-900 dark:text-zinc-100 bg-white dark:bg-zinc-950 border border-zinc-300 rounded dark:border-zinc-700 focus:border-violet-500 focus:outline-none resize-none"
221221 ></textarea>
222222 </div>
223223
224224 <div>
225 <label class="block text-xs font-medium text-zinc-700 dark:text-zinc-300 mb-2">Accent color</label>
226 <div class="flex items-center gap-2">
225 <label class="block mb-2 font-medium text-xs text-zinc-700 dark:text-zinc-300">Accent color</label>
226 <div class="flex gap-2 items-center">
227227 <button
228228 :for="c in colorChoices"
229229 @click="draftColor.set(c)"
@@ -234,20 +234,20 @@
234234 </div>
235235 </div>
236236
237 <p :if="submitError()" class="text-xs text-red-600 dark:text-red-400" :text="submitError()"></p>
237 <p :if="submitError()" class="text-red-600 text-xs dark:text-red-400" :text="submitError()"></p>
238238 </div>
239239
240 <div class="px-5 py-3 border-t border-zinc-200 dark:border-zinc-800 flex justify-end gap-2">
240 <div class="flex gap-2 justify-end px-5 py-3 border-t border-zinc-200 dark:border-zinc-800">
241241 <button
242242 @click="closeCreate()"
243 class="px-3 py-1.5 text-sm rounded-md text-zinc-600 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
243 class="px-3 py-1.5 text-sm text-zinc-600 dark:text-zinc-400 hover:text-zinc-800 dark:hover:text-zinc-200 rounded-md"
244244 :disabled="submitting()"
245245 >
246246 Cancel
247247 </button>
248248 <button
249249 @click="submitCreate()"
250 class="px-3 py-1.5 text-sm rounded-md bg-violet-600 hover:bg-violet-700 text-white"
250 class="px-3 py-1.5 text-sm text-white bg-violet-600 hover:bg-violet-700 rounded-md"
251251 :disabled="submitting()"
252252 >
253253 <span :if="!submitting()">Create</span>