134 lines
5.7 KiB
JavaScript
134 lines
5.7 KiB
JavaScript
import React, { useCallback, useEffect, useState } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { ArrowLeft, ArrowUpCircle, CheckCircle2, Info, Sparkles } from 'lucide-react';
|
|
import { api } from '@/api';
|
|
import { useAuth } from '@/hooks/useAuth';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
|
|
|
export default function AboutPage() {
|
|
const { user } = useAuth();
|
|
|
|
const [about, setAbout] = useState(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [updateStatus, setUpdateStatus] = useState(null);
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
setAbout(await api.about());
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => { load(); }, [load]);
|
|
|
|
useEffect(() => {
|
|
api.updateStatus().then(setUpdateStatus).catch(() => {});
|
|
}, []);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[radial-gradient(circle_at_top_left,oklch(var(--primary)/0.10),transparent_34rem),linear-gradient(180deg,oklch(var(--background)),oklch(var(--muted)/0.28))] px-4 py-8 text-foreground sm:px-6">
|
|
<main className="mx-auto w-full max-w-3xl space-y-5">
|
|
<Button asChild variant="ghost" size="sm" className="-ml-2">
|
|
<Link to={user ? '/' : '/login'}>
|
|
<ArrowLeft className="h-3.5 w-3.5" />
|
|
{user ? 'Back to app' : 'Back'}
|
|
</Link>
|
|
</Button>
|
|
|
|
<Card className="border-border/70 bg-card/95 shadow-sm" id="about-card">
|
|
<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">
|
|
<Info className="h-5 w-5" />
|
|
</div>
|
|
<CardTitle className="text-2xl">{about?.name || 'BillTracker'}</CardTitle>
|
|
<CardDescription>
|
|
<span className="text-sm">{about?.description || ''}</span>
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-5">
|
|
<div className="grid gap-3 sm:grid-cols-3">
|
|
|
|
{/* Version — with update status for admins */}
|
|
<div className="rounded-xl border border-border/70 bg-background/65 p-4">
|
|
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Version</p>
|
|
<p className="mt-1 font-mono text-lg font-bold">v{about?.version || '...'}</p>
|
|
{updateStatus && (
|
|
<div className="mt-2">
|
|
{updateStatus.has_update ? (
|
|
<a
|
|
href={updateStatus.latest_release_url || '#'}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-flex items-center gap-1 text-xs font-medium text-amber-400 hover:text-amber-300 transition-colors"
|
|
>
|
|
<ArrowUpCircle className="h-3 w-3 shrink-0" />
|
|
v{updateStatus.latest_version} available
|
|
</a>
|
|
) : updateStatus.up_to_date ? (
|
|
<span className="inline-flex items-center gap-1 text-xs text-emerald-500/80">
|
|
<CheckCircle2 className="h-3 w-3 shrink-0" />
|
|
Up to date
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="rounded-xl border border-border/70 bg-background/65 p-4">
|
|
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Backend</p>
|
|
<p className="mt-1 text-sm font-semibold">{about?.stack?.backend || 'Node.js / Express'}</p>
|
|
</div>
|
|
<div className="rounded-xl border border-border/70 bg-background/65 p-4">
|
|
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Storage</p>
|
|
<p className="mt-1 text-sm font-semibold">{about?.stack?.database || 'SQLite'}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-xl border border-border/70 bg-muted/35 p-4">
|
|
<div className="flex items-start gap-3">
|
|
<Sparkles className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
|
<div>
|
|
<p className="text-sm font-semibold">Produced with AI assistance</p>
|
|
<p className="mt-1 text-sm leading-6 text-muted-foreground">
|
|
BillTracker is self-hosted software for personal bill planning and history. This product was produced with the assistance of AI.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-2 sm:flex-row">
|
|
<Button asChild>
|
|
<Link to="/release-notes">Release Notes</Link>
|
|
</Button>
|
|
{/* Only shown when the visitor is not signed in */}
|
|
{user == null && (
|
|
<Button asChild variant="outline">
|
|
<Link to="/login">Sign In</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</main>
|
|
|
|
{/* Easter egg — barely visible, reveals on hover for curious explorers */}
|
|
<div className="flex justify-center pt-10 pb-4">
|
|
<a
|
|
href="/legacy"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-[10px] text-muted-foreground/10 hover:text-muted-foreground/50 transition-colors duration-1000 select-none tracking-widest uppercase"
|
|
tabIndex={-1}
|
|
aria-hidden="true"
|
|
>
|
|
remember when
|
|
</a>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|