ReviewOS

also looking at this

loghqorg/loghq

Promote main to production: signed-out chrome and the subscriber tables

#17
Merged glennmichael123 wants to merge main into production
54 files +4252 -1353

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/views/account.stxmodified+24-5
Changes to resources/views/account.stx
@@ -30,6 +30,11 @@ function fmtDate(v: string | null | undefined): string {
3030
3131// Derived reads. Plain functions rather than computed(): each is called from a
3232// binding, which re-evaluates when the signal it reads changes.
33// Signed in / signed out, decided only once /api/me has answered. Both are
34// false while `loaded` is still false, so the page shows its skeleton rather
35// than briefly claiming the visitor is signed out.
36const signedIn = (): boolean => loaded() && !!user()
37const signedOut = (): boolean => loaded() && !user()
3338const displayName = (): string => user()?.name || 'Your account'
3439const email = (): string => user()?.email || ''
3540// A remote avatar can 404 — a revoked GitHub account, a Gravatar that was
@@ -110,8 +115,13 @@ onMount(() => {
110115 <header class="flex items-center justify-between mx-auto px-5 md:px-6 h-16 max-w-2xl">
111116 <StxLink to="/" class="text-lg wordmark">loghq</StxLink>
112117 <div class="flex gap-2.5 items-center">
113 <StxLink to="/dashboard" class="px-3.5 py-2 font-semibold text-ink text-sm border border-line rounded-[10px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] active:translate-y-px [transition:transform_0.12s_ease,opacity_0.15s_ease,background-color_0.15s_ease]">Dashboard</StxLink>
114 <button @click="logout" class="px-3.5 py-2 font-semibold text-ink text-sm border border-line rounded-[10px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] active:translate-y-px [transition:transform_0.12s_ease,opacity_0.15s_ease,background-color_0.15s_ease]">Log out</button>
118 {{-- This page has no server auth block: the viewer arrives from /api/me
119 after paint, so these are bound rather than branched. Ungated, a
120 signed-out visitor was offered "Log out", which logs nobody out, above
121 a profile card of empty dashes. --}}
122 <StxLink to="/dashboard" class="px-3.5 py-2 font-semibold text-ink text-sm border border-line rounded-[10px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] active:translate-y-px [transition:transform_0.12s_ease,opacity_0.15s_ease,background-color_0.15s_ease]" :show="signedIn()">Dashboard</StxLink>
123 <button @click="logout" class="px-3.5 py-2 font-semibold text-ink text-sm border border-line rounded-[10px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] active:translate-y-px [transition:transform_0.12s_ease,opacity_0.15s_ease,background-color_0.15s_ease]" :show="signedIn()">Log out</button>
124 <StxLink to="/login" class="px-3.5 py-2 font-semibold text-accent-ink text-sm bg-accent hover:bg-[color-mix(in_srgb,var(--accent)_88%,#000)] rounded-[10px] active:translate-y-px [transition:transform_0.12s_ease,opacity_0.15s_ease,background-color_0.15s_ease]" :show="signedOut()">Sign in</StxLink>
115125 </div>
116126 </header>
117127
@@ -123,7 +133,16 @@ onMount(() => {
123133 <div class="mx-auto px-5 py-8 md:px-6 max-w-2xl">
124134 <h1 class="mb-6 text-2xl wordmark">Account</h1>
125135
126 <section class="mb-5 p-6 bg-panel border border-line rounded-[14px]">
136 {{-- The signed-out resting state. Without it this page sat on a skeleton
137 name, an empty email and a Free plan badge for a visitor who has no
138 account loaded at all, which reads as a broken profile rather than as a
139 page that needs signing in to. --}}
140 <div class="py-24 text-center" :show="signedOut()">
141 <p class="mb-4 text-sm text-subtle">You're signed out.</p>
142 <StxLink to="/login" class="inline-block px-4 py-2.5 font-semibold text-accent-ink text-sm bg-accent hover:bg-[color-mix(in_srgb,var(--accent)_88%,#000)] rounded-[10px]">Sign in</StxLink>
143 </div>
144
145 <section class="mb-5 p-6 bg-panel border border-line rounded-[14px]" :show="!signedOut()">
127146 <div class="flex gap-4 items-center">
128147 {{-- The initials block is the fallback for BOTH "no avatar" and "the
129148 avatar failed to load", so the two :show conditions are exact
@@ -151,7 +170,7 @@ onMount(() => {
151170 </div>
152171 </section>
153172
154 <section class="mb-5 bg-panel border border-line rounded-[14px]">
173 <section class="mb-5 bg-panel border border-line rounded-[14px]" :show="!signedOut()">
155174 <div class="flex items-center justify-between px-6 py-4">
156175 <div>
157176 <div class="font-mono text-subtle text-xs tracking-wide uppercase">Plan</div>
@@ -183,7 +202,7 @@ onMount(() => {
183202 </div>
184203 </section>
185204
186 <p class="text-sm text-subtle">
205 <p class="text-sm text-subtle" :show="!signedOut()">
187206 Signed in with a private token. <StxLink to="/" class="text-accent">Back to site</StxLink>
188207 </p>
189208 </div>
resources/views/analytics.stxmodified+10-3
Changes to resources/views/analytics.stx
@@ -243,9 +243,16 @@ onMounted(() => {
243243<div class="grain" aria-hidden="true"></div>
244244
245245<div class="px-5 py-10 md:px-10 min-h-[100dvh] page-dashboard">
246 <StxLink class="chrome-upgrade" to="/pricing" :show="!pro()">Upgrade</StxLink>
247 <span class="chrome-pro chrome-upgrade" :show="pro()">Pro</span>
248 <button class="chrome-logout" @click="signOut">Log out</button>
246 {{-- The whole chrome belongs to a signed-in viewer. Ungated, a signed-out
247 visitor got a "Log out" button that logs nobody out and an "Upgrade" call
248 to action, floating over the "You're signed out" panel below. `user` is
249 resolved server-side from the auth-token cookie, so this is decided before
250 paint and there is no flash of the wrong chrome. --}}
251 @if (user)
252 <StxLink class="chrome-upgrade" to="/pricing" :show="!pro()">Upgrade</StxLink>
253 <span class="chrome-pro chrome-upgrade" :show="pro()">Pro</span>
254 <button class="chrome-logout" @click="signOut">Log out</button>
255 @endif
249256
250257 <div class="mx-auto max-w-5xl">
251258
resources/views/dashboard.stxmodified+10-3
Changes to resources/views/dashboard.stx
@@ -647,9 +647,16 @@ onMount(() => {
647647 {{-- Fixed-position chrome. Inside the page container, not document.body, so an
648648 SPA view swap takes it with the rest of the page. Position is fixed, so
649649 where it sits in the DOM does not affect layout. --}}
650 <StxLink class="chrome-upgrade" to="/pricing" :show="!pro()">Upgrade</StxLink>
651 <span class="chrome-pro chrome-upgrade" :show="pro()">Pro</span>
652 <button class="chrome-logout" @click="signOut">Log out</button>
650 {{-- The whole chrome belongs to a signed-in viewer. Ungated, a signed-out
651 visitor got a "Log out" button that logs nobody out and an "Upgrade" call
652 to action, floating over the "You're signed out" panel below. `user` is
653 resolved server-side from the auth-token cookie, so this is decided before
654 paint and there is no flash of the wrong chrome. --}}
655 @if (user)
656 <StxLink class="chrome-upgrade" to="/pricing" :show="!pro()">Upgrade</StxLink>
657 <span class="chrome-pro chrome-upgrade" :show="pro()">Pro</span>
658 <button class="chrome-logout" @click="signOut">Log out</button>
659 @endif
653660
654661 <div class="mx-auto max-w-5xl">
655662
resources/views/projects/index.stxmodified+16-3
Changes to resources/views/projects/index.stx
@@ -120,15 +120,28 @@ function logout(): void {
120120 <header class="flex gap-4 items-center justify-between mb-9">
121121 <div>
122122 <h1 class="leading-none text-2xl md:text-[28px] wordmark">Your apps</h1>
123 <p class="mt-2 text-sm text-subtle">Pick an app to track, or create a new one.</p>
123 {{-- The subtitle invites an action, so it only belongs to someone who can
124 take it. Signed out, the same line read "Pick an app to track, or
125 create a new one." above a panel saying the visitor was signed out. --}}
126 @if (user)
127 <p class="mt-2 text-sm text-subtle">Pick an app to track, or create a new one.</p>
128 @else
129 <p class="mt-2 text-sm text-subtle">Sign in to see the apps you track.</p>
130 @endif
124131 </div>
125132 <div class="flex gap-2.5 items-center">
126133 <button class="inline-flex items-center justify-center h-9 w-9 text-subtle hover:text-ink bg-panel border border-line rounded-[10px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))] cursor-pointer [transition:color_0.15s_ease,border-color_0.15s_ease] theme-btn" @click="toggleTheme" x-tooltip="Toggle theme" aria-label="Toggle theme">
127134 <Icon name="lucide:sun" size="18" class="h-[18px] w-[18px] ic-sun" />
128135 <Icon name="lucide:moon" size="18" class="h-[18px] w-[18px] ic-moon" />
129136 </button>
130 <StxLink to="/projects/new" class="px-4 py-2 font-semibold text-accent-ink text-sm bg-accent hover:bg-[color-mix(in_srgb,var(--accent)_88%,#000)] rounded-[10px]">+ New app</StxLink>
131 <button @click="logout" class="px-3.5 py-2 font-medium text-ink text-sm bg-panel border border-line rounded-[10px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))]">Log out</button>
137 {{-- Both of these are signed-in affordances and neither was guarded, so a
138 signed-out visitor was offered "+ New app", which bounces straight back
139 to the login wall, and "Log out", which logs out nobody. The theme
140 toggle above stays: it works for anyone. --}}
141 @if (user)
142 <StxLink to="/projects/new" class="px-4 py-2 font-semibold text-accent-ink text-sm bg-accent hover:bg-[color-mix(in_srgb,var(--accent)_88%,#000)] rounded-[10px]">+ New app</StxLink>
143 <button @click="logout" class="px-3.5 py-2 font-medium text-ink text-sm bg-panel border border-line rounded-[10px] hover:border-[color-mix(in_srgb,var(--accent)_45%,var(--border))]">Log out</button>
144 @endif
132145 </div>
133146 </header>
134147