Skip to content

feat: administration plans UI improvements #3152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

dkrizan
Copy link
Contributor

@dkrizan dkrizan commented Jun 26, 2025

Closes https://github.com/tolgee/billing/issues/181

Summary by CodeRabbit

  • New Features

    • Added the ability to archive cloud and self-hosted enterprise plans in the administration interface.
    • Introduced visual indicators and controls for archived plans, including an "Archive" button, archived status badge, and disabled styling for archived plans.
    • Displayed subscription counts and enhanced price information for plans in administration views.
    • Added toggle switches to plan forms for managing archived status.
  • Bug Fixes

    • None.
  • Chores

    • Extended API and form schema to support archived state and subscription count for plans.
    • Updated test identifiers to cover new archived plan UI elements.

Copy link
Contributor

coderabbitai bot commented Jun 26, 2025

"""

Walkthrough

This update introduces an archiving feature for both cloud and self-hosted enterprise subscription plans, adds a new "archived" property to relevant models, and enhances the administration UI to display subscription counts and plan prices. It includes new API endpoints for archiving plans, new UI components, and updates to the data schema and type definitions.

Changes

File(s) Change Summary
backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEePlanModel.kt, backend/api/src/main/kotlin/io/tolgee/publicBilling/PublicCloudPlanModel.kt Added archived boolean property to plan models.
webapp/src/service/billingApiSchema.generated.ts Extended API schema: new archive endpoints, archived and subscriptionCount properties added to plan models.
build.gradle Modified startDbChangelogContainer task to forcibly remove existing container before starting a new one.
e2e/cypress/support/dataCyType.d.ts Added new data-cy values for archived plan UI elements.
webapp/src/ee/billing/Subscriptions/cloud/useCloudPlans.tsx, webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHostedList.tsx Added archived: false to hardcoded Enterprise plan objects.
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts Added archived boolean field to GenericPlanFormData interface.
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx New component: PlanArchivedSwitch for toggling archived state in plan forms.
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanForm.tsx, webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx Integrated PlanArchivedSwitch into plan forms above the public switch.
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx, webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx Added archiving feature: mutation hooks, archive confirmation, UI indicators (archived chip, styled text), subscription count, and price info display.
webapp/src/ee/billing/component/Plan/PlanArchivedChip.tsx New component: PlanArchivedChip to visually indicate archived plans.
webapp/src/ee/billing/component/Plan/PlanListPriceInfo.tsx New component: PlanListPriceInfo to display plan prices with tooltip.
webapp/src/ee/billing/component/Plan/PlanSubscriptionCount.tsx New component: PlanSubscriptionCount to display number of subscriptions.
webapp/src/ee/billing/component/Price/PricePrimary.tsx Added noPeriodSwitch prop to disable period switching in price display.

Sequence Diagram(s)

sequenceDiagram
    participant AdminUser as Admin User
    participant UI as Admin UI
    participant API as Backend API

    AdminUser->>UI: Views plan list
    UI->>API: Fetch plans (with archived & subscription count)
    API-->>UI: Return plans data
    UI->>AdminUser: Display plans with price, subscription count, archived badge

    AdminUser->>UI: Clicks "Archive" on a plan
    UI->>AdminUser: Shows confirmation dialog
    AdminUser->>UI: Confirms archive
    UI->>API: PUT /archive endpoint for plan
    API-->>UI: Success response
    UI->>AdminUser: Show success message, update UI (plan marked as archived)
Loading

Assessment against linked issues

Objective Addressed Explanation
Add customers count and prices to the cloud and self-hosted plan lists (#181)
Enable plan archiving (for unused plans) (#181)

Suggested reviewers

  • JanCizmar

Poem

In the warren of plans, some old and some new,
Now archived with care, their numbers in view.
With badges and chips, the UI’s a delight,
Subscription counts twinkle, prices shine bright.
A hop and a click, plans rest in the shade—
The admin’s new tools, by rabbits well made! 🐇✨
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25015c4 and 8593d76.

📒 Files selected for processing (1)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Frontend static check 🪲
  • GitHub Check: Build frontend 🏗️
  • GitHub Check: Build backend 🏗️
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@dkrizan dkrizan changed the title feat: subscriptions count and prices in plans views in administration feat: administration plans UI improvements Jul 4, 2025
@dkrizan dkrizan force-pushed the danielkrizan/admin-plans-ui-improvements branch from 34f4776 to 51e8a45 Compare July 4, 2025 14:39
@dkrizan dkrizan marked this pull request as ready for review July 10, 2025 12:29
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 (4)
build.gradle (1)

96-99: Idempotent cleanup added – mirror ignoreExitValue in the stop task for symmetry

The extra exec { docker rm … } block is a welcome improvement: it prevents “container already exists” failures and keeps the task rerunnable.
For consistency (and to avoid a possible build break if the container gets removed out-of-band) consider applying the same ignoreExitValue = true flag to stopDbChangelogContainer.

 task stopDbChangelogContainer(type: Exec) {
-    commandLine "docker", "rm", "--force", "--volumes", dbSchemaContainerName
+    ignoreExitValue = true
+    commandLine "docker", "rm", "--force", "--volumes", dbSchemaContainerName
 }

This keeps both start/stop tasks tolerant to missing containers and avoids noisy CI failures.

webapp/src/ee/billing/component/Plan/PlanArchivedChip.tsx (1)

8-14: Consider making the data-cy attribute more generic.

The hardcoded data-cy="administration-cloud-plans-item-archived-badge" appears cloud-specific but this component seems to be generic and reusable. Consider making the data-cy attribute configurable via props or using a more generic identifier.

-export function PlanArchivedChip({ isArchived }: { isArchived?: boolean }) {
+export function PlanArchivedChip({ 
+  isArchived, 
+  dataCy = "administration-plan-archived-badge" 
+}: { 
+  isArchived?: boolean;
+  dataCy?: string;
+}) {
   if (!isArchived) {
     return null;
   }
   return (
     <Chip
-      data-cy="administration-cloud-plans-item-archived-badge"
+      data-cy={dataCy}
       size="small"
       label={<T keyName="administration_plan_archived_badge" />}
     />
   );
 }
webapp/src/ee/billing/component/Plan/PlanSubscriptionCount.tsx (1)

5-5: Use strict equality operator.

Replace == with === for the comparison to avoid potential type coercion issues.

-  if (count == 0) {
+  if (count === 0) {
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx (1)

10-10: Consider making the component more generic.

The component is hardcoded to use CloudPlanFormData type, which limits its reusability. Since this is in a genericFields directory, it should ideally work with both cloud and self-hosted EE plan forms.

Consider making it generic:

-export const PlanArchivedSwitch: FC<{ isUpdate?: boolean }> = ({
+export const PlanArchivedSwitch: FC<{ isUpdate?: boolean }> = ({
   isUpdate,
 }) => {
-  const { setFieldValue, values } = useFormikContext<CloudPlanFormData>();
+  const { setFieldValue, values } = useFormikContext<{ archived: boolean }>();

This would allow the component to work with any form data that has an archived field.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a77ac9a and c17ff9f.

📒 Files selected for processing (17)
  • backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEePlanModel.kt (1 hunks)
  • backend/api/src/main/kotlin/io/tolgee/publicBilling/PublicCloudPlanModel.kt (1 hunks)
  • build.gradle (1 hunks)
  • e2e/cypress/support/dataCyType.d.ts (3 hunks)
  • webapp/src/ee/billing/Subscriptions/cloud/useCloudPlans.tsx (1 hunks)
  • webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHostedList.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanForm.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx (1 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx (2 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (4 hunks)
  • webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (5 hunks)
  • webapp/src/ee/billing/component/Plan/PlanArchivedChip.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/PlanListPriceInfo.tsx (1 hunks)
  • webapp/src/ee/billing/component/Plan/PlanSubscriptionCount.tsx (1 hunks)
  • webapp/src/ee/billing/component/Price/PricePrimary.tsx (2 hunks)
  • webapp/src/service/billingApiSchema.generated.ts (12 hunks)
🧰 Additional context used
🧠 Learnings (4)
build.gradle (1)
Learnt from: laz-001
PR: tolgee/tolgee-platform#0
File: :0-0
Timestamp: 2025-05-09T01:35:14.224Z
Learning: Changes to PostgreSQL container names, ports, Liquibase changelog paths, and test data directories in the Tolgee platform are often made to provide dedicated resources to parallel test groups (like `ee-test:test` and `server-app:test`), preventing resource congestion and collisions that could lead to deadlocks during testing.
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (1)
Learnt from: Anty0
PR: tolgee/tolgee-platform#3003
File: webapp/src/ee/glossary/components/GlossaryTermTags.tsx:18-18
Timestamp: 2025-06-04T18:53:24.403Z
Learning: In React 17, use React.VFC for functional components that don't need children props, as React.FC implicitly includes `children?: React.ReactNode`. React.VFC was deprecated in React 18+ where React.FC no longer includes children by default.
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx (1)
Learnt from: Anty0
PR: tolgee/tolgee-platform#3003
File: webapp/src/ee/glossary/components/GlossaryTermTags.tsx:18-18
Timestamp: 2025-06-04T18:53:24.403Z
Learning: In React 17, use React.VFC for functional components that don't need children props, as React.FC implicitly includes `children?: React.ReactNode`. React.VFC was deprecated in React 18+ where React.FC no longer includes children by default.
webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (1)
Learnt from: Anty0
PR: tolgee/tolgee-platform#3003
File: webapp/src/ee/glossary/components/GlossaryTermTags.tsx:18-18
Timestamp: 2025-06-04T18:53:24.403Z
Learning: In React 17, use React.VFC for functional components that don't need children props, as React.FC implicitly includes `children?: React.ReactNode`. React.VFC was deprecated in React 18+ where React.FC no longer includes children by default.
🧬 Code Graph Analysis (5)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanForm.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx (1)
  • PlanArchivedSwitch (7-37)
webapp/src/ee/billing/component/Plan/PlanListPriceInfo.tsx (2)
webapp/src/ee/billing/component/Price/PricePrimary.tsx (1)
  • PricePrimary (28-66)
webapp/src/colors.tsx (1)
  • Tooltip (37-40)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1)
  • CloudPlanFormData (24-27)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx (1)
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx (1)
  • PlanArchivedSwitch (7-37)
webapp/src/service/billingApiSchema.generated.ts (1)
webapp/src/service/apiSchema.generated.ts (2)
  • operations (5712-22269)
  • components (1017-5710)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: E2E testing ‍🔎 (15, 5)
  • GitHub Check: E2E testing ‍🔎 (15, 4)
  • GitHub Check: E2E testing ‍🔎 (15, 14)
  • GitHub Check: E2E testing ‍🔎 (15, 13)
  • GitHub Check: E2E testing ‍🔎 (15, 12)
  • GitHub Check: E2E testing ‍🔎 (15, 11)
  • GitHub Check: E2E testing ‍🔎 (15, 1)
  • GitHub Check: E2E testing ‍🔎 (15, 10)
  • GitHub Check: BT ‍🔎 (data:test)
  • GitHub Check: E2E testing ‍🔎 (15, 9)
  • GitHub Check: E2E testing ‍🔎 (15, 3)
  • GitHub Check: E2E testing ‍🔎 (15, 8)
  • GitHub Check: BT ‍🔎 (server-app:runStandardTests)
  • GitHub Check: E2E testing ‍🔎 (15, 7)
  • GitHub Check: BT ‍🔎 (server-app:runWebsocketTests)
  • GitHub Check: E2E testing ‍🔎 (15, 2)
  • GitHub Check: E2E testing ‍🔎 (15, 6)
  • GitHub Check: BT ‍🔎 (security:test)
  • GitHub Check: E2E testing ‍🔎 (15, 0)
  • GitHub Check: BT ‍🔎 (server-app:runContextRecreatingTests)
  • GitHub Check: BT ‍🔎 (ee-test:test)
  • GitHub Check: BT ‍🔎 (ktlint:test)
  • GitHub Check: BT ‍🔎 (server-app:runWithoutEeTests)
🔇 Additional comments (27)
webapp/src/ee/billing/component/Price/PricePrimary.tsx (1)

25-25: LGTM! Well-implemented feature addition.

The noPeriodSwitch prop addition is correctly implemented with proper typing, default value, and backward compatibility. The logic update appropriately uses the new prop to control period switch display.

Also applies to: 34-34, 39-39

webapp/src/ee/billing/component/Plan/PlanListPriceInfo.tsx (2)

16-66: Excellent component structure and UX design.

The component provides a clean interface for displaying pricing information with helpful tooltips. The use of noPeriodSwitch={true} is appropriate for list displays, and the tooltip content provides clear yearly and monthly pricing details.


32-32: Incorrect concern: theme.palette.primaryText is a valid custom theme property.

  • Defined in webapp/src/custom.d.ts under the extended Palette interface.
  • Supplied in webapp/src/ThemeProvider.tsx as primaryText: c.primaryText.
  • Colors for primaryText are configured in webapp/src/colors.tsx.

No changes needed.

Likely an incorrect or invalid review comment.

webapp/src/ee/billing/Subscriptions/selfHosted/PlansSelfHostedList.tsx (1)

68-68: LGTM! Consistent archiving feature implementation.

The addition of archived: false to the hardcoded Enterprise plan is consistent with the broader archiving feature rollout and maintains proper default state.

webapp/src/ee/billing/Subscriptions/cloud/useCloudPlans.tsx (1)

64-64: LGTM! Consistent archiving feature implementation.

The addition of archived: false to the hardcoded Enterprise plan maintains consistency with the archiving feature across both cloud and self-hosted plans.

backend/api/src/main/kotlin/io/tolgee/hateoas/ee/SelfHostedEePlanModel.kt (1)

24-24: LGTM! Proper backend model extension.

The addition of the archived property with a default value of false is correctly implemented, maintaining backward compatibility while supporting the new archiving feature.

backend/api/src/main/kotlin/io/tolgee/publicBilling/PublicCloudPlanModel.kt (1)

16-16: LGTM! Clean interface extension.

The addition of the archived property to the interface is simple, well-typed, and consistent with the archiving functionality being implemented.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (1)

21-21: LGTM! Consistent interface extension.

The addition of the archived property maintains consistency with the backend interface and follows proper TypeScript typing conventions.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/CloudPlanForm.tsx (2)

10-10: LGTM! Proper component import.

The import path is consistent with the project structure and correctly imports the PlanArchivedSwitch component.


67-70: LGTM! Clean component integration.

The PlanArchivedSwitch is properly integrated into the form with correct prop passing and logical layout using the grid display.

webapp/src/ee/billing/component/Plan/PlanArchivedChip.tsx (1)

4-7: LGTM! Good conditional rendering pattern.

The early return pattern for the null case is clean and follows React best practices.

webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx (1)

15-15: LGTM! Consistent component import.

The import follows the same pattern as in CloudPlanForm, maintaining consistency across the codebase.

e2e/cypress/support/dataCyType.d.ts (1)

43-44: LGTM! Data-cy identifiers are well-defined.

The new test identifiers follow the established naming convention and comprehensively cover the archiving functionality for both cloud and self-hosted EE plans.

Also applies to: 57-57, 71-71

webapp/src/ee/billing/administration/subscriptionPlans/viewsCloud/AdministrationCloudPlansView.tsx (4)

32-36: Good styling approach for archived plans.

The styled component approach for conditional styling is clean and follows Material-UI patterns well.


48-52: Well-structured mutation hook.

The archive mutation hook is properly configured with correct endpoint, method, and cache invalidation.


60-78: Excellent UX pattern for archiving.

The archiving flow implements good UX practices:

  • Confirmation dialog to prevent accidental actions
  • Clear messaging about the action
  • Success feedback to the user
  • Proper error handling through the mutation hook

134-168: Well-organized action buttons layout.

The button layout is thoughtfully organized:

  • Archive button only shown for non-archived plans
  • Logical grouping of actions
  • Proper spacing and alignment
  • Consistent data-cy attributes for testing
webapp/src/ee/billing/administration/subscriptionPlans/viewsSelfHostedEe/AdministrationEePlansView.tsx (4)

30-31: Good type update for enhanced plan model.

The type update from SelfHostedEePlanModel to SelfHostedEePlanAdministrationModel correctly reflects the schema changes that add archived and subscriptionCount properties.


33-37: Consistent styling approach.

The styled component matches the cloud plans implementation, ensuring visual consistency across both admin views.


49-79: Consistent archiving implementation.

The archiving functionality is implemented consistently with the cloud plans view, which promotes maintainability and provides a unified user experience.


141-173: Well-implemented UI enhancements.

The UI improvements are consistent with the cloud plans view:

  • Subscription count display
  • Price information
  • Archived status indicators
  • Proper action button organization
webapp/src/service/billingApiSchema.generated.ts (6)

27-29: New archive endpoints follow consistent RESTful patterns

The addition of archive endpoints for both cloud and self-hosted plans maintains consistency with the existing API structure. Both endpoints use PUT method and follow the same path pattern.

Also applies to: 52-54


221-221: Administration cloud plan model enhanced with archive and subscription tracking

The addition of archived: boolean and subscriptionCount: number properties provides essential functionality for plan management in the administration interface. The subscription count will be particularly useful for tracking plan usage.

Also applies to: 261-262


340-340: Cloud plan models consistently updated with archive property

The archived property is properly added to both CloudPlanModel and CloudPlanRequest, ensuring consistency between read and write operations.

Also applies to: 375-375


1122-1122: Self-hosted EE plan models enhanced with archive and subscription tracking

All self-hosted enterprise plan models are consistently updated with the archived property and administration models include subscriptionCount. This maintains parity with cloud plan functionality.

Also applies to: 1162-1163, 1166-1166, 1200-1200


828-829: New error code added for LLM provider responses

The addition of "llm_provider_empty_response" to the error code union expands error handling capabilities for AI/LLM integrations.


1806-1852: Archive operation definitions are comprehensive and well-structured

Both archivePlan_1 and archivePlan operations include:

  • Proper path parameters for plan identification
  • Complete HTTP status code handling (200, 400, 401, 403, 404)
  • Consistent error response types
  • Appropriate return types matching the administration models

The operations follow the established patterns used by other plan management endpoints.

Also applies to: 2314-2360

Comment on lines +43 to +46
<Box display="grid">
<PlanArchivedSwitch isUpdate={isUpdate} />
<PlanPublicSwitchField />
</Box>
Copy link
Contributor

@coderabbitai coderabbitai bot Jul 10, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify type compatibility of PlanArchivedSwitch with SelfHostedEePlanFormData.

The integration pattern is consistent with CloudPlanForm. However, based on the relevant code snippets, the PlanArchivedSwitch component uses useFormikContext<CloudPlanFormData>() which may not be compatible with SelfHostedEePlanFormData used in this form.


🏁 Script executed:

#!/bin/bash
# Description: Check if PlanArchivedSwitch component is properly typed for both form types
# Expected: The component should handle both CloudPlanFormData and SelfHostedEePlanFormData types

# Search for the PlanArchivedSwitch implementation
ast-grep --pattern 'useFormikContext<$_>()' 

# Also check the form data types to see if they're compatible
ast-grep --pattern 'interface $_ {
  $$$
  archived: $_
  $$$
}'

Length of output: 4554


I’d like to confirm the exact definitions of both form-data types. Let’s pull in the CloudPlanFormData and SelfHostedEePlanFormData interfaces:


🏁 Script executed:

#!/bin/bash
# Print CloudPlanFormData definition
sed -n '1,200p' webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts

# Locate and print SelfHostedEePlanFormData definition
rg -n 'interface SelfHostedEePlanFormData' -A10 webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe

Length of output: 1099


I want to confirm whether SelfHostedEePlanRequest actually includes an archived field. Let’s locate its definition in the generated schema:


🏁 Script executed:

#!/bin/bash
# Locate the generated file(s) that define SelfHostedEePlanRequest
rg -l "SelfHostedEePlanRequest" -t ts

# For each file found, print the definition around it
for file in $(rg -l "SelfHostedEePlanRequest" -t ts); do
  echo "=== $file ==="
  rg -n "SelfHostedEePlanRequest" -A20 -B5 "$file"
done

Length of output: 3268


Ensure PlanArchivedSwitch supports SelfHostedEePlanFormData

PlanArchivedSwitch currently hardcodes

const { setFieldValue, values } = useFormikContext<CloudPlanFormData>();

but SelfHostedEePlanFormData is an independent generated type (components['schemas']['SelfHostedEePlanRequest']) whose archived?: boolean (and other fields) don’t fully match CloudPlanFormData. This mismatch will surface as a TypeScript error when embedding the switch into the SelfHostedEe form.

• File: webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx
– Change the generic on useFormikContext<…>() so it only requires an archived?: boolean field (e.g. useFormikContext<{ archived?: boolean }>()), or make the component generic <T extends { archived?: boolean }> and call useFormikContext<T>().
• File: webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx
– Ensure initialValues includes a default for archived (e.g. archived: false) so the switch has a non-undefined value at runtime.
• After these changes, rerun TypeScript to confirm both CloudPlanForm and SelfHostedEePlanForm compile cleanly with the shared switch.

🤖 Prompt for AI Agents
In
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx
around lines 10-30, modify the useFormikContext hook to be generic by defining
the component as <T extends { archived?: boolean }> and calling
useFormikContext<T>() instead of hardcoding CloudPlanFormData. In
webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx
around lines 40-50, update the initialValues object to include archived: false
to ensure the switch has a defined value at runtime. After these changes, run
TypeScript to verify both forms compile without errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

archived: boolean changed to archived?: boolean

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

@dkrizan That's a good approach! Making the archived field optional should help with type compatibility. Let me verify that this fully resolves the type compatibility issue between the forms.


🏁 Script executed:

#!/bin/bash
# Check the current type definitions to verify the change
echo "=== Checking type definitions ==="
rg -n "archived.*boolean" webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/

# Check if PlanArchivedSwitch handles undefined values properly
echo "=== Checking PlanArchivedSwitch implementation ==="
cat webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx

# Check the generated schema types to confirm the change
echo "=== Checking generated schema types ==="
rg -n "archived.*boolean" webapp/src/service/billingApiSchema.generated.ts

Length of output: 1876


Perfect! Making the archived field optional is a good step toward resolving the type compatibility issue. However, there are still two remaining issues to fully address:

  1. Type Compatibility: The PlanArchivedSwitch component is still hardcoded to use CloudPlanFormData:

    const { setFieldValue, values } = useFormikContext<CloudPlanFormData>();

    This means it won't work with SelfHostedEePlanFormData even though both types now have optional archived fields.

  2. Undefined Value Handling: The component uses checked={values.archived} which could be undefined now that the field is optional. This could cause the switch to behave unexpectedly.

To fully resolve this, you could:

Option 1 - Make PlanArchivedSwitch generic:

const { setFieldValue, values } = useFormikContext<{ archived?: boolean }>();

Option 2 - Handle undefined values properly:

checked={values.archived ?? false}

This would ensure type safety across both form types and handle undefined values gracefully. Would you like me to help implement either of these approaches?


🧬 Code Graph Analysis Results

Relevant snippets for webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/selfHostedEe/SelfHostedEePlanForm.tsx


Snippet from webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/cloud/types.ts (lines 29-30)

export type SelfHostedEePlanFormData =
  components['schemas']['SelfHostedEePlanRequest'];
  • Defines the type SelfHostedEePlanFormData used as the form data type in SelfHostedEePlanForm.
  • This type is an alias for the API schema type SelfHostedEePlanRequest.
  • Changes to optionality of fields like archived would be reflected here if the API schema changes.

Snippet from webapp/src/ee/billing/administration/subscriptionPlans/components/planForm/genericFields/PlanArchivedSwitch.tsx (lines 7-37)

export const PlanArchivedSwitch: FC<{ isUpdate?: boolean }> = ({
  isUpdate,
}) => {
  const { setFieldValue, values } = useFormikContext<CloudPlanFormData>();

  const { t } = useTranslate();

  if (!isUpdate) {
    return null;
  }

  return (
    <>
      <FormControlLabel
        control={
          <Switch
            checked={values.archived}
            onChange={() => setFieldValue('archived', !values.archived)}
          />
        }
        data-cy="administration-plan-field-archived"
        label={t('administration_cloud_plan_field_archived')}
      />
      {values.archived && (
        <Alert severity="warning" sx={{ mt: 1, mb: 4 }}>
          <T keyName={'admin_billing_plan_archived_warning'} />
        </Alert>
      )}
    </>
  );
};
  • React functional component rendering a switch to toggle the archived boolean field in the form.
  • Uses Formik context to read and update the archived field value.
  • Only renders if isUpdate prop is true.
  • Displays a warning alert if the plan is archived.
  • The archived field is accessed as values.archived and toggled on switch change.
  • This component is used in SelfHostedEePlanForm.tsx and directly relates to the archived field behavior.

Snippet from webapp/src/constants/GlobalValidationSchema.tsx (lines 44-511, excerpt)

  • Contains Yup validation schemas for various forms including EE_PLAN_FORM.
  • The EE_PLAN_FORM schema validates fields like name and stripeProductId.
  • Although the snippet does not explicitly show validation for archived, this file is the central place for form validation rules.
  • Changes to the optionality of archived might require updates here if archived is validated.

Summary of relevance:

  • The type definition for the form data (SelfHostedEePlanFormData) is relevant to understand the data shape and optionality of archived.
  • The PlanArchivedSwitch component is directly related to the archived field UI and form state management.
  • The global validation schema file is relevant for understanding if and how archived is validated in the form.

[End of relevant snippets]


🧠 Learnings used
Learnt from: Anty0
PR: tolgee/tolgee-platform#3003
File: webapp/src/ee/glossary/components/AssignedProjectsSelect.tsx:33-34
Timestamp: 2025-06-04T19:05:18.736Z
Learning: Formik has known type annotation issues where meta.error may be typed incorrectly. This has been reported in https://github.com/jaredpalmer/formik/issues/3994. When working with Formik error handling, using type guards or careful type checking is recommended to work around these type annotation limitations rather than avoiding the `any` cast entirely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant