Files
inventory-plus-plus/templates/layout.html.tmpl
T
angelandClaude Sonnet 5 ede7555d43
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
Tests / Go tests (push) Successful in 13s
auth: split dev-mode auth constructor and wire up dev-login/logout UI
Squeamish about New()'s empty-domain-string sentinel for "dev mode, skip
OIDC discovery" - split into New (always makes a real OIDC discovery
call, all params required) and NewDev (no ctx/domain/credentials at all,
since none are used). main.go now branches on cfg.DevAuthEnabled to pick
the right constructor instead of main.go/config.go coordinating on when
it's safe to pass empty strings.

Also finishes out the dev-auth flow this enables: config.Load reads a
DEV_AUTH_ENABLED-aware env file and only requires Auth0 vars when dev
auth is off; a PORT config var replaces the hardcoded :8082; and the nav
UI (layout/index templates, ui router) points login/logout links at
/api/auth/dev-login and a new /api/auth/dev-logout route when dev auth
is enabled, so the whole login/logout loop works locally without a real
Auth0 app.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 00:36:55 -06:00

357 lines
9.1 KiB
Cheetah

{{- $loggedIn := and (and .Identity .Identity.AccessToken) true -}}
{{- $hasAccount := and (and .Identity .Identity.Account) true -}}
{{- $acctID := 0 }}
{{- if $hasAccount }}
{{- $acctID = .Identity.Account.AccountID }}
{{- end }}
{{- $mockMode := .MockMode }}
{{- $devAuthEnabled := .DevAuthEnabled }}
<!DOCTYPE html>
<html class="min-h-full flex flex-col items-stretch">
<head>
<title>
{{ block "title" . }} Inventory++ {{ end }}
</title>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<link rel="stylesheet" href="/styles/index.css">
<script src="/scripts/htmx.min.js.gz"></script>
<script src="/scripts/_hyperscript.min.js.gz"></script>
<script src="/scripts/htmx-ext-sse.js"></script>
<script src="/scripts/htmx-ext-path-params.js"></script>
<script src="/scripts/idiomorph-ext.js"></script>
<script src="/scripts/hx-drag.js"></script>
<script src="/scripts/hx-resize.js"></script>
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/favicon/site.webmanifest">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Alexandria:wght@100..900&family=Geist:wght@100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
{{/* This can be set by the template in ./pages being rendered */}}
{{- block "head" . }}{{ end }}
</head>
<body
class="basis-full grow bg-background flex flex-col items-stretch"
hx-ext="morph,path-params,sse,drag"
{{/* get requests to just replace the body - until we start replacing the head, this will be good */}}
hx-boost="true"
{{/* subscribe to sse (if logged in) */}}
{{- if and $loggedIn $hasAccount }}
sse-connect="/api/events/"
sse-close="close"
{{- end }}
>
<header>
{{- $path := or .Request.URL.Path "/" }}
{{ define "navbar-class" }}class="
w-full
text-xl
overflow-x-hidden
overflow-y-hidden
flex
justify-center
{{ if .MockMode }}
border-[2px]
border-[gray]
{{ end }}
"{{ end }}
{{ define "navbar-ul-class" }}class="
max-w-full
overflow-x-auto
overflow-y-hidden
text-nowrap
flex
basis-[fit-content]
"}}
{{ end }}
{{ define "navbar-link" }}
{{/* Href, NoHXBoost, Selected, Content */}}
<li class="pt-[1em] pb-[1em]">
<a
href="{{.Href}}"
{{if .NoHXBoost}}hx-boost="false"{{end}}
class="p-[1em] font-display {{if .Selected}}selected{{ end }}"
>
{{.Content}}
</a>
</li>
{{ end }}
{{/* navbar updater */}}
{{- if $hasAccount }}
<div
hx-trigger="sse:accounts_{{$acctID}}_mock-mode"
hx-get="/ui{{$path}}"
hx-select="#header-navbar"
hx-target="#header-navbar"
hx-swap="outerHTML"
hidden
>
</div>
{{- end }}
<nav
{{ template "navbar-class" . }}
id="header-navbar"
>
<ul
hx-swap="morph:innerHTML"
{{ template "navbar-ul-class" . }}
>
{{- if not $loggedIn }}
{{- $loginPath := "/api/auth/login" }}
{{- if $devAuthEnabled }}
{{- $loginPath = "/api/auth/dev-login" }}
{{- end }}
{{ template "navbar-link" (props
"Href" $loginPath
"NoHXBoost" true
"Selected" (eq $path "/auth/login")
"Content" "Log In"
)}}
{{- else if $hasAccount }}
{{ template "navbar-link" (props
"Href" (printf "/ui/accounts/%d" $acctID)
"Selected" (eq $path (printf "/accounts/%d" $acctID))
"Content" "Account"
)}}
{{- $shopUIPath := printf "/accounts/%d/shops" $acctID }}
{{- if $mockMode }}
{{- $shopUIPath = printf "%s/mock" $shopUIPath }}
{{- end }}
{{ template "navbar-link" (props
"Href" (printf "/ui%s" $shopUIPath)
"Selected" (hasPrefix $path $shopUIPath)
"Content" "Shops"
)}}
{{- $inventoryUIPath := printf "/accounts/%d/inventory" $acctID }}
{{- if $mockMode }}
{{- $inventoryUIPath = printf "%s/mock" $inventoryUIPath }}
{{- end }}
{{ template "navbar-link" (props
"Href" (printf "/ui%s" $inventoryUIPath)
"Selected" (hasPrefix $path $inventoryUIPath)
"Content" "Inventory"
)}}
{{- if $mockMode }}
{{- $simulationsUIPath := printf "/accounts/%d/simulations" $acctID }}
{{ template "navbar-link" (props
"Href" (printf "/ui%s" $simulationsUIPath)
"Selected" (hasPrefix $path $simulationsUIPath)
"Content" "Simulations"
)}}
{{- end }}
{{- $reportsUIPath := printf "/accounts/%d/reports" $acctID }}
{{- if $mockMode }}
{{- $reportsUIPath = printf "%s/mock" $reportsUIPath }}
{{- end }}
{{ template "navbar-link" (props
"Href" (printf "/ui%s" $reportsUIPath)
"Selected" (hasPrefix $path $reportsUIPath)
"Content" "Reports"
)}}
{{- end }}
</ul>
</nav>
{{/* TODO: delete this AFTER we're sure we don't need this anymore */}}
{{/*- if hasPrefix $path (printf "/accounts/%d/shops" $acctID) }}
<nav {{ template "navbar-class" . }}>
<ul
hx-swap="morph:innerHTML"
{{ template "navbar-ul-class" . }}
>
{{ template "navbar-link" (props
"Href" (printf "/ui/accounts/%d/shops" $acctID)
"Selected" (eq $path (printf "/accounts/%d/shops" $acctID))
"Content" "Live"
)}}
{{ template "navbar-link" (props
"Href" (printf "/ui/accounts/%d/shops/mock" $acctID)
"Selected" (eq $path (printf "/accounts/%d/shops/mock" $acctID))
"Content" "Mock"
)}}
</ul>
</nav>
{{- end */}}
{{/*- if hasPrefix $path (printf "/accounts/%d/inventory" $acctID) }}
<nav {{ template "navbar-class" . }}>
<ul
hx-swap="morph:innerHTML"
{{ template "navbar-ul-class" . }}
>
{{ template "navbar-link" (props
"Href" (printf "/ui/accounts/%d/inventory" $acctID)
"Selected" (eq $path (printf "/accounts/%d/inventory" $acctID))
"Content" "Live"
)}}
{{ template "navbar-link" (props
"Href" (printf "/ui/accounts/%d/inventory/mock" $acctID)
"Selected" (eq $path (printf "/accounts/%d/inventory/mock" $acctID))
"Content" "Mock"
)}}
</ul>
</nav>
{{- end */}}
</header>
<main class="basis-full grow max-w-full">
{{/* This is where the page_body template will be inserted into the page */}}
{{- block "body" . }}{{ end }}
</main>
<footer class="grid grid-cols-[6rem_10fr_6rem] justify-center items-center">
<address class="grow-1 col-2 flex justify-center">
<a href="mailto:contact-us@inventory-plus-plus.com" class="p-[1em] font-display text-center">Contact Us</a>
<a href="mailto:support@inventory-plus-plus.com" class="p-[1em] font-display text-center">Support</a>
</address>
{{- if (or .Identity.Claims.Picture .Identity.Claims.Name) }}
<button
popovertarget="identity-popover"
class="
col-3
max-w-full
max-h-full
flex
justify-center
items-center
"
>
{{ if .Identity.Claims.Picture }}
<img
src="{{ .Identity.Claims.Picture }}"
class="rounded-[50%]"
style="
max-height: 3rem;
margin: 1rem 0;
"
/>
{{ else if .Identity.Claims.Name }}
{{ .Identity.Claims.Name }}
{{ else }}
User
{{ end }}
</button>
<div
popover="auto"
id="identity-popover"
class="
border-[1px] border-sidebar-border rounded-lg
bg-card
p-[1em]
"
style="
top: calc(100% - 15rem);
left: calc(100% - 20rem);
box-shadow: 2px 2px 2px 1px rgb(0 0 0 / 20%);
"
>
<div class="
flex
justify-start
items-center
gap-[1em]
pb-[1em]
border-b-[2px]
border-sidebar-border
">
<div
class="
flex
justify-center
items-center
"
style="
flex-basis: 3rem;
flex-shrink: 0;
"
>
<img
src="{{ .Identity.Claims.Picture }}"
class="rounded-[50%]"
/>
</div>
<div>
<div class="font-display" style="word-break: break-word;">
{{ .Identity.Claims.Name }}
</div>
<div style="word-break: break-word;">
{{ or .Identity.Claims.Nickname .Identity.Claims.Subject }}
</div>
</div>
</div>
<a
href="/api/auth/{{if $devAuthEnabled}}dev-logout{{else}}logout{{end}}"
hx-boost="false"
class="
w-fit
flex
items-center
p-[1em]
font-display
hover:underline
cursor-pointer
gap-[1em]
group
hover:bg-accent
rounded-lg
mt-[0.25em]
pr-[2em]
"
>
<img
src="/images/door.png"
class="h-[2rem] group-hover:scale-120 transition-[scale] dark:invert"
/>
Log Out
</a>
</div>
{{- end }}
</footer>
</body>
</html>