1import type { EnvironmentId, UsageLimitsReport } from "@t3tools/contracts";
2import { Pressable, ScrollView, useWindowDimensions, View } from "react-native";
3
4import { SymbolView } from "../../components/AppSymbol";
5import { AppText as Text } from "../../components/AppText";
6import { AccountLimits, ResetCredits } from "../usage/UsageLimitsSection";
7
8const DRIVER_LABEL: Partial<Record<string, string>> = { codex: "Codex", claudeAgent: "Claude" };
9
10/**
11 * The /usage-limits result, docked above the composer. It is the Usage → Limits
12 * card one size down, so the two read as the same thing. The surface is opaque
13 * because nothing blurs the feed behind it.
14 */
15export function ComposerUsageLimits({
16 report,
17 environmentId,
18 onClose,
19}: {
20 readonly report: UsageLimitsReport;
21 readonly environmentId: EnvironmentId;
22 readonly onClose: () => void;
23}) {
24 const now = Date.parse(report.createdAt);
25 const { height } = useWindowDimensions();
26 const close = (
27 <Pressable
28 accessibilityLabel="Dismiss usage limits"
29 accessibilityRole="button"
30 hitSlop={12}
31 onPress={onClose}
32 className="-me-1 p-1 active:opacity-60"
33 >
34 <SymbolView name="xmark" size={14} tintColorClassName="accent-icon-muted" type="monochrome" />
35 </Pressable>
36 );
37 return (
38 <View className="overflow-hidden rounded-[20px] border-continuous bg-card">
39 <ScrollView
40 bounces={false}
41 showsVerticalScrollIndicator={false}
42 style={{ maxHeight: Math.round(height * 0.4) }}
43 >
44 {report.accounts.map((account, index) => {
45 const resetCreditInput =
46 account.resetCreditInput ??
47 (account.instanceId ? { instanceId: account.instanceId } : undefined);
48 const driverLabel = DRIVER_LABEL[account.driver] ?? String(account.driver);
49 return (
50 <AccountLimits
51 key={account.id}
52 dense
53 first={index === 0}
54 driver={account.driver}
55 label={driverLabel}
56 // Siblings need telling apart: a custom instance without a name shows its
57 // id, and a pooled account shows its hub and account id.
58 instanceLabel={
59 account.instanceId
60 ? account.displayName?.trim() ||
61 (String(account.instanceId) !== String(account.driver)
62 ? account.instanceId
63 : driverLabel)
64 : account.label
65 }
66 detail={account.plan}
67 limits={account.limits}
68 now={now}
69 trailing={index === 0 ? close : undefined}
70 footer={
71 resetCreditInput && account.limits.resetCredits ? (
72 <ResetCredits
73 dense
74 environmentId={environmentId}
75 input={resetCreditInput}
76 credits={account.limits.resetCredits}
77 now={now}
78 />
79 ) : undefined
80 }
81 />
82 );
83 })}
84 {report.accounts.length === 0 ? (
85 // Nothing but notices, so the close control needs a row of its own.
86 <View className="flex-row items-center gap-3 px-4 pt-3">
87 <Text className="min-w-0 flex-1 text-base text-foreground">Usage limits</Text>
88 {close}
89 </View>
90 ) : null}
91 {report.notices.map((notice) => (
92 <Text
93 key={notice}
94 className={
95 report.accounts.length === 0
96 ? "px-4 py-3 text-xs text-foreground-muted"
97 : "border-t border-border-subtle px-4 py-3 text-xs text-foreground-muted"
98 }
99 >
100 {notice}
101 </Text>
102 ))}
103 </ScrollView>
104 </View>
105 );
106}