All checks were successful
check / check (push) Successful in 3m35s
Terminology: the nav labelled its link Sources while every heading said
Webhooks. Both nav links and the sources list page title now say
Webhooks, matching the product name and the database.Webhook model. The
/sources and /source/{id} routes are deliberately unchanged; renaming
them would break existing bookmarks for no gain, since the URL is not
what a user reads.
Profile: the "Settings" column held only placeholder copy promising
settings that would appear later. Password change is the only real
account setting and it already has its own card below, so the column is
removed and the two-column grid collapses to the single remaining one.
Retention copy on the create and edit forms now describes what the code
does rather than what the field implies. The reaper permanently deletes
events past the cutoff along with their deliveries and delivery
results, so the hint says so instead of "how long to keep event data".
Both forms state that an empty field is not a way to ask for forever:
the create path falls back to DefaultRetentionDays and the edit path
leaves the stored policy alone, while 0 is what BeforeSave rewrites to
the retain-forever sentinel.
Entrypoint URL copy button as progressive enhancement. The button is
rendered with the hidden attribute and a data-copy-target naming the
element that holds the URL. app.js reveals it only after confirming
both a resolvable target and a usable Clipboard API, so a browser
without either shows no dead control, and the URL is plain selectable
text in every case. The script uses const throughout, per the JS
styleguide the repo policies bind this repo to; nothing in the repo's
checks covers JavaScript, so that is enforced by reading rather than by
the gate.
Template rendering assertions cover the nav labels, the absence of any
remaining user-visible "Sources", the button's hidden-by-default
markup, and both forms' retention copy including the forever label, so
the copy cannot drift back silently. The edit-page assertions pass the
webhook as a pointer because RetentionLabel is a pointer method and a
map element is not addressable.
101 lines
3.5 KiB
HTML
101 lines
3.5 KiB
HTML
{{template "base" .}}
|
|
|
|
{{define "title"}}Profile - Webhooker{{end}}
|
|
|
|
{{define "content"}}
|
|
<div class="max-w-4xl mx-auto px-6 py-12">
|
|
<h1 class="text-2xl font-medium text-gray-900 mb-6">User Profile</h1>
|
|
|
|
{{if .SuccessMessage}}
|
|
<div class="alert-success">
|
|
<span>{{.SuccessMessage}}</span>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .ErrorMessage}}
|
|
<div class="alert-error">
|
|
<span>{{.ErrorMessage}}</span>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="card p-6">
|
|
<div class="flex items-center mb-6">
|
|
<div class="mr-4">
|
|
<svg class="w-16 h-16 text-primary-500" fill="currentColor" viewBox="0 0 16 16">
|
|
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
|
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xl font-medium text-gray-900">{{.User.Username}}</h2>
|
|
<p class="text-sm text-gray-500">User ID: {{.User.ID}}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="border-gray-200 mb-6">
|
|
|
|
<div>
|
|
<h3 class="text-lg font-medium text-gray-900 mb-3">Account Information</h3>
|
|
<dl class="space-y-3">
|
|
<div class="flex">
|
|
<dt class="w-32 text-sm font-medium text-gray-500">Username</dt>
|
|
<dd class="text-sm text-gray-900">{{.User.Username}}</dd>
|
|
</div>
|
|
<div class="flex">
|
|
<dt class="w-32 text-sm font-medium text-gray-500">Account Type</dt>
|
|
<dd class="text-sm text-gray-900">Standard User</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-6 mt-6">
|
|
<h3 class="text-lg font-medium text-gray-900 mb-3">Change Password</h3>
|
|
<form method="POST" action="/user/{{.User.Username}}/password" class="space-y-6">
|
|
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
|
<div class="form-group">
|
|
<label for="current_password" class="label">Current Password</label>
|
|
<input
|
|
type="password"
|
|
id="current_password"
|
|
name="current_password"
|
|
required
|
|
autocomplete="current-password"
|
|
class="input"
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="new_password" class="label">New Password</label>
|
|
<input
|
|
type="password"
|
|
id="new_password"
|
|
name="new_password"
|
|
required
|
|
autocomplete="new-password"
|
|
class="input"
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="confirm_password" class="label">Confirm New Password</label>
|
|
<input
|
|
type="password"
|
|
id="confirm_password"
|
|
name="confirm_password"
|
|
required
|
|
autocomplete="new-password"
|
|
class="input"
|
|
>
|
|
</div>
|
|
|
|
<button type="submit" class="btn-primary">Change Password</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="mt-6">
|
|
<a href="/" class="btn-secondary">Back to Home</a>
|
|
</div>
|
|
</div>
|
|
{{end}}
|