import { useEffect, useMemo, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import {
useMutation,
useQuery,
useQueryClient,
} from '@tanstack/react-query';
import type {
AuthResponse,
CompleteCodexManualLoginInput,
LoginInput,
RegisterInput,
StartCodexLoginInput,
} from '@codexdash/shared-types';
import {
Activity,
CirclePlus,
ExternalLink,
Gauge,
Link as LinkIcon,
LoaderCircle,
LogOut,
RefreshCw,
ShieldCheck,
Trash2,
Waypoints,
} from 'lucide-react';
import { toast, Toaster } from 'sonner';
import { api } from '@/lib/api';
import { clearToken, getToken, setToken } from '@/lib/storage';
import { flattenNumericMetrics, formatDate, titleizeMetric } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { JsonViewer } from '@/components/json-viewer';
const registerSchema = z.object({
name: z.string().min(2),
email: z.email(),
password: z.string().min(8),
});
const loginSchema = z.object({
email: z.email(),
password: z.string().min(8),
});
const connectSchema = z.object({
label: z.string().min(2),
emailHint: z.string().optional(),
});
const manualCallbackSchema = z.object({
callbackUrl: z.string().min(10),
});
function AuthShell({
onAuthenticated,
}: {
onAuthenticated: (response: AuthResponse) => void;
}) {
const [mode, setMode] = useState<'login' | 'register'>('register');
const schema = mode === 'register' ? registerSchema : loginSchema;
const form = useForm<{ name?: string; email: string; password: string }>({
resolver: zodResolver(schema),
defaultValues: {
email: '',
password: '',
...(mode === 'register' ? { name: '' } : {}),
},
});
const mutation = useMutation({
mutationFn: async (values: {
name?: string;
email: string;
password: string;
}) => {
return mode === 'register'
? api.register(values as RegisterInput)
: api.login({
email: values.email,
password: values.password,
} as LoginInput);
},
onSuccess: (response) => {
setToken(response.token);
onAuthenticated(response);
toast.success(
mode === 'register'
? 'Welcome to CodexDash.'
: 'Signed in successfully.',
);
},
onError: (error: Error) => toast.error(error.message),
});
return (
Mobile-first Codex monitor
CodexDash keeps every Codex account in one gorgeous live dashboard.
Sign into CodexDash, connect multiple OpenAI Codex accounts through a real login flow, and view combined limits,
remaining usage, raw API payloads, and per-account drilldowns from a single responsive UI.
{[
{
icon: Gauge,
title: 'Unified usage',
desc: 'Merge multiple OpenAI accounts into one overview.',
},
{
icon: ShieldCheck,
title: 'Stored safely',
desc: 'OAuth session data is encrypted at rest.',
},
{
icon: Activity,
title: 'Live detail',
desc: 'See refreshed usage plus raw usage payloads.',
},
].map((item) => (
))}
{mode === 'register' ? 'Create your account' : 'Welcome back'}
{mode === 'register'
? 'Start with your CodexDash account, then connect OpenAI Codex logins inside the dashboard.'
: 'Log in to continue monitoring your combined Codex usage.'}
OpenAI account connection now uses a real sign-in flow based on the Codex client OAuth pattern.
After you click connect, CodexDash opens OpenAI login in a popup and can also finish from a pasted callback URL if localhost is unavailable.
setMode(mode === 'register' ? 'login' : 'register')
}
>
{mode === 'register'
? 'Already have an account? Sign in'
: 'Need an account? Register'}
);
}
function ConnectAccountDialog() {
const queryClient = useQueryClient();
const [open, setOpen] = useState(false);
const [attemptId, setAttemptId] = useState(null);
const [authorizeUrl, setAuthorizeUrl] = useState(null);
const popupRef = useRef(null);
const handledAttemptStatusRef = useRef(null);
const form = useForm>({
resolver: zodResolver(connectSchema),
defaultValues: { label: '', emailHint: '' },
});
const manualCallbackForm = useForm>({
resolver: zodResolver(manualCallbackSchema),
defaultValues: { callbackUrl: '' },
});
const startMutation = useMutation({
mutationFn: api.startCodexLogin,
onSuccess: (response) => {
setAttemptId(response.attemptId);
setAuthorizeUrl(response.authorizeUrl);
manualCallbackForm.reset();
popupRef.current = window.open(
response.authorizeUrl,
'codexdash-openai-login',
'popup=yes,width=520,height=760',
);
if (!popupRef.current) {
toast.error('Popup was blocked. Use the fallback link inside the dialog.');
} else {
toast.success('Continue the OpenAI sign-in flow in the popup.');
}
},
onError: (error: Error) => toast.error(error.message),
});
const manualCompleteMutation = useMutation({
mutationFn: ({
callbackUrl,
currentAttemptId,
}: CompleteCodexManualLoginInput & { currentAttemptId: string }) =>
api.completeCodexManualLogin(currentAttemptId, { callbackUrl }),
onSuccess: () => {
toast.success('Processing the pasted OpenAI callback URL…');
void attemptQuery.refetch();
},
onError: (error: Error) => toast.error(error.message),
});
const attemptQuery = useQuery({
enabled: Boolean(attemptId),
queryKey: ['codex-login-attempt', attemptId],
queryFn: () => api.getCodexLoginAttempt(attemptId as string),
refetchInterval: (query) =>
query.state.data?.status === 'pending' ? 2_000 : false,
});
const cancelMutation = useMutation({
mutationFn: api.cancelCodexLoginAttempt,
onSuccess: () => {
toast.success('Login attempt cancelled.');
setAttemptId(null);
setAuthorizeUrl(null);
manualCallbackForm.reset();
popupRef.current?.close();
},
onError: (error: Error) => toast.error(error.message),
});
useEffect(() => {
const attempt = attemptQuery.data;
if (!attempt) {
return;
}
const statusKey = `${attempt.id}:${attempt.status}:${attempt.completedAt ?? ''}:${attempt.lastError ?? ''}`;
if (handledAttemptStatusRef.current === statusKey) {
return;
}
if (attempt.status === 'completed') {
handledAttemptStatusRef.current = statusKey;
window.setTimeout(() => {
toast.success('OpenAI Codex account connected.');
setOpen(false);
setAttemptId(null);
setAuthorizeUrl(null);
form.reset();
manualCallbackForm.reset();
popupRef.current?.close();
void queryClient.invalidateQueries({ queryKey: ['usage-summary'] });
}, 0);
return;
}
if (attempt.status === 'error' || attempt.status === 'expired') {
handledAttemptStatusRef.current = statusKey;
toast.error(attempt.lastError || 'OpenAI login failed.');
}
}, [attemptQuery.data, form, manualCallbackForm, queryClient]);
useEffect(() => {
function onMessage(event: MessageEvent) {
if (event.data?.type !== 'codexdash:oauth-complete') {
return;
}
if (event.data?.attemptId && event.data.attemptId === attemptId) {
void attemptQuery.refetch();
}
}
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, [attemptId, attemptQuery]);
const attempt = attemptQuery.data;
const isPendingAttempt = attempt?.status === 'pending';
return (
{
setOpen(next);
if (!next && isPendingAttempt && attemptId) {
cancelMutation.mutate(attemptId);
}
}}
>
Connect OpenAI account
Connect an OpenAI Codex account
Start a real OpenAI sign-in flow. CodexDash opens the official login,
completes the Codex-style OAuth callback locally, then stores the
encrypted session for future usage refreshes.
{attemptId ? (
Waiting for OpenAI login
{attempt?.status === 'completed'
? 'The login finished successfully. Closing this dialog…'
: 'Finish the sign-in flow in the popup window. CodexDash will detect the callback automatically.'}
Expires: {formatDate(attempt?.expiresAt ?? null)}
{authorizeUrl ? (
{
popupRef.current = window.open(
authorizeUrl,
'codexdash-openai-login',
'popup=yes,width=520,height=760',
);
}}
>
Re-open login popup
Open login in new tab
) : null}
Manual fallback
If localhost:1455 is not listening, OpenAI may finish on a browser error page.
Copy the full URL from the address bar and paste it below to complete the login manually.
{attempt?.lastError ? (
{attempt.lastError}
) : null}
void attemptQuery.refetch()}
>
Check status
attemptId && cancelMutation.mutate(attemptId)}
>
Cancel
) : (
)}
);
}
function Dashboard() {
const queryClient = useQueryClient();
const summaryQuery = useQuery({
queryKey: ['usage-summary'],
queryFn: () => api.getUsageSummary(true),
});
const userQuery = useQuery({ queryKey: ['me'], queryFn: api.me });
const deleteMutation = useMutation({
mutationFn: api.deleteAccount,
onSuccess: () => {
toast.success('OpenAI account removed.');
void queryClient.invalidateQueries({ queryKey: ['usage-summary'] });
},
onError: (error: Error) => toast.error(error.message),
});
const metricCards = useMemo(() => {
return flattenNumericMetrics(summaryQuery.data?.aggregatedUsage).slice(0, 6);
}, [summaryQuery.data?.aggregatedUsage]);
if (summaryQuery.isLoading || userQuery.isLoading) {
return (
Loading CodexDash…
);
}
if (summaryQuery.isError || userQuery.isError) {
return (
Unable to load dashboard
{(summaryQuery.error as Error | undefined)?.message ??
(userQuery.error as Error | undefined)?.message}
window.location.reload()}>Retry
);
}
const summary = summaryQuery.data!;
const user = userQuery.data!;
const firstMetric = metricCards[0]?.value ?? 0;
const secondMetric = metricCards[1]?.value ?? 0;
const progressValue =
firstMetric + secondMetric > 0
? (firstMetric / (firstMetric + secondMetric)) * 100
: 0;
return (
Signed in as {user.name}
CodexDash overview
Combined usage is refreshed by calling Codex usage endpoints for each attached OpenAI account and merging numeric fields into one dashboard.
summaryQuery.refetch()}>
Refresh now
{
clearToken();
window.location.reload();
}}
>
Sign out
Unified capacity
Fast glance card for the first two numeric metrics extracted from the merged usage payload.
{firstMetric.toLocaleString()}
{titleizeMetric(metricCards[0]?.label ?? 'Primary metric')}
{secondMetric.toLocaleString()}
{titleizeMetric(metricCards[1]?.label ?? 'Secondary metric')}
Accounts: {summary.totals.totalAccounts}
Healthy: {summary.totals.activeAccounts}
Errors: {summary.totals.erroredAccounts}
Updated: {formatDate(summary.refreshedAt)}
{[
{
title: 'Connected sessions',
value: summary.totals.totalAccounts,
tone: 'text-sky-300',
},
{
title: 'Healthy sessions',
value: summary.totals.activeAccounts,
tone: 'text-emerald-300',
},
{
title: 'Errored sessions',
value: summary.totals.erroredAccounts,
tone: 'text-rose-300',
},
].map((item) => (
{item.title}
{item.value.toLocaleString()}
))}
Usage metrics
CodexDash extracts numeric leaf nodes from the aggregated usage payload for quick overview cards.
{metricCards.length === 0 ? (
No usage data yet. Connect an OpenAI account and complete the sign-in flow to start refreshing.
) : (
{metricCards.map((metric) => (
{titleizeMetric(metric.label)}
{metric.value.toLocaleString()}
))}
)}
Connected OpenAI accounts
By default, these accounts are merged into one Codex usage view. Switch tabs to inspect individual account payloads and timestamps.
{summary.accounts.length === 0 ? (
No OpenAI accounts connected yet.
) : (
{summary.accounts.map((account) => (
{account.label}
))}
{summary.accounts.map((account) => (
{account.label}
{account.providerEmail ||
account.emailHint ||
'No email available yet'}
{account.status}
Auth: {account.authType}
Plan: {account.planType || 'Unknown'}
Provider account:{' '}
{account.providerAccountId || 'Unknown'}
Session expires: {formatDate(account.sessionExpiresAt)}
Last synced: {formatDate(account.lastSyncedAt)}
Connected: {formatDate(account.createdAt)}
Error:{' '}
{account.lastError || 'None'}
summaryQuery.refetch()}
>
Refresh
deleteMutation.mutate(account.id)}
>
Remove
))}
)}
);
}
export default function App() {
const [authenticated, setAuthenticated] = useState(Boolean(getToken()));
return (
{authenticated ? (
) : (
setAuthenticated(true)} />
)}
);
}