Polish product artwork and branding

This commit is contained in:
null 2026-07-10 19:10:42 -05:00
parent 92439e74e6
commit 562da73a76
15 changed files with 79 additions and 55 deletions

View File

@ -183,7 +183,7 @@
### Release Image
![Doing my part](/img/doingmypart.jpg)
![Bill Tracker release preview](/brand/social-preview.png)
---
@ -301,7 +301,7 @@
### Release Image
![Doing my part](/img/doingmypart.jpg)
![Bill Tracker release preview](/brand/social-preview.png)
---
@ -433,7 +433,7 @@
### Release Image
![Doing my part](/img/doingmypart.jpg)
![Bill Tracker release preview](/brand/social-preview.png)
---
## v0.35.1
@ -448,7 +448,7 @@
### Release Image
![Doing my part](/img/doingmypart.jpg)
![Bill Tracker release preview](/brand/social-preview.png)
---
## v0.35.0
@ -467,7 +467,7 @@
### Release Image
![Doing my part](/img/doingmypart.jpg)
![Bill Tracker release preview](/brand/social-preview.png)
---
## v0.34.3
@ -516,7 +516,7 @@
### Release Image
![Doing my part](/img/doingmypart.jpg)
![Bill Tracker release preview](/brand/social-preview.png)
---
## v0.34.2
@ -806,7 +806,7 @@
### Release Image
![Doing my part](/img/doingmypart.jpg)
![Bill Tracker release preview](/brand/social-preview.png)
## v0.28.0
@ -821,7 +821,7 @@
- **Login device details** — Login history now stores parsed browser, OS, device type, and a short privacy-preserving device fingerprint hash derived from user-agent plus coarse IP prefix. The Profile login history dialog shows the richer device details to help users recognize their own sign-ins.
- **Profile last-login card** — The Profile summary now shows the most recent login date, IP address, device type, browser, and OS inline. Clicking the modern last-login card opens the full login history modal with device IDs and recent sign-in details.
- **Public privacy page** — Added public `/api/privacy` and `/privacy` pages with a modern, scan-friendly policy layout, plus an About page Privacy button. The Profile login-history modal now notes that login device details are shown only to the user and are not shared with admins in the app UI.
- **Release notes image support** — Release notes now render Markdown image blocks in a centered responsive frame, and the in-app release notes dialog displays the `doingmypart.jpg` release image at the end of the v0.28.0 entry.
- **Release notes image support** — Release notes now render Markdown image blocks in a centered responsive frame, and the in-app release notes dialog displays branded release art at the end of the v0.28.0 entry.
- **Backend-controlled update card reset** — The “Whats new” card now relies only on the backend `last_seen_version` check. `/api/auth/me` exposes the active `release_notes_version`, acknowledging an update stores that version, and any future package-version update will automatically show the card again for users who have not seen it.
- **Instant bill search and filters** — Bills and Tracker now include search inputs for quickly finding bills by name, category, notes, or amount. Bills adds category, billing-cycle, autopay, due-bucket, debt, and inactive filters; Tracker adds current-month category, billing-cycle, unpaid, overdue, autopay, due-bucket, and debt filters.
- **Command palette bill lookup** — Added a global Ctrl+K command palette for finding bills by name, category, notes, due details, or amount, with quick jumps into Bills or the current Tracker search.
@ -858,7 +858,7 @@
### Release Image
![Doing my part](/img/doingmypart.jpg)
![Bill Tracker release preview](/brand/social-preview.png)
## v0.27.04

View File

@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { FieldRow, Toggle } from './adminShared';
const AUTHENTIK_ICON_URL = '/img/auth.png';
const OIDC_ICON_URL = '/img/auth.svg';
interface OidcForm {
local_login_enabled: boolean;
@ -248,7 +248,7 @@ export default function AuthMethodsCard() {
<div className="space-y-4 pt-2 border-t border-border">
<div className="flex items-center gap-2 pt-1 text-sm font-medium text-muted-foreground">
<img
src={AUTHENTIK_ICON_URL}
src={OIDC_ICON_URL}
alt=""
aria-hidden="true"
className="h-5 w-5 shrink-0 object-contain"
@ -466,7 +466,7 @@ export default function AuthMethodsCard() {
}}
>
<img
src={AUTHENTIK_ICON_URL}
src={OIDC_ICON_URL}
alt=""
aria-hidden="true"
className="mr-2 h-4 w-4 shrink-0 object-contain"

View File

@ -227,7 +227,6 @@ export default function LoginPage() {
const localEnabled = authMode?.local_enabled !== false;
const oidcEnabled = !!authMode?.oidc_enabled && !!authMode?.oidc_login_url;
const providerName = authMode?.oidc_provider_name || 'authentik';
const isAuthentikProvider = providerName.toLowerCase().includes('authentik');
const handleChangePassword = async (e: React.FormEvent) => {
e.preventDefault();
@ -455,17 +454,15 @@ export default function LoginPage() {
window.location.href = authMode?.oidc_login_url || '';
}}
>
{isAuthentikProvider && (
<img
src="/img/auth.png"
alt=""
aria-hidden="true"
className="h-5 w-5 object-contain shrink-0"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
)}
<img
src="/img/auth.svg"
alt=""
aria-hidden="true"
className="h-5 w-5 object-contain shrink-0"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
Continue with {providerName}
</Button>
)}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 331 KiB

View File

@ -1,11 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<!-- Authentik brand mark — orange geometric shield/key icon -->
<path
fill="#FD4B2D"
d="M12 2L4 5.5v6c0 4.25 3.4 8.2 8 9.5 4.6-1.3 8-5.25 8-9.5v-6L12 2z"
/>
<path
fill="#fff"
d="M12 7a2.5 2.5 0 0 1 2.5 2.5c0 1-.6 1.88-1.5 2.3V14h-2v-2.2A2.5 2.5 0 0 1 9.5 9.5 2.5 2.5 0 0 1 12 7zm0 1.5a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-.75 5.75h1.5v1.5h-1.5v-1.5z"
/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-labelledby="title desc">
<title id="title">Bill Tracker secure access</title>
<desc id="desc">A dark security shield with a green confirmation check and teal connection points.</desc>
<defs>
<linearGradient id="ink" x1="34" x2="222" y1="34" y2="222" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#172025"/>
<stop offset="1" stop-color="#0f1417"/>
</linearGradient>
<linearGradient id="trust" x1="62" x2="184" y1="190" y2="58" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#1e9f93"/>
<stop offset="0.58" stop-color="#40c878"/>
<stop offset="1" stop-color="#96e6a1"/>
</linearGradient>
<filter id="shadow" x="-18%" y="-16%" width="136%" height="140%" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="12" stdDeviation="16" flood-color="#061014" flood-opacity="0.28"/>
</filter>
</defs>
<rect width="256" height="256" rx="58" fill="url(#ink)"/>
<g filter="url(#shadow)">
<path d="M128 45 186 67v44c0 41-24 71-58 90-34-19-58-49-58-90V67l58-22Z" fill="none" stroke="url(#trust)" stroke-width="14" stroke-linejoin="round"/>
<path d="M101 126.5 120.5 146 162 93" fill="none" stroke="#96e6a1" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
<path d="M101 126.5 120.5 146 162 93" fill="none" stroke="#40c878" stroke-linecap="round" stroke-linejoin="round" stroke-width="8"/>
<circle cx="75" cy="182" r="13" fill="#23b6a8"/>
<circle cx="181" cy="182" r="13" fill="#23b6a8"/>
<path d="M88 182h80" stroke="#23b6a8" stroke-linecap="round" stroke-width="8" opacity="0.76"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 456 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -32,14 +32,14 @@ The visual lane is **premium calm**.
## Core Colors
| Token | Hex | Use |
| --- | --- | --- |
| Ink | `#101417` | Dark app shell, mark background, premium depth |
| Paper | `#f8faf9` | Light app background, high-contrast text on dark |
| Token | Hex | Use |
| ----- | --------- | --------------------------------------------------- |
| Ink | `#101417` | Dark app shell, mark background, premium depth |
| Paper | `#f8faf9` | Light app background, high-contrast text on dark |
| Trust | `#40c878` | Primary actions, brand mark, success, active states |
| Teal | `#23b6a8` | Bank sync, data flow, secondary brand accent |
| Amber | `#d99a24` | Due soon, warnings, drift, review-needed states |
| Rose | `#df4661` | Overdue, destructive, high urgency |
| Teal | `#23b6a8` | Bank sync, data flow, secondary brand accent |
| Amber | `#d99a24` | Due soon, warnings, drift, review-needed states |
| Rose | `#df4661` | Overdue, destructive, high urgency |
The app implementation maps these through OKLCH theme tokens in `client/index.css`; do not hardcode the hex values in components unless creating static brand assets.
@ -49,7 +49,7 @@ Primary source files live in `client/public/brand/`.
- `lockup.svg` - primary logo for dark surfaces.
- `lockup-light.svg` - primary logo for light surfaces.
- `logo.png` - raster export of the primary logo for docs and non-SVG contexts.
- `logo.png` - light-surface raster export of the primary logo for docs and non-SVG contexts.
- `mark.svg` - compact app mark and favicon.
- `mark.png` - raster export of the compact app mark.
- `mark-mono.svg` - single-color mark for constrained contexts.
@ -59,6 +59,11 @@ Primary source files live in `client/public/brand/`.
- `pwa-192.png`, `pwa-512.png`, `pwa-maskable.svg`, `pwa-maskable-512.png`, `apple-touch-icon.png` - install surfaces.
- `glyphs/*.svg` - standalone product glyph source files.
Supporting app artwork:
- `client/public/img/auth.svg` - branded secure-access glyph for OIDC/authentication surfaces.
- `client/public/img/auth.png` - raster fallback export of the secure-access glyph.
Rules:
- Use the full lockup when the product needs to introduce itself.
@ -70,15 +75,15 @@ Rules:
Bill Tracker has a small proprietary product glyph set for page identity only:
| Product area | Asset |
| --- | --- |
| Tracker | `client/public/brand/glyphs/tracker.svg` |
| Bills | `client/public/brand/glyphs/bills.svg` |
| Calendar | `client/public/brand/glyphs/calendar.svg` |
| Analytics | `client/public/brand/glyphs/analytics.svg` |
| Data / Banking | `client/public/brand/glyphs/data-banking.svg` |
| Product area | Asset |
| ----------------- | ------------------------------------------------ |
| Tracker | `client/public/brand/glyphs/tracker.svg` |
| Bills | `client/public/brand/glyphs/bills.svg` |
| Calendar | `client/public/brand/glyphs/calendar.svg` |
| Analytics | `client/public/brand/glyphs/analytics.svg` |
| Data / Banking | `client/public/brand/glyphs/data-banking.svg` |
| Snowball / Payoff | `client/public/brand/glyphs/snowball-payoff.svg` |
| Admin / Security | `client/public/brand/glyphs/admin-security.svg` |
| Admin / Security | `client/public/brand/glyphs/admin-security.svg` |
Rules:
@ -86,6 +91,7 @@ Rules:
- Keep Lucide icons for actions, controls, menus, buttons, tables, and common UI patterns.
- Never replace familiar action icons such as edit, delete, search, settings, upload, download, calendar navigation, or logout.
- Glyphs should reuse the mark language: bars, checks, ledger lines, quiet rounded geometry.
- Avoid third-party-style or novelty images inside core app surfaces unless the product explicitly needs a partner logo.
## Typography

View File

@ -1222,7 +1222,11 @@ The lazy audit helper in `db/database.js` is:
let _logAudit = null;
function getLogAudit() {
if (!_logAudit) {
try { _logAudit = require('../services/auditService').logAudit; } catch { _logAudit = () => {}; }
try {
_logAudit = require('../services/auditService').logAudit;
} catch {
_logAudit = () => {};
}
}
return _logAudit;
}
@ -1255,7 +1259,7 @@ Creates the React root and wraps `App` with global providers. Theme is provided
### `client/App.tsx`
- Creates a TanStack `QueryClient` with:
- Global `QueryCache.onError` — toasts a `sonner` error when a *background* refetch fails while stale data is on screen. Pages render inline errors on the initial load so the toast only fires for refetches.
- Global `QueryCache.onError` — toasts a `sonner` error when a _background_ refetch fails while stale data is on screen. Pages render inline errors on the initial load so the toast only fires for refetches.
- `staleTime: 2 * 60 * 1000`, `retry: 1`, `refetchOnWindowFocus: false`.
- Uses lazy loading and `Suspense` with `PageLoader` for most pages.
- Wraps route elements in `ErrorBoundary`; pages further wrap in `PageTransition`.
@ -1310,8 +1314,8 @@ Routes (all rendered through `Routes` with `ErrorBoundary`, lazy + `Suspense` fo
- Exposes grouped functions for `auth`, `admin`, `notifications`, `profile`, `tracker`, `calendar`, `summary`, `bills`, `payments`, `categories`, `settings`, `analytics`, `status`, `version`/`about`, `import`, `export`, `data-sources`, `transactions`, `matches`, `snowball`, `subscriptions`, `spending`, `monthlyStartingAmounts`, `csvImport`, `matchSuggestions`, `bankTransactionsLedger`, `matchTransaction`, `unmatchTransaction`, `ignoreTransaction`, `unignoreTransaction`, `applyTransactionMerchantMatch`, `autoCategorizeTransactions`, `categoryGroups`, `copySpendingBudgets`, `spendingIncome`, `spendingCategoryRules`, `createPaymentOrConfirm` (via `client/lib/paymentActions.ts`).
- File download/upload endpoints use raw `fetch` because responses/bodies are blobs or octet streams.
- **All 16 import sites** that referenced `@/api.js` were normalized to `@/api` so Vite/TS resolve the `.ts`.
- **Track C response typing** — see §7 *Domain types* for the full list. In short, the high-traffic domains (`auth`, `bills`, `payments`, `categories`, `tracker`, `snowball`, `subscriptions`, `spending`, `bank-ledger`) all return typed responses via `get<T>` / `post<T>`; the remaining single-consumer non-money casts (adminUsers, version/about/privacy/health/importHistory) are deliberately left as-is.
- **Server error-shape consistency (Track D).** The global 500 handler in `server.js` emits `{ error, code: 'INTERNAL_ERROR' }` so an unexpected server error carries the same `{error, code}` field the client can switch on as every structured 4xx. See §5 *Response conventions*.
- **Track C response typing** — see §7 _Domain types_ for the full list. In short, the high-traffic domains (`auth`, `bills`, `payments`, `categories`, `tracker`, `snowball`, `subscriptions`, `spending`, `bank-ledger`) all return typed responses via `get<T>` / `post<T>`; the remaining single-consumer non-money casts (adminUsers, version/about/privacy/health/importHistory) are deliberately left as-is.
- **Server error-shape consistency (Track D).** The global 500 handler in `server.js` emits `{ error, code: 'INTERNAL_ERROR' }` so an unexpected server error carries the same `{error, code}` field the client can switch on as every structured 4xx. See §5 _Response conventions_.
### Domain types — `client/types.ts`
@ -1514,7 +1518,7 @@ Forgejo Actions runs on every push and PR: `actions/checkout@v4` on `node:22-boo
### Vite (`vite.config.mjs`)
- `@vitejs/plugin-react` with `babel-plugin-react-compiler` enabled (React 19 auto-memoization). The codebase is rules-of-hooks clean (enforced by `eslint-plugin-react-hooks`).
- `vite-plugin-pwa` with `registerType: 'autoUpdate'`, manifest, icons in `client/public/img/`, runtime caching for `/api/(tracker|bills|calendar|summary|analytics|snowball|categories)` via `NetworkFirst` (5s network timeout, 30-entry max, 5-min max-age).
- `vite-plugin-pwa` with `registerType: 'autoUpdate'`, manifest, icons in `client/public/brand/`, runtime caching for `/api/(tracker|bills|calendar|summary|analytics|snowball|categories)` via `NetworkFirst` (5s network timeout, 30-entry max, 5-min max-age).
- `__APP_VERSION__` is injected at build time from `package.json`; the SPA reads it through `client/lib/version.ts`.
- Manual chunk splitting: `vendor-react` (react / react-dom / react-router-dom), `vendor-radix` (dialog / select / dropdown-menu / tabs / tooltip / alert-dialog), `vendor-motion` (framer-motion), `vendor-query` (@tanstack/react-query + @tanstack/react-virtual).
- `resolve.alias['@']``client/`, matches the Vite alias and the `tsconfig.json` `paths` mapping.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -36,6 +36,7 @@ export default defineConfig({
'brand/empty-state.svg',
'brand/social-preview.svg',
'brand/social-preview.png',
'img/auth.svg',
'brand/pwa-maskable.svg',
'brand/glyphs/tracker.svg',
'brand/glyphs/bills.svg',