Polish: per-route titles and a way to sign out
There was no sign-out control anywhere. Easy to miss when you develop permanently signed in, and a stranded feeling for anyone on a shared machine. Clearing the identity provider's session is sufficient — PIG holds no session of its own — and the auth listener returns to sign-in without a reload. A hard redirect follows regardless, so a failed provider call cannot leave a half-signed-out interface. Every route now sets its own document title. A single static title makes browser history and a wall of tabs useless: every entry reads "pig" and nobody can tell the margin view from the pipeline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Per-route document titles.
|
||||
*
|
||||
* A single static title makes browser history and a wall of open tabs useless
|
||||
* — every entry reads "pig" and nobody can tell the margin view from the
|
||||
* pipeline. The page name leads, because that is the part a tab strip has room
|
||||
* to show before truncating.
|
||||
*/
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function usePageTitle(title?: string): void {
|
||||
useEffect(() => {
|
||||
document.title = title ? `${title} · pig` : 'pig — Prime Intellect Growth';
|
||||
return () => {
|
||||
document.title = 'pig — Prime Intellect Growth';
|
||||
};
|
||||
}, [title]);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { Building2 } from 'lucide-react';
|
||||
import { get, relativeTime } from '@/lib/api';
|
||||
import { Badge, ConfidenceBadge, EmptyState, Input, Skeleton } from '@/components/ui';
|
||||
import { usePageTitle } from '@/lib/title';
|
||||
|
||||
interface Account {
|
||||
id: string;
|
||||
@@ -20,6 +21,7 @@ interface Account {
|
||||
}
|
||||
|
||||
export function Accounts() {
|
||||
usePageTitle('Accounts');
|
||||
const [side, setSide] = useState<'all' | 'supply' | 'demand'>('all');
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useState } from 'react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { Search, Server, Zap } from 'lucide-react';
|
||||
import { compactNumber, get, money, percent, post, shortDate } from '@/lib/api';
|
||||
import { usePageTitle } from '@/lib/title';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@@ -42,6 +43,7 @@ interface AvailabilityRow {
|
||||
type MatchRow = AvailabilityRow & { score: number; rationale: string[] };
|
||||
|
||||
export function Capacity() {
|
||||
usePageTitle('Capacity');
|
||||
const [tab, setTab] = useState<'available' | 'match'>('available');
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { compactNumber, get, money, moneyExact, percent } from '@/lib/api';
|
||||
import { Card, CardContent, CardHeader, CardTitle, EmptyState, Skeleton, Stat } from '@/components/ui';
|
||||
import { usePageTitle } from '@/lib/title';
|
||||
|
||||
interface MarginReport {
|
||||
totals: {
|
||||
@@ -36,6 +37,7 @@ interface MarginReport {
|
||||
}
|
||||
|
||||
export function Margin() {
|
||||
usePageTitle('Margin');
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['margin'],
|
||||
queryFn: () => get<MarginReport>('/api/capacity/margin'),
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AlertTriangle, ArrowRight, Server, TrendingUp } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { compactNumber, get, money, percent, relativeTime } from '@/lib/api';
|
||||
import { Badge, Card, CardContent, CardHeader, CardTitle, EmptyState, Skeleton, Stat } from '@/components/ui';
|
||||
import { usePageTitle } from '@/lib/title';
|
||||
|
||||
interface Dashboard {
|
||||
me: { name: string; teams: { team: string; role: string }[] };
|
||||
@@ -44,6 +45,7 @@ interface Dashboard {
|
||||
}
|
||||
|
||||
export function Overview() {
|
||||
usePageTitle('Overview');
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['dashboard'],
|
||||
queryFn: () => get<Dashboard>('/api/dashboard'),
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { get, money, relativeTime } from '@/lib/api';
|
||||
import { Badge, Card, EmptyState, Skeleton } from '@/components/ui';
|
||||
import { usePageTitle } from '@/lib/title';
|
||||
|
||||
interface DemandDeal {
|
||||
id: string;
|
||||
@@ -126,6 +127,8 @@ function PipelineBoard<T extends { id: string; stage: string; updatedAt: string
|
||||
endpoint: string;
|
||||
renderCard: (deal: T, accountName: string | null) => React.ReactNode;
|
||||
}) {
|
||||
usePageTitle(title);
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: [endpoint],
|
||||
queryFn: () => get<Board<T>>(endpoint),
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
* devices rather than being a per-browser quirk.
|
||||
*/
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Check, Monitor, Moon, Sun, Terminal } from 'lucide-react';
|
||||
import { get, patch } from '@/lib/api';
|
||||
import { Check, LogOut, Monitor, Moon, Sun, Terminal } from 'lucide-react';
|
||||
import { get, getSupabase, patch } from '@/lib/api';
|
||||
import { useTheme } from '@/lib/theme';
|
||||
import { getAccent, THEME_MODES, type ThemeMode } from '@pig/core';
|
||||
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Input } from '@/components/ui';
|
||||
import { useState } from 'react';
|
||||
import { usePageTitle } from '@/lib/title';
|
||||
|
||||
interface Me {
|
||||
id: string;
|
||||
@@ -23,6 +24,7 @@ interface Me {
|
||||
}
|
||||
|
||||
export function Settings() {
|
||||
usePageTitle('Settings');
|
||||
const { data: me } = useQuery({ queryKey: ['me'], queryFn: () => get<Me>('/api/me') });
|
||||
|
||||
return (
|
||||
@@ -34,6 +36,7 @@ export function Settings() {
|
||||
<Appearance />
|
||||
<Profile me={me} />
|
||||
<ConnectAgent />
|
||||
<SessionCard />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -236,3 +239,47 @@ claude mcp add pig -- npx -y @pig/mcp`}</code>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signing out.
|
||||
*
|
||||
* There was no way to do this at all until now, which is an easy omission to
|
||||
* make when developing signed in and a genuinely stranded feeling for anyone
|
||||
* on a shared machine.
|
||||
*
|
||||
* Clearing the identity provider's session is enough: PIG holds no session of
|
||||
* its own, and the auth listener in App.tsx notices and returns to sign-in
|
||||
* without a reload.
|
||||
*/
|
||||
function SessionCard() {
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
async function signOut() {
|
||||
setBusy(true);
|
||||
try {
|
||||
await getSupabase()?.auth.signOut();
|
||||
} finally {
|
||||
// Belt and braces: if the provider call fails, a reload still lands on
|
||||
// the sign-in screen rather than leaving a half-signed-out interface.
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Session</CardTitle>
|
||||
<p className="text-sm text-muted">
|
||||
Signing out clears this browser only. API keys you have issued keep working —
|
||||
revoke those separately.
|
||||
</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button variant="outline" onClick={signOut} disabled={busy}>
|
||||
<LogOut className="h-4 w-4" aria-hidden />
|
||||
{busy ? 'Signing out…' : 'Sign out'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user