Repository navigation
feat: admin frontend for the OpenTaberna store - #2
Merged
Merged
Conversation
Angular 22 back-office for administering what customers see: catalogue,
stock, orders and returns. Sidenav shell, Tailwind v4, one global design
file, reusable components.
Structure
core/ singletons - Keycloak session, HTTP interceptor, admin guard,
one typed client per API domain, models mirroring the schemas
shared/ presentational components that never call the API, so they can
be reused on a screen that already holds the data
layout/ the frame: shell, sidenav, topbar
features/ one lazy-loaded folder per screen
src/styles.css is the single source of truth for appearance: Tailwind
@theme tokens plus a small set of semantic classes for patterns repeated
across screens. Status colour is assigned by meaning rather than by
feature, so danger reads the same on an order, a stock level and a
return.
Auth
Authorization-code + PKCE through the opentaberna-admin-ui client, run by
an APP_INITIALIZER so the guard never evaluates an empty session. The
interceptor attaches the token only to requests aimed at the configured
API host, never to a third party. The guard is for usability, not
security - the API enforces access itself, and the guard exists so a
signed-in customer gets an explanation instead of failing requests.
Two bugs found by driving the running app
The dashboard requested 200 catalogue items where the endpoint caps at
100, producing a 422 and a blank panel. The service now clamps, so no
caller can trip it again.
The product form offered an "inactive" status the API does not have - its
enum is draft, active, archived - so saving anything but active failed.
Corrected, and the copy now says which states are hidden from customers.
Verified against the live stack: the Keycloak round trip completes, all
five routes render with real data, no console errors, no failed API
calls, the sidenav collapses to a drawer below lg, a duplicate SKU shows
the API's message rather than failing silently, and a customer account
lands on the "not an administrator" screen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Feature
The back-office application: catalogue, stock, orders and returns, behind a sidenav.
Closes #1.
Heads-up on the storefront
The intent was to mirror
OpenTaberna/frontend. That repository currently holds onecommit with a one-line README — there is no Angular code to clone, run or mirror. So the
structure is defined here and documented in the README, and the storefront can follow it.
Worth telling @maltonoloco before he starts, so the two do not diverge.
Structure
Four rules keep it honest:
core/is injected and never imported into a template;shared/ui/never calls the API, so a component can be reused on a screen that alreadyholds the data;
features/reach the API only through acore/apiclient; and no designliterals in templates — if a token is missing, it goes in
styles.css.The global design file
src/styles.cssholds Tailwind v4@themetokens (colour, radius, shadow, type) plus asmall set of semantic classes —
.card,.field-control,.data-table.Semantic classes are deliberately few. Something used once stays a utility in the template;
a class earns its place only when copying the utility string around would let screens
drift. Status colour is assigned by meaning rather than by feature, so
dangerreadsthe same on an order, a stock level and a return — the palette is learned once.
Reusable components
ot-button,ot-badge,ot-card,ot-modal,ot-page-header,ot-empty-state,ot-spinner,ot-alert, and amoneypipe. Standalone and imported per feature, so alazy chunk pulls in only what it renders.
ot-empty-stateexists because "no data" and "still loading" otherwise look identical —the most common way an admin screen confuses the person using it.
moneyexists becausethe API stores minor units, and dividing by 100 in each template eventually gets forgotten.
Auth
Authorization-code + PKCE via
opentaberna-admin-ui, completed by anAPP_INITIALIZERsothe guard never evaluates an empty session and bounces a legitimate admin. The interceptor
attaches the token only to requests aimed at the configured API host, never to a third
party.
adminGuardis a usability guard, not a security boundary — the API enforces accessitself. It exists so a signed-in customer sees an explanation instead of a screen of
failing requests.
Two bugs found by driving the running app
and a blank panel. The service now clamps, so no caller can trip it again — with a test.
inactivestatus the API does not have. Its enum isdraft | active | archived, so saving anything but active failed. Corrected, and thecopy now says which states are hidden from customers.
Neither was visible from a build. Both came out of clicking through the real thing.
Verification
Driven headlessly against the live stack (API, Keycloak, Postgres, Redis, MinIO):
Also confirmed:
token attachment and list refresh.
Item with sku='TAB-RED-001' already exists) rather than failing silently.testuser) lands on/forbiddenwith "This account is not anadministrator".
CI runs all three on every PR.
Not included
should follow once the API exposes it.
yet surfaced.
environment.prod.tscarries placeholder hosts, to be set at deploy time.