Compare commits

..
Author SHA1 Message Date
6a0bb623fc
Restyle the Micropub tokens admin table
Breaks the table out of the centred content column to full page
width via a subgrid on <main> (adds a reusable .full-bleed utility),
and reworks the table itself to match a Claude Design mockup: a
rounded card wrapper, scope values as pill chips, a dotted status
pill, and proportional column widths. Colours use the site's
existing --primary-hue/light-dark() tokens so it stays correct in
both themes. The card scrolls horizontally on narrow viewports
instead of clipping content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHN7V9kyZxrsxaspMSqoGJ
2026-08-21 16:05:48 +01:00
30ec2ec0e8
Initial work on better admin page styling 2026-08-21 15:36:18 +01:00
8 changed files with 195 additions and 19 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -0,0 +1,137 @@
@layer components {
.token-list-wrapper {
margin-block-start: 1em;
border: 1px solid var(--clr-border);
border-radius: 16px;
overflow: auto hidden;
background: light-dark(
oklch(99% 0.02 var(--primary-hue)),
oklch(22% 0.05 var(--primary-hue))
);
}
.token-list {
width: 100%;
min-width: 640px;
table-layout: fixed;
border-collapse: collapse;
th,
td {
text-align: left;
padding: 0.9em 1.2em;
border-bottom: 1px solid var(--clr-border);
vertical-align: middle;
}
th {
font-size: 0.8em;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
opacity: 0.7;
}
tr.is-revoked {
opacity: 0.6;
}
a {
word-break: break-all;
}
th:nth-child(1),
td:nth-child(1) {
width: 31%;
}
th:nth-child(2),
td:nth-child(2) {
width: 24%;
}
th:nth-child(3),
td:nth-child(3) {
width: 15%;
}
th:nth-child(4),
td:nth-child(4) {
width: 16%;
}
th:nth-child(5),
td:nth-child(5) {
width: 14%;
text-align: right;
}
tr:last-child td {
border-bottom: none;
}
}
.scope-chips {
display: flex;
flex-wrap: wrap;
gap: 0.4em;
}
.scope-chip {
display: inline-block;
padding: 0.2em 0.7em;
border-radius: 999px;
background: light-dark(
oklch(92% 0.05 var(--primary-hue)),
oklch(35% 0.08 var(--primary-hue))
);
font-size: 0.85em;
white-space: nowrap;
}
.badge,
.token-list button.revoke {
display: inline-flex;
align-items: center;
gap: 0.4em;
padding: 0.3em 0.8em;
border-radius: 999px;
font-size: 0.85em;
font-weight: 600;
line-height: 1.5;
white-space: nowrap;
}
.badge {
background: transparent;
border: 1px solid currentcolor;
}
.badge-active {
color: light-dark(oklch(35% 0.16 145deg), oklch(75% 0.16 145deg));
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentcolor;
}
}
.badge-revoked {
color: var(--clr-text);
opacity: 0.7;
}
.token-list button.revoke {
background: light-dark(oklch(92% 0.15 25deg), oklch(30% 0.12 25deg));
color: light-dark(oklch(30% 0.15 25deg), oklch(92% 0.12 25deg));
border: 1px solid transparent;
cursor: pointer;
transition: background-color 150ms ease;
}
.token-list button.revoke:hover {
background: light-dark(oklch(80% 0.2 25deg), oklch(45% 0.18 25deg));
}
}

View file

@ -8,3 +8,4 @@
@import url('notes.css'); @import url('notes.css');
@import url('pagination.css'); @import url('pagination.css');
@import url('theme-selector.css'); @import url('theme-selector.css');
@import url('admin-tokens.css');

View file

@ -18,9 +18,19 @@
} }
> main { > main {
grid-column: 1/-1;
display: grid;
grid-template-columns: subgrid;
> * {
grid-column: 2/3; grid-column: 2/3;
} }
> .full-bleed {
grid-column: 1/-1;
}
}
> footer { > footer {
grid-column: 1/-1; grid-column: 1/-1;
margin-block-start: 2ex; margin-block-start: 2ex;

View file

@ -7,21 +7,49 @@
@if($tokens->isEmpty()) @if($tokens->isEmpty())
<p>No tokens have been issued.</p> <p>No tokens have been issued.</p>
@else @else
<ul> <div class="full-bleed token-list-wrapper">
<table class="token-list">
<thead>
<tr>
<th scope="col">Client</th>
<th scope="col">Scope</th>
<th scope="col">Issued</th>
<th scope="col">Status</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
@foreach($tokens as $token) @foreach($tokens as $token)
<li> <tr class="{{ $token->isRevoked ? 'is-revoked' : '' }}">
{{ $token->client_id }} scope: {{ $token->scope }} issued {{ $token->created_at->diffForHumans() }} <td><a href="{{ $token->client_id }}" rel="noopener">{{ $token->client_id }}</a></td>
<td>
<span class="scope-chips">
@foreach(explode(' ', $token->scope) as $scope)
<span class="scope-chip">{{ $scope }}</span>
@endforeach
</span>
</td>
<td><time datetime="{{ $token->created_at->toIso8601String() }}" title="{{ $token->created_at }}">{{ $token->created_at->diffForHumans() }}</time></td>
<td>
@if($token->isRevoked) @if($token->isRevoked)
revoked {{ $token->revoked_at->diffForHumans() }} <span class="badge badge-revoked">Revoked {{ $token->revoked_at->diffForHumans() }}</span>
@else @else
<form action="/admin/tokens/{{ $token->id }}/revoke" method="post"> <span class="badge badge-active"><span class="dot"></span>Active</span>
@endif
</td>
<td>
@unless($token->isRevoked)
<form action="/admin/tokens/{{ $token->id }}/revoke" method="post" onsubmit="return confirm('Revoke this token? This cannot be undone.')">
{{ csrf_field() }} {{ csrf_field() }}
{{ method_field('PUT') }} {{ method_field('PUT') }}
<button type="submit" name="revoke">Revoke</button> <button type="submit" class="revoke" name="revoke">Revoke</button>
</form> </form>
@endif @endunless
</li> </td>
</tr>
@endforeach @endforeach
</ul> </tbody>
</table>
</div>
@endif @endif
@stop @stop