Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,8 @@ type AdminCodingPlanSubscriptionItem = {
canceledAt: string | null;
createdAt: string;
costKiloCredits: number;
inventoryKeyId: string | null;
upstreamPlanId: string | null;
};

const INVENTORY_STATUS_ORDER = [
Expand Down Expand Up @@ -1092,6 +1094,7 @@ function SubscriptionsTable({
<TableHead>User</TableHead>
<TableHead>Subscription</TableHead>
<TableHead>Provider / plan</TableHead>
<TableHead>Inventory / upstream</TableHead>
<TableHead>Status</TableHead>
<TableHead>Billing date</TableHead>
<TableHead className="text-right">Price</TableHead>
Expand All @@ -1101,13 +1104,13 @@ function SubscriptionsTable({
<TableBody>
{isError ? (
<TableRow>
<TableCell colSpan={7} className="h-24 text-center text-red-300">
<TableCell colSpan={8} className="h-24 text-center text-red-300">
Unable to load subscriptions.
</TableCell>
</TableRow>
) : items.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-muted-foreground h-24 text-center">
<TableCell colSpan={8} className="text-muted-foreground h-24 text-center">
{isLoading ? 'Loading subscriptions...' : 'No subscriptions recorded.'}
</TableCell>
</TableRow>
Expand All @@ -1121,6 +1124,8 @@ function SubscriptionsTable({
: formatDateLabel(billingDate.date);
const canCancel = item.status === 'active' && !item.cancelAtPeriodEnd;
const canExtend = item.status === 'active';
const inventoryKeyId = item.inventoryKeyId;
const upstreamPlanId = item.upstreamPlanId;

return (
<TableRow key={item.id}>
Expand Down Expand Up @@ -1153,6 +1158,44 @@ function SubscriptionsTable({
<div>{item.providerName}</div>
<div className="text-muted-foreground mt-1">{item.planName}</div>
</TableCell>
<TableCell className="min-w-72 font-mono text-xs">
<div className="flex items-center gap-2">
<span className="text-muted-foreground w-24 shrink-0 font-sans">
Inventory key
</span>
<span>{inventoryKeyId ?? '—'}</span>
{inventoryKeyId ? (
<Button
type="button"
variant="ghost"
size="icon"
className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
aria-label={`Copy inventory key ${inventoryKeyId}`}
onClick={() => void copyIdentifier(inventoryKeyId, 'Inventory key')}
>
<Copy className="size-3.5" />
</Button>
) : null}
</div>
<div className="mt-1 flex items-center gap-2">
<span className="text-muted-foreground w-24 shrink-0 font-sans">
Upstream plan
</span>
<span>{upstreamPlanId ?? '—'}</span>
{upstreamPlanId ? (
<Button
type="button"
variant="ghost"
size="icon"
className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
aria-label={`Copy upstream plan ${upstreamPlanId}`}
onClick={() => void copyIdentifier(upstreamPlanId, 'Upstream plan')}
>
<Copy className="size-3.5" />
</Button>
) : null}
</div>
</TableCell>
<TableCell>
<SubscriptionStatusBadge status={displayStatus} />
</TableCell>
Expand Down Expand Up @@ -1219,11 +1262,15 @@ function SubscriptionsTable({
}

async function copySubscriptionId(subscriptionId: string): Promise<void> {
await copyIdentifier(subscriptionId, 'Subscription ID');
}

async function copyIdentifier(value: string, label: string): Promise<void> {
try {
await navigator.clipboard.writeText(subscriptionId);
toast.success('Subscription ID copied');
await navigator.clipboard.writeText(value);
toast.success(`${label} copied`);
} catch {
toast.error('Failed to copy subscription ID');
toast.error(`Failed to copy ${label.toLowerCase()}`);
}
}

Expand Down
25 changes: 25 additions & 0 deletions apps/web/src/routers/coding-plans-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,31 @@ describe('coding plans router', () => {
expect(secondPage.pagination).toEqual({ page: 2, total: 23, totalPages: 2 });
});

it('includes inventory and upstream plan identifiers in admin subscriptions', async () => {
const admin = await insertTestUser({ is_admin: true });
const user = await insertTestUser();
const inventory = await insertInventory({
status: 'assigned',
assigned_to_user_id: user.id,
upstream_plan_id: 'upstream-plan-admin-display',
});
await db.insert(coding_plan_subscriptions).values(
subscriptionValues(user.id, {
key_inventory_id: inventory.id,
})
);
const caller = await createCallerForUser(admin.id);

const result = await caller.codingPlans.adminListSubscriptions({});

expect(result.items).toEqual([
expect.objectContaining({
inventoryKeyId: inventory.id,
upstreamPlanId: 'upstream-plan-admin-display',
}),
]);
});

it('searches admin subscriptions by user id and email substring', async () => {
const admin = await insertTestUser({ is_admin: true });
const emailUser = await insertTestUser({
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/routers/coding-plans-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ export const codingPlansRouter = createTRPCRouter({
const subscriptions = await db
.select({
...codingPlanSubscriptionColumns,
upstreamPlanId: coding_plan_key_inventory.upstream_plan_id,
userName: kilocode_users.google_user_name,
userEmail: kilocode_users.google_user_email,
})
Expand All @@ -624,6 +625,8 @@ export const codingPlansRouter = createTRPCRouter({
userId: subscription.userId,
userName: subscription.userName,
userEmail: subscription.userEmail,
inventoryKeyId: subscription.keyInventoryId,
upstreamPlanId: subscription.upstreamPlanId,
})),
pagination: { page, total, totalPages },
};
Expand Down