chore(brand): update PWA icons, release notes, and About page

This commit is contained in:
null 2026-07-06 12:13:01 -05:00
parent abb9f7d443
commit 3a14c50e08
9 changed files with 70 additions and 42 deletions

View File

@ -1,7 +1,7 @@
# Bill Tracker # Bill Tracker
<p align="center"> <p align="center">
<img src="docs/images/logo_cut.png" alt="Bill Tracker logo"> <img src="client/public/brand/social-preview.svg" alt="Bill Tracker — Calm command for household money">
</p> </p>
Bill Tracker is a private, self-hosted bill planning app for households and Bill Tracker is a private, self-hosted bill planning app for households and

View File

@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { RELEASE_NOTES } from '@/lib/version'; import { RELEASE_NOTES } from '@/lib/version';
import { Sparkles } from 'lucide-react'; import { BrandGlyph } from '@/components/brand/Brand';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { api } from '@/api'; import { api } from '@/api';
@ -28,9 +28,7 @@ export function ReleaseNotesDialog() {
<DialogContent className="max-h-[92dvh] max-w-md overflow-y-auto sm:max-w-lg"> <DialogContent className="max-h-[92dvh] max-w-md overflow-y-auto sm:max-w-lg">
<DialogHeader> <DialogHeader>
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10"> <BrandGlyph name="tracker" className="h-8 w-8 rounded-lg" />
<Sparkles className="h-4 w-4 text-primary" />
</div>
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider"> <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
v{RELEASE_NOTES.version} · {RELEASE_NOTES.date} v{RELEASE_NOTES.version} · {RELEASE_NOTES.date}
</span> </span>

View File

@ -2,7 +2,7 @@ import { useState } from 'react';
import { AlertCircle, ChevronDown, ChevronUp, BellOff, SkipForward, CreditCard, EyeOff } from 'lucide-react'; import { AlertCircle, ChevronDown, ChevronUp, BellOff, SkipForward, CreditCard, EyeOff } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { api } from '@/api'; import { api } from '@/api';
import { cn, fmt, localDateString } from '@/lib/utils'; import { fmt, localDateString } from '@/lib/utils';
import { asDollars } from '@/lib/money'; import { asDollars } from '@/lib/money';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';

View File

@ -58,7 +58,7 @@ export const RELEASE_NOTES: ReleaseNotes = {
}, },
], ],
image: { image: {
src: '/img/doingmypart.jpg', src: '/brand/social-preview.svg',
alt: 'Doing my part', alt: 'Bill Tracker brand preview',
}, },
}; };

View File

@ -4,8 +4,11 @@ import { ArrowLeft, ArrowUpCircle, CheckCircle2, Info, Loader2, Sparkles, AlertC
import { api } from '@/api'; import { api } from '@/api';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { APP_VERSION } from '@/lib/version'; import { APP_VERSION } from '@/lib/version';
import { BRAND } from '@/lib/brand';
import { BrandMark } from '@/components/brand/Brand';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { PageHeader } from '@/components/ui/app-primitives';
const REPOSITORY_URL = 'https://dream.scheller.ltd/null/BillTracker'; const REPOSITORY_URL = 'https://dream.scheller.ltd/null/BillTracker';
@ -99,7 +102,7 @@ export default function AboutPage() {
const displayVersion = about?.version ?? APP_VERSION; const displayVersion = about?.version ?? APP_VERSION;
return ( return (
<div className="min-h-screen bg-[radial-gradient(circle_at_top_left,oklch(var(--primary)/0.06),transparent_34rem),linear-gradient(180deg,oklch(var(--background)),oklch(var(--muted)/0.18))] px-4 py-8 text-foreground sm:px-6"> <div className="min-h-screen bg-[radial-gradient(circle_at_top_left,oklch(var(--primary)/0.10),transparent_32rem),radial-gradient(circle_at_top_right,oklch(var(--accent)/0.08),transparent_24rem),linear-gradient(180deg,oklch(var(--background)),oklch(var(--muted)/0.18))] px-4 py-8 text-foreground sm:px-6">
<main className="mx-auto w-full max-w-3xl space-y-5"> <main className="mx-auto w-full max-w-3xl space-y-5">
<Button asChild variant="ghost" size="sm" className="-ml-2"> <Button asChild variant="ghost" size="sm" className="-ml-2">
<Link to={user ? '/' : '/login'}> <Link to={user ? '/' : '/login'}>
@ -108,14 +111,20 @@ export default function AboutPage() {
</Link> </Link>
</Button> </Button>
<Card className="border-border/70 bg-card shadow-sm" id="about-card"> <Card className="overflow-hidden border-border/70 bg-card/95 shadow-sm" id="about-card">
<CardHeader> <CardHeader>
<div className="mb-2 flex h-10 w-10 items-center justify-center rounded-xl border border-border/70 bg-primary/10 text-primary"> <div className="mb-4 flex justify-center rounded-xl border border-border/70 bg-[radial-gradient(circle_at_top,oklch(var(--primary)/0.14),transparent_70%),oklch(var(--muted)/0.20)] px-5 py-7">
<Info className="h-5 w-5" /> <BrandMark className="h-auto w-full max-w-[18rem] drop-shadow-[0_10px_30px_rgba(0,0,0,0.16)]" />
</div> </div>
<CardTitle className="text-2xl">{about?.name || 'Bill Tracker'}</CardTitle> <PageHeader
eyebrow="About"
title={about?.name || BRAND.name}
description={about?.description || BRAND.tagline}
glyph="security"
/>
<CardTitle className="sr-only">{about?.name || BRAND.name}</CardTitle>
<CardDescription> <CardDescription>
<span className="text-sm">{about?.description || ''}</span> <span className="sr-only">{about?.description || BRAND.tagline}</span>
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-5"> <CardContent className="space-y-5">
@ -141,12 +150,24 @@ export default function AboutPage() {
</div> </div>
<div className="rounded-xl border border-border/70 bg-muted/35 p-4"> <div className="rounded-xl border border-border/70 bg-muted/35 p-4">
<div className="flex items-start gap-3">
<Info className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
<div>
<p className="text-sm font-semibold">Private, practical, self-hosted</p>
<p className="mt-1 text-sm leading-6 text-muted-foreground">
{BRAND.name} is self-hosted software for personal bill planning and history. It is built around local ownership, readable records, and calm monthly review.
</p>
</div>
</div>
</div>
<div className="rounded-xl border border-primary/20 bg-primary/[0.06] p-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<Sparkles className="mt-0.5 h-4 w-4 shrink-0 text-primary" /> <Sparkles className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
<div> <div>
<p className="text-sm font-semibold">Produced with AI assistance</p> <p className="text-sm font-semibold">{BRAND.tagline}</p>
<p className="mt-1 text-sm leading-6 text-muted-foreground"> <p className="mt-1 text-sm leading-6 text-muted-foreground">
Bill Tracker is self-hosted software for personal bill planning and history. This product was produced with the assistance of AI. The brand direction is premium calm: trustworthy, quiet, and focused on reducing household money stress.
</p> </p>
</div> </div>
</div> </div>

View File

@ -6,6 +6,7 @@ import { api } from '@/api';
import { cn, errMessage } from '@/lib/utils'; import { cn, errMessage } from '@/lib/utils';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { MarkdownText } from '@/components/MarkdownText'; import { MarkdownText } from '@/components/MarkdownText';
import { EmptyState, PageHeader } from '@/components/ui/app-primitives';
interface ReleaseHistoryData { interface ReleaseHistoryData {
history?: string; history?: string;
@ -110,27 +111,32 @@ export default function ReleaseNotesPage() {
const history = data?.history || ''; const history = data?.history || '';
return ( return (
<div className="min-h-screen bg-[radial-gradient(circle_at_top_left,oklch(var(--primary)/0.06),transparent_34rem),linear-gradient(180deg,oklch(var(--background)),oklch(var(--muted)/0.18))] px-4 py-8 text-foreground sm:px-6"> <div className="min-h-screen bg-[radial-gradient(circle_at_top_left,oklch(var(--primary)/0.10),transparent_32rem),radial-gradient(circle_at_top_right,oklch(var(--accent)/0.08),transparent_24rem),linear-gradient(180deg,oklch(var(--background)),oklch(var(--muted)/0.18))] px-4 py-8 text-foreground sm:px-6">
<main className="mx-auto w-full max-w-4xl space-y-5"> <main className="mx-auto w-full max-w-4xl space-y-5">
<div className="flex items-center justify-between gap-3"> <Button asChild variant="ghost" size="sm" className="-ml-2">
<div> <Link to="/about">
<Button asChild variant="ghost" size="sm" className="mb-2 -ml-2"> <ArrowLeft className="h-3.5 w-3.5" />
<Link to="/about"> About
<ArrowLeft className="h-3.5 w-3.5" /> </Link>
About
</Link>
</Button>
<h1 className="text-2xl font-bold tracking-tight">Release Notes</h1>
<p className="text-sm text-muted-foreground mt-0.5">
{data?.version ? `Current version v${data.version}` : 'Full project changelog'}
{data?.updated_at ? ` · Updated ${formatDateTime(data.updated_at)}` : ''}
</p>
</div>
<Button variant="outline" size="sm" onClick={load} disabled={loading}>
<RefreshCw className={cn('h-3.5 w-3.5 mr-1.5', loading && 'animate-spin')} />
Refresh
</Button> </Button>
</div>
<PageHeader
eyebrow="Product history"
title="Release Notes"
description={(
<>
{data?.version ? `Current version v${data.version}` : 'Full project changelog'}
{data?.updated_at ? ` · Updated ${formatDateTime(data.updated_at)}` : ''}
</>
)}
glyph="analytics"
actions={(
<Button variant="outline" size="sm" onClick={load} disabled={loading}>
<RefreshCw className={cn('h-3.5 w-3.5 mr-1.5', loading && 'animate-spin')} />
Refresh
</Button>
)}
/>
<div className="table-surface"> <div className="table-surface">
{loading ? ( {loading ? (
@ -141,14 +147,17 @@ export default function ReleaseNotesPage() {
<div className="h-3 w-3/4 rounded bg-muted" /> <div className="h-3 w-3/4 rounded bg-muted" />
</div> </div>
) : error ? ( ) : error ? (
<div className="p-6"> <EmptyState
<p className="text-sm font-medium text-destructive">Unable to load release notes.</p> title="Unable to load release notes"
<p className="text-sm text-muted-foreground mt-1">{error}</p> description={error}
</div> className="border-0 shadow-none"
/>
) : !history.trim() ? ( ) : !history.trim() ? (
<div className="p-6"> <EmptyState
<p className="text-sm text-muted-foreground">No release notes are available.</p> title="No release notes are available"
</div> description="Check back after the next release."
className="border-0 shadow-none"
/>
) : ( ) : (
<div className="p-6"> <div className="p-6">
{history.split('\n').map((line, index) => ( {history.split('\n').map((line, index) => (

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 68 KiB