Skip to content

Comments

feat: admin tooling for orgs plus orgs billing#24197

Closed
sean-brydon wants to merge 27 commits intomainfrom
feat/admin-org-table-data-table
Closed

feat: admin tooling for orgs plus orgs billing#24197
sean-brydon wants to merge 27 commits intomainfrom
feat/admin-org-table-data-table

Conversation

@sean-brydon
Copy link
Member

@sean-brydon sean-brydon commented Oct 1, 2025

What does this PR do

This PR improve admin tooling for billing and organizations. Allows us to see the stripe history for any org + adds pagination and basic search to the organization table

Video demo: https://cap.so/s/55nfk6re5qxznzz

How to test

Login as admin@example.com
Goto admin/organizations
Search -> use table as normal etc
View an org with billing enabled
Profit

Before:
CleanShot 2025-10-01 at 09 22 49
CleanShot 2025-10-01 at 09 22 45

After
image
image

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 1, 2025

Walkthrough

Adds admin billing, metadata, and management features for teams and organizations. Introduces billing UI components (AdminBillingInfo, AdminPaymentHistory, AdminMetadata) and team/org wrappers (TeamBillingInfo/OrgBillingInfo, TeamPaymentHistory/OrgPaymentHistory, TeamMetadata/OrgMetadata). Adds data-table-driven admin list/edit UIs for teams and organizations, TRPC admin endpoints and zod schemas (adminGetAll, adminGet, adminGetBilling, adminUpdate, adminDelete, adminUpdateMetadata), server handlers (including StripeBillingService:getInvoices and getSubscription), a database seed script for organizations, and new Next.js admin pages and a "teams" admin tab.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title directly references the addition of administrative tooling for organizations and billing features, which aligns with the primary changes in the pull request. It clearly communicates that this PR introduces admin functionality for orgs and billing. Although the phrasing repeats “orgs,” it still concisely captures the essence of the changes.
Description Check ✅ Passed The description clearly outlines the enhancements made to admin tooling and billing features, including Stripe history visibility, pagination, and search capabilities for organizations. It provides testing steps and a demo link, ensuring reviewers understand the changes and how to verify them. Overall, it is directly related to the changes in the pull request.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/admin-org-table-data-table

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@keithwillcode keithwillcode added consumer core area: core, team members only labels Oct 1, 2025
@vercel
Copy link

vercel bot commented Oct 1, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Oct 22, 2025 10:42am
cal-eu Ignored Ignored Oct 22, 2025 10:42am

import { Badge } from "@calcom/ui/components/badge";
import { PanelCard } from "@calcom/ui/components/card";

type Metadata = Record<string, any>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO can probably use inferance from teamMetadataSchema here

}

const formatCurrency = (amount: number, currency: string) => {
return new Intl.NumberFormat("en-US", {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think en-US makes the most sense for us to default to in admin tooling, Regardless if the bills are in your local currency in dev

Comment on lines +50 to +51
limit,
offset,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use pagination here finally 💃

@sean-brydon sean-brydon marked this pull request as ready for review October 1, 2025 08:26
@graphite-app graphite-app bot requested a review from a team October 1, 2025 08:26
@dosubot dosubot bot added billing area: billing, stripe, payments, paypal, get paid ✨ feature New feature or request labels Oct 1, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/features/ee/billing/stripe-billling-service.ts (1)

8-10: Update Stripe API version.
The constructor’s apiVersion should be bumped from "2020-08-27" to the latest stable version "2025-08-27.basil".

- apiVersion: "2020-08-27",
+ apiVersion: "2025-08-27.basil",
packages/trpc/server/routers/viewer/organizations/adminGetAll.handler.ts (1)

18-84: Wire column filters into the Prisma query

input.filters is ignored, so every column filter configured in the data table UI silently does nothing. We already import makeWhereClause/TypedColumnFilter; use them (or equivalent) to merge the filters into whereClause so the admin filters behave. Otherwise the new filtering UI is non-functional.

🧹 Nitpick comments (7)
scripts/seed-organizations.ts (1)

42-61: Consider wrapping organization creation in a transaction.

The organization and organizationSettings are created in separate operations. If the second operation fails, you'll have an organization without settings, which may cause issues downstream.

Consider using a Prisma transaction:

const organization = await prisma.$transaction(async (tx) => {
  return await tx.team.create({
    data: {
      name: `Test Organization ${i}`,
      slug: isPublished ? slug : null,
      isOrganization: true,
      metadata: {
        isOrganization: true,
        requestedSlug: slug,
      },
      organizationSettings: {
        create: {
          isOrganizationVerified: true,
          orgAutoAcceptEmail: `test-org-${i}.com`,
          isAdminAPIEnabled: hasAdminApi,
          isAdminReviewed: isReviewed,
          isOrganizationConfigured: isDnsConfigured,
        },
      },
    },
  });
});
packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx (1)

104-104: Prefer named exports over default exports.

The default export should be removed in favor of only using named exports. This provides better tree-shaking, easier refactoring, and clearer imports.

As per coding guidelines.

-export default OrgForm;
packages/features/ee/organizations/components/OrgMetadata.tsx (2)

7-7: Use stricter type inference from teamMetadataSchema.

The Metadata type is defined as Record<string, any> which loses type safety. Consider inferring the type from teamMetadataSchema for better type safety and intellisense.

Based on past review comments.

+import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
+import type { z } from "zod";
+
-type Metadata = Record<string, any>;
+type Metadata = z.infer<typeof teamMetadataSchema>;

32-32: Incomplete URL detection.

The check value.startsWith("http") only matches "http" and "https" but doesn't account for protocol-relative URLs or other schemes. Consider using a more robust check.

-if (typeof value === "string" && value.startsWith("http")) {
+if (typeof value === "string" && /^https?:\/\//i.test(value)) {
packages/trpc/server/routers/viewer/organizations/adminGetBilling.handler.ts (3)

20-20: Avoid any type for invoices.

The invoices array is typed as any[], which loses type safety. Consider importing Stripe types or defining an interface for the invoice structure.

+import type Stripe from "stripe";
+
-let invoices: any[] = [];
+let invoices: Stripe.Invoice[] = [];

37-39: Improve error handling and observability.

Using console.error for error logging is insufficient for production. Errors are silently swallowed, making debugging difficult. Consider using a proper logging service and potentially surfacing critical errors to the caller.

    } catch (error) {
-     console.error("Error fetching subscription:", error);
+     logger.error("Error fetching subscription", { error, orgId: input.id, subscriptionId: stripeSubscriptionId });
+     // Consider throwing or returning error info for critical failures
    }

Similar changes should be applied to the other catch blocks at lines 44-46 and 53-55.

Also applies to: 44-46, 53-55


90-90: Prefer named exports over default exports.

The default export should be removed in favor of only using named exports for better tree-shaking and clearer imports.

As per coding guidelines.

-export default adminGetBillingHandler;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 43cc40d and af6c865.

📒 Files selected for processing (13)
  • packages/features/ee/billing/stripe-billling-service.ts (1 hunks)
  • packages/features/ee/organizations/components/OrgBillingInfo.tsx (1 hunks)
  • packages/features/ee/organizations/components/OrgMetadata.tsx (1 hunks)
  • packages/features/ee/organizations/components/OrgPaymentHistory.tsx (1 hunks)
  • packages/features/ee/organizations/components/index.ts (1 hunks)
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx (2 hunks)
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx (4 hunks)
  • packages/trpc/server/routers/viewer/organizations/_router.tsx (2 hunks)
  • packages/trpc/server/routers/viewer/organizations/adminGetAll.handler.ts (3 hunks)
  • packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/adminGetBilling.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/adminGetBilling.schema.ts (1 hunks)
  • scripts/seed-organizations.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/ee/organizations/components/OrgMetadata.tsx
  • packages/features/ee/organizations/components/OrgPaymentHistory.tsx
  • packages/features/ee/organizations/components/OrgBillingInfo.tsx
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/ee/organizations/components/OrgMetadata.tsx
  • packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts
  • packages/features/ee/organizations/components/OrgPaymentHistory.tsx
  • packages/features/ee/billing/stripe-billling-service.ts
  • packages/features/ee/organizations/components/OrgBillingInfo.tsx
  • packages/features/ee/organizations/components/index.ts
  • scripts/seed-organizations.ts
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx
  • packages/trpc/server/routers/viewer/organizations/adminGetAll.handler.ts
  • packages/trpc/server/routers/viewer/organizations/adminGetBilling.schema.ts
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • packages/trpc/server/routers/viewer/organizations/adminGetBilling.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/ee/organizations/components/OrgMetadata.tsx
  • packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts
  • packages/features/ee/organizations/components/OrgPaymentHistory.tsx
  • packages/features/ee/billing/stripe-billling-service.ts
  • packages/features/ee/organizations/components/OrgBillingInfo.tsx
  • packages/features/ee/organizations/components/index.ts
  • scripts/seed-organizations.ts
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx
  • packages/trpc/server/routers/viewer/organizations/adminGetAll.handler.ts
  • packages/trpc/server/routers/viewer/organizations/adminGetBilling.schema.ts
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • packages/trpc/server/routers/viewer/organizations/adminGetBilling.handler.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts
  • packages/features/ee/billing/stripe-billling-service.ts
  • packages/features/ee/organizations/components/index.ts
  • scripts/seed-organizations.ts
  • packages/trpc/server/routers/viewer/organizations/adminGetAll.handler.ts
  • packages/trpc/server/routers/viewer/organizations/adminGetBilling.schema.ts
  • packages/trpc/server/routers/viewer/organizations/adminGetBilling.handler.ts
🧬 Code graph analysis (10)
packages/features/ee/organizations/components/OrgMetadata.tsx (1)
packages/features/ee/organizations/components/index.ts (1)
  • OrgMetadata (7-7)
packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts (1)
packages/features/data-table/lib/types.ts (1)
  • ZFilterValue (109-115)
packages/features/ee/organizations/components/OrgPaymentHistory.tsx (3)
packages/features/ee/organizations/components/index.ts (1)
  • OrgPaymentHistory (6-6)
packages/trpc/react/trpc.ts (1)
  • trpc (54-138)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
packages/features/ee/organizations/components/OrgBillingInfo.tsx (2)
packages/trpc/react/trpc.ts (1)
  • trpc (54-138)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
scripts/seed-organizations.ts (1)
packages/platform/libraries/index.ts (1)
  • MembershipRole (34-34)
packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx (3)
packages/features/ee/organizations/components/OrgBillingInfo.tsx (1)
  • OrgBillingInfo (10-145)
packages/features/ee/organizations/components/OrgPaymentHistory.tsx (1)
  • OrgPaymentHistory (9-120)
packages/features/ee/organizations/components/OrgMetadata.tsx (1)
  • OrgMetadata (9-63)
packages/features/ee/organizations/pages/settings/admin/AdminOrgPage.tsx (5)
packages/trpc/react/trpc.ts (2)
  • RouterOutputs (143-143)
  • trpc (54-138)
packages/features/data-table/DataTableProvider.tsx (1)
  • DataTableProvider (93-426)
packages/features/data-table/hooks/useSegments.ts (1)
  • useSegments (9-66)
packages/features/ee/organizations/lib/orgDomains.ts (1)
  • subdomainSuffix (138-145)
packages/features/data-table/components/DataTableWrapper.tsx (1)
  • DataTableWrapper (44-154)
packages/trpc/server/routers/viewer/organizations/adminGetAll.handler.ts (1)
packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts (1)
  • TAdminGetAllSchema (17-17)
packages/trpc/server/routers/viewer/organizations/_router.tsx (3)
packages/trpc/server/procedures/authedProcedure.ts (1)
  • authedAdminProcedure (29-29)
packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts (1)
  • ZAdminGetAllInputSchema (10-15)
packages/trpc/server/routers/viewer/organizations/adminGetBilling.schema.ts (1)
  • ZAdminGetBilling (3-5)
packages/trpc/server/routers/viewer/organizations/adminGetBilling.handler.ts (3)
packages/trpc/server/routers/viewer/organizations/adminGetBilling.schema.ts (1)
  • TAdminGetBilling (7-7)
packages/lib/server/repository/organization.ts (1)
  • OrganizationRepository (23-479)
packages/features/ee/billing/stripe-billling-service.ts (1)
  • StripeBillingService (5-212)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Production builds / Build Atoms
  • GitHub Check: Production builds / Build Web App
  • GitHub Check: Production builds / Build API v1
  • GitHub Check: Tests / Unit
  • GitHub Check: Production builds / Build API v2
  • GitHub Check: Type check / check-types
  • GitHub Check: Linters / lint
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (15)
packages/features/ee/organizations/components/index.ts (1)

5-7: LGTM! New component exports added correctly.

The new billing-related component exports follow the existing pattern and use named exports as per coding guidelines.

packages/features/ee/billing/stripe-billling-service.ts (2)

200-206: LGTM! Invoice retrieval method is correctly implemented.

The getInvoices method properly calls the Stripe API with customer ID and limit, returning the invoice data array.


208-211: LGTM! Subscription retrieval method is correctly implemented.

The getSubscription method properly retrieves a subscription by ID and returns it.

packages/trpc/server/routers/viewer/organizations/_router.tsx (3)

14-15: LGTM! Schema imports added correctly.

The new schema imports for input validation are properly added.


140-143: LGTM! Input validation added to adminGetAll route.

The route now properly validates input using the new schema, enabling type-safe pagination and filtering.


148-151: LGTM! New adminGetBilling route correctly wired.

The new route follows the established pattern with proper input validation, admin authorization, and handler import.

packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx (1)

70-100: Well-structured panel layout with billing components.

The refactor to use PanelCard and the addition of billing and metadata sections provides a clean, organized UI. The integration of OrgBillingInfo, OrgPaymentHistory, and OrgMetadata components follows good composition patterns.

packages/features/ee/organizations/components/OrgMetadata.tsx (1)

18-44: Well-designed value renderer with type-aware formatting.

The renderValue function provides thoughtful type-aware rendering for different metadata value types (null, booleans, objects, links, strings). The approach is clean and user-friendly.

packages/trpc/server/routers/viewer/organizations/adminGetBilling.schema.ts (1)

3-7: Clean and correct schema definition.

The schema is simple, well-typed, and follows best practices for TRPC input validation.

packages/trpc/server/routers/viewer/organizations/adminGetBilling.handler.ts (3)

58-87: Well-structured return object mapping Stripe data.

The return object provides a clean mapping of Stripe billing data with appropriate field transformations (snake_case to camelCase). The invoice mapping is comprehensive.


16-17: No changes needed: adminFindById uses Prisma select exclusively.


15-16: Authorization already enforced via authedAdminProcedureadminGetBilling is defined with authedAdminProcedure, which applies isAdminMiddleware to verify admin privileges. No additional check needed here.

Likely an incorrect or invalid review comment.

packages/features/ee/organizations/components/OrgPaymentHistory.tsx (3)

34-39: Currency formatting implementation is correct.

The formatCurrency helper correctly uses Intl.NumberFormat with en-US locale and divides by 100 to convert Stripe's cent-based amounts. The hardcoded en-US locale is appropriate for admin tooling as noted in past review comments.

Based on past review comments.


41-55: Clean status-to-color mapping.

The getStatusColor helper provides a clear mapping of invoice statuses to badge variants with good coverage of Stripe's invoice states.


60-116: Well-structured invoice list with comprehensive details.

The invoice list provides excellent UX with status badges, formatted dates/amounts, conditional display of partial payments, and action buttons for viewing/downloading invoices. The hover effect and layout are polished.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 1, 2025

E2E results are ready!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx (2)

69-69: Localize hardcoded strings.

The PanelCard title "Basic Information" and subtitle "Organization name, slug, and settings" are hardcoded and should use the t() function for proper localization.

As per coding guidelines.


73-76: Localize hardcoded warning text.

The warning paragraph text about slug changes is hardcoded and should use the t() function for proper localization.

As per coding guidelines.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between af6c865 and d907fd5.

📒 Files selected for processing (2)
  • packages/features/ee/organizations/components/OrgBillingInfo.tsx (1 hunks)
  • packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/features/ee/organizations/components/OrgBillingInfo.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx
🧬 Code graph analysis (1)
packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx (3)
packages/features/ee/organizations/components/OrgBillingInfo.tsx (1)
  • OrgBillingInfo (10-145)
packages/features/ee/organizations/components/OrgPaymentHistory.tsx (1)
  • OrgPaymentHistory (9-120)
packages/features/ee/organizations/components/OrgMetadata.tsx (1)
  • OrgMetadata (9-63)
🔇 Additional comments (1)
packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx (1)

67-97: Well-structured component layout with proper separation of concerns.

The refactored layout effectively organizes the admin edit page into clear sections using PanelCard components. The integration of OrgBillingInfo, OrgPaymentHistory, and OrgMetadata provides a comprehensive view of organization data while maintaining clean component boundaries.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/features/ee/organizations/components/OrgBillingInfo.tsx (1)

125-132: Consider explicit date formatting for admin consistency.

The dates use toLocaleDateString(), which varies by user locale. For admin tooling, consider an explicit format (e.g., toLocaleDateString('en-US', { dateStyle: 'medium' }) or ISO format) to ensure consistent date display across different admin users and regions.

Apply this diff for consistent US date formatting:

-                {new Date(billingData.subscriptionDetails.currentPeriodStart * 1000).toLocaleDateString()}
+                {new Date(billingData.subscriptionDetails.currentPeriodStart * 1000).toLocaleDateString('en-US', { dateStyle: 'medium' })}
-                {new Date(billingData.subscriptionDetails.currentPeriodEnd * 1000).toLocaleDateString()}
+                {new Date(billingData.subscriptionDetails.currentPeriodEnd * 1000).toLocaleDateString('en-US', { dateStyle: 'medium' })}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d907fd5 and 18363a5.

📒 Files selected for processing (1)
  • packages/features/ee/organizations/components/OrgBillingInfo.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/ee/organizations/components/OrgBillingInfo.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/ee/organizations/components/OrgBillingInfo.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/ee/organizations/components/OrgBillingInfo.tsx
🧬 Code graph analysis (1)
packages/features/ee/organizations/components/OrgBillingInfo.tsx (2)
packages/trpc/react/trpc.ts (1)
  • trpc (54-138)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (4)
packages/features/ee/organizations/components/OrgBillingInfo.tsx (4)

1-13: LGTM: Clean component setup with proper named export.

The component structure follows best practices with a named export and standard TRPC query setup.


37-40: LGTM: Safe Stripe URL construction.

The URL construction correctly handles both required and optional Stripe resource IDs.


67-76: LGTM: Badge logic correctly handles subscription states.

The status badge appropriately maps active → green, canceled → red, with gray as a sensible fallback for other Stripe subscription states.


93-116: LGTM: Well-structured conditional rendering.

The billing details section correctly uses defensive checks to ensure it only renders when relevant data exists, with granular field-level conditionals inside.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/features/teams/components/TeamMetadata.tsx (1)

1-6: Switch Zod import to type-only

teamMetadataSchema is only used for z.infer, so the runtime z import keeps dead code in the bundle. Use a type-only import instead.

-import { z } from "zod";
+import type { z } from "zod";
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5cdec and b7b2019.

📒 Files selected for processing (6)
  • packages/features/billing/components/AdminMetadata.tsx (1 hunks)
  • packages/features/billing/components/AdminPaymentHistory.tsx (1 hunks)
  • packages/features/ee/organizations/components/OrgMetadata.tsx (1 hunks)
  • packages/features/teams/components/TeamMetadata.tsx (1 hunks)
  • packages/features/teams/pages/admin/AdminTeamEditPage.tsx (1 hunks)
  • packages/features/teams/pages/admin/AdminTeamPage.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/features/teams/pages/admin/AdminTeamPage.tsx
  • packages/features/ee/organizations/components/OrgMetadata.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/teams/components/TeamMetadata.tsx
  • packages/features/billing/components/AdminPaymentHistory.tsx
  • packages/features/billing/components/AdminMetadata.tsx
  • packages/features/teams/pages/admin/AdminTeamEditPage.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/teams/components/TeamMetadata.tsx
  • packages/features/billing/components/AdminPaymentHistory.tsx
  • packages/features/billing/components/AdminMetadata.tsx
  • packages/features/teams/pages/admin/AdminTeamEditPage.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/teams/components/TeamMetadata.tsx
  • packages/features/billing/components/AdminPaymentHistory.tsx
  • packages/features/billing/components/AdminMetadata.tsx
  • packages/features/teams/pages/admin/AdminTeamEditPage.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-12T07:17:03.512Z
Learnt from: hbjORbj
PR: calcom/cal.com#23475
File: packages/lib/payment/shouldChargeNoShowCancellationFee.ts:3-3
Timestamp: 2025-09-12T07:17:03.512Z
Learning: When using `z.infer<typeof ZodSchema>` pattern in TypeScript, the schema can be imported as a type-only import because the `typeof` operation happens at the type level during compilation, not at runtime. This is a common and valid pattern in Zod-based codebases.

Applied to files:

  • packages/features/billing/components/AdminMetadata.tsx
🧬 Code graph analysis (4)
packages/features/teams/components/TeamMetadata.tsx (1)
packages/features/billing/components/AdminMetadata.tsx (1)
  • AdminMetadata (26-315)
packages/features/billing/components/AdminPaymentHistory.tsx (2)
packages/trpc/react/trpc.ts (1)
  • RouterOutputs (143-143)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
packages/features/billing/components/AdminMetadata.tsx (4)
packages/features/billing/components/index.ts (1)
  • AdminMetadata (3-3)
packages/ui/components/form/inputs/TextField.tsx (1)
  • TextField (234-236)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
packages/ui/components/dialog/Dialog.tsx (1)
  • Dialog (35-38)
packages/features/teams/pages/admin/AdminTeamEditPage.tsx (6)
packages/trpc/react/trpc.ts (1)
  • trpc (54-138)
packages/ui/components/form/inputs/TextField.tsx (1)
  • TextField (234-236)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
packages/features/teams/components/TeamBillingInfo.tsx (1)
  • TeamBillingInfo (6-12)
packages/features/teams/components/TeamPaymentHistory.tsx (1)
  • TeamPaymentHistory (6-12)
packages/features/teams/components/TeamMetadata.tsx (1)
  • TeamMetadata (8-10)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Production builds / Build Docs
  • GitHub Check: Production builds / Build Web App
  • GitHub Check: Production builds / Build API v1
  • GitHub Check: Production builds / Build Atoms
  • GitHub Check: Tests / Unit
  • GitHub Check: Production builds / Build API v2
  • GitHub Check: Linters / lint
  • GitHub Check: Type check / check-types

Comment on lines 22 to 116
<PanelCard title="Payment History">
<div className="p-4 space-y-3">
<SkeletonText className="h-4 w-full" />
<SkeletonText className="h-4 w-full" />
<SkeletonText className="h-4 w-full" />
</div>
</PanelCard>
);
}

if (!billingData?.invoices || billingData.invoices.length === 0) {
return (
<PanelCard title="Payment History">
<div className="p-4 text-subtle text-sm">No payment history available for this {entityType}.</div>
</PanelCard>
);
}

const formatCurrency = (amount: number, currency: string) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency.toUpperCase(),
}).format(amount / 100);
};

const getStatusColor = (status: string) => {
switch (status) {
case "paid":
return "green";
case "open":
return "blue";
case "draft":
return "gray";
case "uncollectible":
case "void":
return "red";
default:
return "gray";
}
};

return (
<PanelCard title="Payment History" subtitle={`${billingData.invoices.length} recent invoices`}>
<div className="divide-y divide-subtle">
{billingData.invoices.map((invoice) => (
<div key={invoice.id} className="p-4 hover:bg-subtle transition-colors">
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="text-sm font-medium text-emphasis">
{invoice.number || invoice.id}
</span>
<Badge variant={getStatusColor(invoice.status || "draft")}>{invoice.status}</Badge>
</div>
<div className="text-xs text-subtle">
{new Date(invoice.created * 1000).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<div className="text-sm font-semibold text-emphasis">
{formatCurrency(invoice.amountDue, invoice.currency)}
</div>
{invoice.amountPaid > 0 && invoice.amountPaid !== invoice.amountDue && (
<div className="text-xs text-subtle">
Paid: {formatCurrency(invoice.amountPaid, invoice.currency)}
</div>
)}
</div>
<div className="flex gap-2">
{invoice.hostedInvoiceUrl && (
<Button
color="minimal"
size="sm"
StartIcon="external-link"
href={invoice.hostedInvoiceUrl}
target="_blank"
rel="noopener noreferrer">
View
</Button>
)}
{invoice.invoicePdf && (
<Button
color="minimal"
size="sm"
StartIcon="download"
href={invoice.invoicePdf}
target="_blank"
rel="noopener noreferrer">
PDF
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Localize all user-facing strings

Every literal here ("Payment History", "No payment history...", dates, "Paid", "View", "PDF", etc.) bypasses t(). Our TSX guideline requires routing panel titles, subtitles, empty-state copy, status badges, and button labels through t().

Please import useLocale, grab { t }, and wrap each visible string with translation keys, including interpolations like the invoice count and entity name. [coding_guidelines]

🤖 Prompt for AI Agents
In packages/features/billing/components/AdminPaymentHistory.tsx around lines 22
to 116, the file uses hard-coded user-facing strings; import useLocale from the
locale hooks, get const { t } = useLocale(), and replace all visible literals
with t() keys (e.g. t('billing.paymentHistory.title') for "Payment History",
t('billing.paymentHistory.empty', { entity: entityType }) for the empty-state,
t('billing.paymentHistory.subtitle', { count: billingData.invoices.length }) for
the subtitle, t('billing.paymentHistory.paid') for "Paid", t('common.view') for
"View", t('common.pdf') for "PDF", and translate status labels via
t('billing.status.paid') etc. For date formatting keep the toLocaleDateString
call but wrap any accompanying label text with t(); ensure interpolation values
(count, entity) are passed as params to t() and add the corresponding
translation keys to your locale files.

Comment on lines +66 to +89
<PanelCard title="Basic Information" subtitle="Team name and slug">
<Form form={form} className="space-y-4 p-4" handleSubmit={onSubmit}>
<TextField label="Name" placeholder="My Team" required {...form.register("name")} />
<TextField label="Slug" placeholder="my-team" required {...form.register("slug")} />
{team.parentId && (
<p className="text-default mt-2 text-sm">
This team belongs to an organization. Some settings may be managed at the organization level.
</p>
)}
<Button type="submit" color="primary" loading={mutation.isPending}>
{t("save")}
</Button>
</Form>
</PanelCard>

{/* Stripe & Billing Section */}
<TeamBillingInfo teamId={team.id} />

{/* Payment History Section */}
<TeamPaymentHistory teamId={team.id} />

{/* Metadata Section */}
<TeamMetadata metadata={team.metadata} />
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace hard-coded copy with t()

Section titles, subtitles, field labels/placeholders, helper text, and status text here ("Basic Information", "Team name and slug", "Name", "Slug", "My Team", "This team belongs...") violate our localization rule for TSX. Import t() (already available) and wrap every user-visible string so the admin UI stays translatable. [coding_guidelines]

- Fix AdminMetadata.tsx type narrowing for nullable metadata
- Add proper type casting for Object.entries() result
- Fix property access using keyof typeof for type safety
- Convert oldValue to string before rendering to fix React children type error
- Add Metadata type import in AdminTeamEditPage.tsx
- Update team prop types to use parsed Metadata type instead of raw Prisma JSON type

This resolves all 6 type errors introduced in this PR while maintaining type safety without using 'as any' casting.

Co-Authored-By: sean@cal.com <Sean@brydon.io>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
packages/features/teams/pages/admin/AdminTeamEditPage.tsx (1)

70-82: Replace hard-coded strings with localized text.

The section title, subtitle, field labels, placeholders, and helper text remain hard-coded. As flagged in previous reviews, all user-facing strings must use t() for localization to keep the admin UI translatable. [coding_guidelines]

Apply localization to all user-facing strings:

-      <PanelCard title="Basic Information" subtitle="Team name and slug">
+      <PanelCard title={t("basic_information")} subtitle={t("team_name_and_slug")}>
         <Form form={form} className="space-y-4 p-4" handleSubmit={onSubmit}>
-          <TextField label="Name" placeholder="My Team" required {...form.register("name")} />
+          <TextField label={t("name")} placeholder={t("my_team")} required {...form.register("name")} />
-          <TextField label="Slug" placeholder="my-team" required {...form.register("slug")} />
+          <TextField label={t("slug")} placeholder={t("my_team_slug")} required {...form.register("slug")} />
           {team.parentId && (
             <p className="text-default mt-2 text-sm">
-              This team belongs to an organization. Some settings may be managed at the organization level.
+              {t("team_belongs_to_organization_notice")}
             </p>
           )}
packages/features/billing/components/AdminMetadata.tsx (2)

70-239: Localize all user-facing strings.

All visible text ("Metadata", "No metadata available.", "null", "true", "false", "Editable", "Edit", field helper text, etc.) must be routed through t() per the TSX coding guideline. Import useLocale() and wrap each literal.

[coding_guidelines]


241-312: Localize dialog and floating panel strings.

Confirmation dialog text ("Confirm Metadata Changes", "Save Changes", "You are about to update...", etc.) and floating panel messages need t() wrappers per coding guidelines.

[coding_guidelines]

🧹 Nitpick comments (5)
packages/features/teams/pages/admin/AdminTeamEditPage.tsx (2)

40-65: Add Zod validation schema for robust form validation.

The form uses react-hook-form but lacks a validation schema. While HTML required attributes provide basic validation, a Zod schema would enable comprehensive validation (e.g., slug format, name length limits) and prevent invalid data from reaching the mutation.

Consider adding a schema and resolver:

+import { zodResolver } from "@hookform/resolvers/zod";
+
+const teamFormSchema = z.object({
+  name: z.string().min(1, "Name is required"),
+  slug: z.string().regex(/^[a-z0-9-]+$/, "Invalid slug format").optional().or(z.literal("")),
+  metadata: teamMetadataSchema,
+});
+
 const form = useForm<FormValues>({
+  resolver: zodResolver(teamFormSchema),
   defaultValues: {
     name: team.name,
     slug: team.slug || "",
     metadata: team.metadata,
   },
 });

25-110: Consider extracting shared team prop type.

Both TeamForm and AdminTeamEditPage duplicate the same team prop type definition. Extracting this to a shared type would reduce duplication and improve maintainability.

Example refactor:

+type TeamFormProps = {
+  team: {
+    id: number;
+    name: string;
+    slug: string | null;
+    metadata: Metadata;
+    isOrganization: boolean;
+    parentId: number | null;
+  };
+};
+
-export const TeamForm = ({
-  team,
-}: {
-  team: {
-    id: number;
-    name: string;
-    slug: string | null;
-    metadata: Metadata;
-    isOrganization: boolean;
-    parentId: number | null;
-  };
-}) => {
+export const TeamForm = ({ team }: TeamFormProps) => {
   // ... rest of component
 };

-export const AdminTeamEditPage = ({
-  team,
-}: {
-  team: {
-    id: number;
-    name: string;
-    slug: string | null;
-    metadata: Metadata;
-    isOrganization: boolean;
-    parentId: number | null;
-  };
-}) => {
+export const AdminTeamEditPage = ({ team }: TeamFormProps) => {
   return <TeamForm team={team} />;
 };
packages/features/billing/components/AdminMetadata.tsx (3)

38-50: Consider server-structured error responses.

The string-based error parsing is fragile—if server error messages change format, this function silently fails to extract field-level errors. Consider having the server return structured validation errors (e.g., { field: "subscriptionId", message: "Must start with 'sub_'" }) so you can map them directly without regex or substring matching.


52-68: Client-side validation mirrors server logic.

This validation duplicates server-side rules. While client-side validation improves UX by providing immediate feedback, ensure the server remains the source of truth. If validation rules evolve, both locations need updates.


241-267: Consider accessibility enhancements for the floating panel.

The fixed-position action panel could benefit from:

  • Keyboard focus trap while editing (Esc to cancel, Enter to save when appropriate)
  • ARIA attributes (role="dialog", aria-label)
  • Focus management when entering/exiting edit mode

These improvements would make the editing workflow more accessible to keyboard and screen reader users.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b7b2019 and 914d542.

📒 Files selected for processing (2)
  • packages/features/billing/components/AdminMetadata.tsx (1 hunks)
  • packages/features/teams/pages/admin/AdminTeamEditPage.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/teams/pages/admin/AdminTeamEditPage.tsx
  • packages/features/billing/components/AdminMetadata.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/teams/pages/admin/AdminTeamEditPage.tsx
  • packages/features/billing/components/AdminMetadata.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/teams/pages/admin/AdminTeamEditPage.tsx
  • packages/features/billing/components/AdminMetadata.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-12T07:17:03.512Z
Learnt from: hbjORbj
PR: calcom/cal.com#23475
File: packages/lib/payment/shouldChargeNoShowCancellationFee.ts:3-3
Timestamp: 2025-09-12T07:17:03.512Z
Learning: When using `z.infer<typeof ZodSchema>` pattern in TypeScript, the schema can be imported as a type-only import because the `typeof` operation happens at the type level during compilation, not at runtime. This is a common and valid pattern in Zod-based codebases.

Applied to files:

  • packages/features/billing/components/AdminMetadata.tsx
📚 Learning: 2025-09-12T07:17:03.512Z
Learnt from: hbjORbj
PR: calcom/cal.com#23475
File: packages/lib/payment/shouldChargeNoShowCancellationFee.ts:3-3
Timestamp: 2025-09-12T07:17:03.512Z
Learning: When using `z.infer<typeof ZodSchema>` in TypeScript, the `typeof` is a type-level operator resolved at compile time, not a runtime operation. This means the schema can be imported as `import type` because the `typeof` computation happens entirely within TypeScript's type system during compilation.

Applied to files:

  • packages/features/billing/components/AdminMetadata.tsx
🧬 Code graph analysis (2)
packages/features/teams/pages/admin/AdminTeamEditPage.tsx (4)
packages/trpc/react/trpc.ts (1)
  • trpc (54-138)
packages/features/teams/components/TeamBillingInfo.tsx (1)
  • TeamBillingInfo (6-12)
packages/features/teams/components/TeamPaymentHistory.tsx (1)
  • TeamPaymentHistory (6-12)
packages/features/teams/components/TeamMetadata.tsx (1)
  • TeamMetadata (8-10)
packages/features/billing/components/AdminMetadata.tsx (4)
packages/features/billing/components/index.ts (1)
  • AdminMetadata (3-3)
packages/ui/components/form/inputs/TextField.tsx (1)
  • TextField (234-236)
packages/ui/components/button/Button.tsx (1)
  • Button (221-349)
packages/ui/components/dialog/Dialog.tsx (1)
  • Dialog (35-38)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (3)
packages/features/teams/pages/admin/AdminTeamEditPage.tsx (1)

1-23: LGTM!

The imports and type definitions are clean and well-structured.

packages/features/billing/components/AdminMetadata.tsx (2)

1-24: LGTM!

The imports and type definitions are correctly structured. The Zod import pattern with z.infer<typeof teamMetadataSchema> is properly implemented, and the type-only import for the schema is appropriate.


83-133: LGTM!

The event handlers are well-structured with proper state management, validation flow, and error handling. The async update pattern with loading states and toast notifications follows good practices.

Copy link
Contributor

@eunjae-lee eunjae-lee Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, but something like this might be cleaner to maintain

const allowedMetadataSchema = z.object({
  paymentId: z.string().optional(),
  subscriptionId: z.string().transform(val => val === "" ? null : val).optional(),
  subscriptionItemId: z.string().transform(val => val === "" ? null : val).optional(),
}).strict();

const errors: Record<string, string> = {};

Object.entries(editedValues).forEach(([key, value]) => {
if (value && value.trim()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use zod schema to validate and provide error messages instead of this?

Copy link
Contributor

@eunjae-lee eunjae-lee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed and tested. left some comments.

Copy link
Contributor

@eunjae-lee eunjae-lee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type error and conflicts

@eunjae-lee eunjae-lee marked this pull request as draft October 21, 2025 08:45
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

13 issues found across 40 files

Prompt for AI agents (all 13 issues)

Understand the root cause of the following 13 issues and fix them.


<file name="packages/features/billing/components/AdminBillingInfo.tsx">

<violation number="1" location="packages/features/billing/components/AdminBillingInfo.tsx:23">
Wrap this user-visible string with the t() localization helper and add the corresponding translation key so it participates in i18n.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/teams/adminGetAll.schema.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/teams/adminGetAll.schema.ts:4">
Please ensure pagination inputs are validated as integers; without .int() the schema allows decimal values that will break downstream skip/limit handling.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/teams/adminDelete.handler.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/teams/adminDelete.handler.ts:14">
Limit the delete response so Prisma only returns the fields the admin tooling needs instead of the entire team record.</violation>
</file>

<file name="packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx">

<violation number="1" location="packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx:69">
Please wrap the new PanelCard title and subtitle in t() (with appropriate keys) so the admin UI stays localized.</violation>
</file>

<file name="packages/features/billing/components/AdminPaymentHistory.tsx">

<violation number="1" location="packages/features/billing/components/AdminPaymentHistory.tsx:64">
Wrap the user-facing strings here with t() so they participate in our localization flow instead of hard-coding English text.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/teams/adminUpdate.handler.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/teams/adminUpdate.handler.ts:16">
This prisma.team.update() call returns the full team record to the API consumer; please add a select to limit the fields exposed and avoid leaking unnecessary data.</violation>
</file>

<file name="packages/features/teams/pages/admin/AdminTeamEditPage.tsx">

<violation number="1" location="packages/features/teams/pages/admin/AdminTeamEditPage.tsx:70">
Localize the PanelCard title and subtitle with `t()` instead of hardcoded English to meet our i18n requirement.</violation>

<violation number="2" location="packages/features/teams/pages/admin/AdminTeamEditPage.tsx:72">
Wrap the TextField label and placeholder in `t()` so the name field respects localization.</violation>

<violation number="3" location="packages/features/teams/pages/admin/AdminTeamEditPage.tsx:76">
Move this helper sentence into `t()` so the message can be translated per our localization standard.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/organizations/adminGetAll.schema.ts:12">
`offset` should be constrained to non-negative integers; otherwise a negative input will reach prisma.team.findMany({ skip: offset }) and Prisma will throw. Add `.min(0)` (and optionally a default) so invalid offsets are caught at validation time.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/teams/adminUpdate.schema.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/teams/adminUpdate.schema.ts:7">
Allow admins to set metadata to null so they can clear the JSON column instead of being blocked by validation.</violation>
</file>

<file name="scripts/seed-organizations.ts">

<violation number="1" location="scripts/seed-organizations.ts:21">
Please add a select clause to this prisma.team.findFirst call so we only load the minimal fields needed (e.g., the id) instead of hydrating the entire team record.</violation>

<violation number="2" location="scripts/seed-organizations.ts:122">
Rule violated: **Avoid Logging Sensitive Information**

This log prints the seeded owner password, violating the “Avoid Logging Sensitive Information” guideline. Remove the credential value from the log message.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this new router to simpilfy the types by trpc that are generated. Was getting OOM

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

14 issues found across 44 files

Prompt for AI agents (all 14 issues)

Understand the root cause of the following 14 issues and fix them.


<file name="scripts/seed-organizations.ts">

<violation number="1" location="scripts/seed-organizations.ts:21">
Re-running this seed will create duplicate organizations for the unpublished cases: the guard only checks `slug`, but the creation path sets `slug` to null for ~20% of runs, so subsequent executions insert another copy. Please key the existence check off a value that survives creation (e.g., requestedSlug in metadata) or always persist the slug.</violation>
</file>

<file name="packages/features/teams/pages/admin/AdminTeamEditPage.tsx">

<violation number="1" location="packages/features/teams/pages/admin/AdminTeamEditPage.tsx:70">
Please wrap the PanelCard title and subtitle in t() so these headings are localized rather than hard-coded English strings.</violation>

<violation number="2" location="packages/features/teams/pages/admin/AdminTeamEditPage.tsx:72">
Localize the TextField label/placeholder with t() instead of embedding raw English so translators can handle this copy.</violation>

<violation number="3" location="packages/features/teams/pages/admin/AdminTeamEditPage.tsx:76">
Route this paragraph text through t() so it can be translated and stays consistent with our localization practice.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/organizations/adminUpdateMetadata.schema.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/organizations/adminUpdateMetadata.schema.ts:4">
Restrict the organization id to an integer so Prisma doesn’t receive fractional values that slip past validation and trigger runtime errors.</violation>
</file>

<file name="apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/teams/[id]/edit/page.tsx">

<violation number="1" location="apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/teams/[id]/edit/page.tsx:39">
If the dynamic route param fails validation, throwing a generic Error causes the admin edit page to return a 500 instead of the expected 404. Use Next.js notFound() (or a safe redirect) for invalid params so the route handles bad IDs gracefully.</violation>
</file>

<file name="packages/features/teams/pages/admin/AdminTeamPage.tsx">

<violation number="1" location="packages/features/teams/pages/admin/AdminTeamPage.tsx:99">
Replace the hard-coded &quot;None&quot; string in the organization accessor with a localized value via t() so the empty state is translated.</violation>

<violation number="2" location="packages/features/teams/pages/admin/AdminTeamPage.tsx:109">
Use the localization helper t() instead of the hard-coded &quot;None&quot; string when rendering the organization fallback.</violation>

<violation number="3" location="packages/features/teams/pages/admin/AdminTeamPage.tsx:123">
Localize the &quot;No owner&quot; fallback text in the owner accessor so it respects translations.</violation>

<violation number="4" location="packages/features/teams/pages/admin/AdminTeamPage.tsx:128">
Wrap the &quot;No owner&quot; fallback in t() so the message is translated.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/teams/adminGet.schema.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/teams/adminGet.schema.ts:4">
Team IDs are stored as Int in Prisma, but this schema only ensures the value is a number. A fractional value would pass validation and then Prisma rejects the query—please add `.int()` so the input must be an integer.</violation>
</file>

<file name="packages/features/billing/components/AdminMetadata.tsx">

<violation number="1" location="packages/features/billing/components/AdminMetadata.tsx:72">
User-facing copy here should be wrapped with the project’s t() localization helper rather than hard-coded strings so the admin tooling can be translated.</violation>
</file>

<file name="packages/features/billing/components/AdminBillingInfo.tsx">

<violation number="1" location="packages/features/billing/components/AdminBillingInfo.tsx:23">
Please wrap this title in the translation helper (t()) instead of hardcoding the string so it can be localized.</violation>
</file>

<file name="packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx">

<violation number="1" location="packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx:69">
Please replace the newly added hard-coded PanelCard title/subtitle with localized t() calls so the text participates in translations.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

const slug = `test-org-${i}`;

// Check if organization already exists
const existingOrg = await prisma.team.findFirst({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-running this seed will create duplicate organizations for the unpublished cases: the guard only checks slug, but the creation path sets slug to null for ~20% of runs, so subsequent executions insert another copy. Please key the existence check off a value that survives creation (e.g., requestedSlug in metadata) or always persist the slug.

Prompt for AI agents
Address the following comment on scripts/seed-organizations.ts at line 21:

<comment>Re-running this seed will create duplicate organizations for the unpublished cases: the guard only checks `slug`, but the creation path sets `slug` to null for ~20% of runs, so subsequent executions insert another copy. Please key the existence check off a value that survives creation (e.g., requestedSlug in metadata) or always persist the slug.</comment>

<file context>
@@ -0,0 +1,141 @@
+    const slug = `test-org-${i}`;
+
+    // Check if organization already exists
+    const existingOrg = await prisma.team.findFirst({
+      where: {
+        slug,
</file context>
Fix with Cubic

<TextField label="Slug" placeholder="my-team" required {...form.register("slug")} />
{team.parentId && (
<p className="text-default mt-2 text-sm">
This team belongs to an organization. Some settings may be managed at the organization level.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Route this paragraph text through t() so it can be translated and stays consistent with our localization practice.

Prompt for AI agents
Address the following comment on packages/features/teams/pages/admin/AdminTeamEditPage.tsx at line 76:

<comment>Route this paragraph text through t() so it can be translated and stays consistent with our localization practice.</comment>

<file context>
@@ -0,0 +1,112 @@
+          &lt;TextField label=&quot;Slug&quot; placeholder=&quot;my-team&quot; required {...form.register(&quot;slug&quot;)} /&gt;
+          {team.parentId &amp;&amp; (
+            &lt;p className=&quot;text-default mt-2 text-sm&quot;&gt;
+              This team belongs to an organization. Some settings may be managed at the organization level.
+            &lt;/p&gt;
+          )}
</file context>
Fix with Cubic

{/* Basic Information Section */}
<PanelCard title="Basic Information" subtitle="Team name and slug">
<Form form={form} className="space-y-4 p-4" handleSubmit={onSubmit}>
<TextField label="Name" placeholder="My Team" required {...form.register("name")} />
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localize the TextField label/placeholder with t() instead of embedding raw English so translators can handle this copy.

Prompt for AI agents
Address the following comment on packages/features/teams/pages/admin/AdminTeamEditPage.tsx at line 72:

<comment>Localize the TextField label/placeholder with t() instead of embedding raw English so translators can handle this copy.</comment>

<file context>
@@ -0,0 +1,112 @@
+      {/* Basic Information Section */}
+      &lt;PanelCard title=&quot;Basic Information&quot; subtitle=&quot;Team name and slug&quot;&gt;
+        &lt;Form form={form} className=&quot;space-y-4 p-4&quot; handleSubmit={onSubmit}&gt;
+          &lt;TextField label=&quot;Name&quot; placeholder=&quot;My Team&quot; required {...form.register(&quot;name&quot;)} /&gt;
+          &lt;TextField label=&quot;Slug&quot; placeholder=&quot;my-team&quot; required {...form.register(&quot;slug&quot;)} /&gt;
+          {team.parentId &amp;&amp; (
</file context>
Fix with Cubic

return (
<div className="flex flex-col gap-4">
{/* Basic Information Section */}
<PanelCard title="Basic Information" subtitle="Team name and slug">
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please wrap the PanelCard title and subtitle in t() so these headings are localized rather than hard-coded English strings.

Prompt for AI agents
Address the following comment on packages/features/teams/pages/admin/AdminTeamEditPage.tsx at line 70:

<comment>Please wrap the PanelCard title and subtitle in t() so these headings are localized rather than hard-coded English strings.</comment>

<file context>
@@ -0,0 +1,112 @@
+  return (
+    &lt;div className=&quot;flex flex-col gap-4&quot;&gt;
+      {/* Basic Information Section */}
+      &lt;PanelCard title=&quot;Basic Information&quot; subtitle=&quot;Team name and slug&quot;&gt;
+        &lt;Form form={form} className=&quot;space-y-4 p-4&quot; handleSubmit={onSubmit}&gt;
+          &lt;TextField label=&quot;Name&quot; placeholder=&quot;My Team&quot; required {...form.register(&quot;name&quot;)} /&gt;
</file context>
Fix with Cubic

import { z } from "zod";

export const ZAdminUpdateMetadataSchema = z.object({
id: z.number(),
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restrict the organization id to an integer so Prisma doesn’t receive fractional values that slip past validation and trigger runtime errors.

Prompt for AI agents
Address the following comment on packages/trpc/server/routers/viewer/organizations/adminUpdateMetadata.schema.ts at line 4:

<comment>Restrict the organization id to an integer so Prisma doesn’t receive fractional values that slip past validation and trigger runtime errors.</comment>

<file context>
@@ -0,0 +1,8 @@
+import { z } from &quot;zod&quot;;
+
+export const ZAdminUpdateMetadataSchema = z.object({
+  id: z.number(),
+  metadata: z.record(z.string(), z.string()),
+});
</file context>
Suggested change
id: z.number(),
id: z.number().int(),
Fix with Cubic

cell: ({ row }) => {
const team = row.original;
if (!team.parent) {
return <span className="text-subtle text-sm">None</span>;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the localization helper t() instead of the hard-coded "None" string when rendering the organization fallback.

Prompt for AI agents
Address the following comment on packages/features/teams/pages/admin/AdminTeamPage.tsx at line 109:

<comment>Use the localization helper t() instead of the hard-coded &quot;None&quot; string when rendering the organization fallback.</comment>

<file context>
@@ -0,0 +1,249 @@
+        cell: ({ row }) =&gt; {
+          const team = row.original;
+          if (!team.parent) {
+            return &lt;span className=&quot;text-subtle text-sm&quot;&gt;None&lt;/span&gt;;
+          }
+          return (
</file context>
Suggested change
return <span className="text-subtle text-sm">None</span>;
return <span className="text-subtle text-sm">{t("none")}</span>;
Fix with Cubic

import { z } from "zod";

export const ZAdminGetTeamSchema = z.object({
id: z.number(),
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Team IDs are stored as Int in Prisma, but this schema only ensures the value is a number. A fractional value would pass validation and then Prisma rejects the query—please add .int() so the input must be an integer.

Prompt for AI agents
Address the following comment on packages/trpc/server/routers/viewer/teams/adminGet.schema.ts at line 4:

<comment>Team IDs are stored as Int in Prisma, but this schema only ensures the value is a number. A fractional value would pass validation and then Prisma rejects the query—please add `.int()` so the input must be an integer.</comment>

<file context>
@@ -0,0 +1,7 @@
+import { z } from &quot;zod&quot;;
+
+export const ZAdminGetTeamSchema = z.object({
+  id: z.number(),
+});
+
</file context>
Suggested change
id: z.number(),
id: z.number().int(),
Fix with Cubic


if (!metadata || typeof metadata !== "object" || Object.keys(metadata).length === 0) {
return (
<PanelCard title="Metadata">
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User-facing copy here should be wrapped with the project’s t() localization helper rather than hard-coded strings so the admin tooling can be translated.

Prompt for AI agents
Address the following comment on packages/features/billing/components/AdminMetadata.tsx at line 72:

<comment>User-facing copy here should be wrapped with the project’s t() localization helper rather than hard-coded strings so the admin tooling can be translated.</comment>

<file context>
@@ -0,0 +1,315 @@
+
+  if (!metadata || typeof metadata !== &quot;object&quot; || Object.keys(metadata).length === 0) {
+    return (
+      &lt;PanelCard title=&quot;Metadata&quot;&gt;
+        &lt;div className=&quot;text-subtle p-4 text-sm&quot;&gt;No metadata available.&lt;/div&gt;
+      &lt;/PanelCard&gt;
</file context>
Fix with Cubic

}) => {
if (isLoading) {
return (
<PanelCard title="Stripe & Billing">
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please wrap this title in the translation helper (t()) instead of hardcoding the string so it can be localized.

Prompt for AI agents
Address the following comment on packages/features/billing/components/AdminBillingInfo.tsx at line 23:

<comment>Please wrap this title in the translation helper (t()) instead of hardcoding the string so it can be localized.</comment>

<file context>
@@ -0,0 +1,153 @@
+}) =&gt; {
+  if (isLoading) {
+    return (
+      &lt;PanelCard title=&quot;Stripe &amp; Billing&quot;&gt;
+        &lt;div className=&quot;space-y-3 p-4&quot;&gt;
+          &lt;SkeletonText className=&quot;h-4 w-full&quot; /&gt;
</file context>
Fix with Cubic

</Form>
<div className="flex flex-col gap-4">
{/* Basic Information Section */}
<PanelCard title="Basic Information" subtitle="Organization name, slug, and settings">
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace the newly added hard-coded PanelCard title/subtitle with localized t() calls so the text participates in translations.

Prompt for AI agents
Address the following comment on packages/features/ee/organizations/pages/settings/admin/AdminOrgEditPage.tsx at line 69:

<comment>Please replace the newly added hard-coded PanelCard title/subtitle with localized t() calls so the text participates in translations.</comment>

<file context>
@@ -61,23 +64,36 @@ export const OrgForm = ({
-    &lt;/Form&gt;
+    &lt;div className=&quot;flex flex-col gap-4&quot;&gt;
+      {/* Basic Information Section */}
+      &lt;PanelCard title=&quot;Basic Information&quot; subtitle=&quot;Organization name, slug, and settings&quot;&gt;
+        &lt;Form form={form} className=&quot;space-y-4 p-4&quot; handleSubmit={onSubmit}&gt;
+          &lt;TextField label=&quot;Name&quot; placeholder=&quot;example&quot; required {...form.register(&quot;name&quot;)} /&gt;
</file context>
Fix with Cubic

@github-actions
Copy link
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

admin billing area: billing, stripe, payments, paypal, get paid consumer core area: core, team members only ✨ feature New feature or request organizations area: organizations, orgs ready-for-e2e size/XXL Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants