From a67fa13c32fbee877f7c8a80b49177589edc41e0 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 08:44:16 +0000 Subject: [PATCH 1/2] feat(ui): implement dynamic feature access control for deployments Replace the static `ENABLE_DEPLOY_FEATURE` constant with a dynamic feature access system. The new implementation evaluates deployment feature visibility based on PostHog feature flags and the presence of existing deployments for both users and organizations. - Add `feature-access` logic to determine deployment visibility - Implement `isDeployFeatureEnabled` for server-side route protection - Add `hasExistingDeployments` query to check for existing user/org deployments - Update sidebars (Personal and Organization) to conditionally show Deploy and Integrations links - Protect deployment and integration routes with dynamic feature checks - Add unit tests for deployment feature access logic --- .../components/OrganizationAppSidebar.tsx | 40 ++++++++++++----- .../(app)/components/PersonalAppSidebar.tsx | 34 ++++++++++----- .../app/(app)/deploy/[deploymentId]/page.tsx | 6 +-- apps/web/src/app/(app)/deploy/page.tsx | 6 +-- .../[id]/deploy/[deploymentId]/page.tsx | 10 ++--- .../(app)/organizations/[id]/deploy/page.tsx | 8 ++-- .../organizations/[id]/integrations/page.tsx | 6 --- apps/web/src/lib/constants.ts | 2 - .../user-deployments/deployments-service.ts | 15 +++++++ .../user-deployments/feature-access.test.ts | 43 +++++++++++++++++++ .../lib/user-deployments/feature-access.ts | 14 ++++++ .../is-deploy-feature-enabled.ts | 27 ++++++++++++ apps/web/src/routers/deployments-router.ts | 4 ++ .../organization-deployments-router.ts | 7 +++ 14 files changed, 178 insertions(+), 44 deletions(-) create mode 100644 apps/web/src/lib/user-deployments/feature-access.test.ts create mode 100644 apps/web/src/lib/user-deployments/feature-access.ts create mode 100644 apps/web/src/lib/user-deployments/is-deploy-feature-enabled.ts diff --git a/apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx b/apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx index 18a08fa70b..35d0a004a0 100644 --- a/apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx +++ b/apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx @@ -32,6 +32,7 @@ import { } from 'lucide-react'; import { usePathname } from 'next/navigation'; import { useEffect, useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; import OrganizationSwitcher from './OrganizationSwitcher'; import { useRoleTesting } from '@/contexts/RoleTestingContext'; import HeaderLogo from '@/components/HeaderLogo'; @@ -39,8 +40,13 @@ import { useOrganizationWithMembers } from '@/app/api/organizations/hooks'; import { useOrgKiloClawNavState } from '@/hooks/useOrgKiloClaw'; import SidebarMenuList from './SidebarMenuList'; import SidebarUserFooter from './SidebarUserFooter'; -import { ENABLE_DEPLOY_FEATURE } from '@/lib/constants'; import { useFeatureFlagEnabled } from 'posthog-js/react'; +import { useTRPC } from '@/lib/trpc/utils'; +import { + DEPLOY_FEATURE_FLAG, + DEPLOY_FEATURE_QUERY_STALE_TIME_MS, + shouldShowDeployFeature, +} from '@/lib/user-deployments/feature-access'; import { canManageOrganizationBilling } from '@kilocode/app-shared/organizations'; type OrganizationAppSidebarProps = React.ComponentProps & { @@ -51,6 +57,7 @@ export default function OrganizationAppSidebar({ organizationId, ...props }: OrganizationAppSidebarProps) { + const trpc = useTRPC(); const { data: user, isLoading } = useUser(); const pathname = usePathname(); const { assumedRole, setAssumedRole, setOriginalRole } = useRoleTesting(); @@ -61,7 +68,22 @@ export default function OrganizationAppSidebar({ // Feature flags const isAutoTriageFeatureEnabled = useFeatureFlagEnabled('auto-triage-feature'); const isAppBuilderEnabled = useFeatureFlagEnabled('app-builder-feature'); + const isDeployFlagEnabled = useFeatureFlagEnabled(DEPLOY_FEATURE_FLAG); const isDevelopment = process.env.NODE_ENV === 'development'; + const { data: hasExistingDeployments } = useQuery( + trpc.organizations.deployments.hasExistingDeployments.queryOptions( + { organizationId }, + { + enabled: !isDevelopment && isDeployFlagEnabled !== true, + staleTime: DEPLOY_FEATURE_QUERY_STALE_TIME_MS, + } + ) + ); + const isDeployEnabled = shouldShowDeployFeature({ + isDevelopment, + isFlagEnabled: isDeployFlagEnabled, + hasExistingDeployments, + }); // Get current organization role and data const currentOrg = organizationData; @@ -233,7 +255,7 @@ export default function OrganizationAppSidebar({ { title: 'Auto Fix', icon: Wrench, url: `/organizations/${organizationId}/auto-fix` }, ] : []), - ...(ENABLE_DEPLOY_FEATURE + ...(isDeployEnabled ? [ { title: 'Deploy', @@ -287,15 +309,11 @@ export default function OrganizationAppSidebar({ }, ] : []), - ...(ENABLE_DEPLOY_FEATURE - ? [ - { - title: 'Integrations', - icon: Cable, - url: `/organizations/${organizationId}/integrations`, - }, - ] - : []), + { + title: 'Integrations', + icon: Cable, + url: `/organizations/${organizationId}/integrations`, + }, ...(hasOwnerLevelAccess && currentOrg?.plan === 'enterprise' ? [ { diff --git a/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx b/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx index 8be8ec769c..45623f89d9 100644 --- a/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx +++ b/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx @@ -35,12 +35,16 @@ import OrganizationSwitcher from './OrganizationSwitcher'; import SidebarMenuList from './SidebarMenuList'; import SidebarPromoBanner from './SidebarPromoBanner'; import SidebarUserFooter from './SidebarUserFooter'; -import { ENABLE_DEPLOY_FEATURE } from '@/lib/constants'; import { isEnabledForUser } from '@/lib/code-indexing/util'; import { useFeatureFlagEnabled } from 'posthog-js/react'; import { usePathname } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; import { useTRPC } from '@/lib/trpc/utils'; +import { + DEPLOY_FEATURE_FLAG, + DEPLOY_FEATURE_QUERY_STALE_TIME_MS, + shouldShowDeployFeature, +} from '@/lib/user-deployments/feature-access'; const SIDEBAR_PROMO_ELIGIBILITY_STALE_TIME_MS = 5 * 60_000; @@ -59,7 +63,19 @@ export default function PersonalAppSidebar(props: React.ComponentProps; }) { - await getUserFromAuthOrRedirect(); + const user = await getUserFromAuthOrRedirect(); - if (!ENABLE_DEPLOY_FEATURE) { + if (!(await isDeployFeatureEnabled(user.id, { type: 'user', id: user.id }))) { return notFound(); } diff --git a/apps/web/src/app/(app)/deploy/page.tsx b/apps/web/src/app/(app)/deploy/page.tsx index 6be54599ca..16d5a7dc3a 100644 --- a/apps/web/src/app/(app)/deploy/page.tsx +++ b/apps/web/src/app/(app)/deploy/page.tsx @@ -1,12 +1,12 @@ import { getUserFromAuthOrRedirect } from '@/lib/user/server'; import { DeployPageClient } from './DeployPageClient'; import { notFound } from 'next/navigation'; -import { ENABLE_DEPLOY_FEATURE } from '@/lib/constants'; +import { isDeployFeatureEnabled } from '@/lib/user-deployments/is-deploy-feature-enabled'; export default async function DeployPage() { - await getUserFromAuthOrRedirect('/users/sign_in?callbackPath=/deploy'); + const user = await getUserFromAuthOrRedirect('/users/sign_in?callbackPath=/deploy'); - if (!ENABLE_DEPLOY_FEATURE) { + if (!(await isDeployFeatureEnabled(user.id, { type: 'user', id: user.id }))) { return notFound(); } diff --git a/apps/web/src/app/(app)/organizations/[id]/deploy/[deploymentId]/page.tsx b/apps/web/src/app/(app)/organizations/[id]/deploy/[deploymentId]/page.tsx index 0010996925..bbec960f50 100644 --- a/apps/web/src/app/(app)/organizations/[id]/deploy/[deploymentId]/page.tsx +++ b/apps/web/src/app/(app)/organizations/[id]/deploy/[deploymentId]/page.tsx @@ -2,21 +2,21 @@ import { getUserFromAuthOrRedirect } from '@/lib/user/server'; import { DeployPageClient } from '../DeployPageClient'; import { notFound } from 'next/navigation'; import { OrganizationByPageLayout } from '@/components/organizations/OrganizationByPageLayout'; -import { ENABLE_DEPLOY_FEATURE } from '@/lib/constants'; +import { isDeployFeatureEnabled } from '@/lib/user-deployments/is-deploy-feature-enabled'; export default async function OrgDeploymentDetailPage({ params, }: { params: Promise<{ id: string; deploymentId: string }>; }) { - await getUserFromAuthOrRedirect('/users/sign_in'); + const user = await getUserFromAuthOrRedirect('/users/sign_in'); + const { id, deploymentId } = await params; + const organizationId = decodeURIComponent(id); - if (!ENABLE_DEPLOY_FEATURE) { + if (!(await isDeployFeatureEnabled(user.id, { type: 'org', id: organizationId }))) { return notFound(); } - const { deploymentId } = await params; - return ( ; }) { - await getUserFromAuthOrRedirect('/users/sign_in'); + const user = await getUserFromAuthOrRedirect('/users/sign_in'); + const { id } = await params; + const organizationId = decodeURIComponent(id); - if (!ENABLE_DEPLOY_FEATURE) { + if (!(await isDeployFeatureEnabled(user.id, { type: 'org', id: organizationId }))) { return notFound(); } diff --git a/apps/web/src/app/(app)/organizations/[id]/integrations/page.tsx b/apps/web/src/app/(app)/organizations/[id]/integrations/page.tsx index bf1001e823..a5815637c3 100644 --- a/apps/web/src/app/(app)/organizations/[id]/integrations/page.tsx +++ b/apps/web/src/app/(app)/organizations/[id]/integrations/page.tsx @@ -3,16 +3,10 @@ import { IntegrationsPageClient } from './IntegrationsPageClient'; import { OrganizationByPageLayout } from '@/components/organizations/OrganizationByPageLayout'; import { SetPageTitle } from '@/components/SetPageTitle'; import { getUserFromAuthOrRedirect } from '@/lib/user/server'; -import { notFound } from 'next/navigation'; -import { ENABLE_DEPLOY_FEATURE } from '@/lib/constants'; export default async function IntegrationsPage({ params }: { params: Promise<{ id: string }> }) { await getUserFromAuthOrRedirect('/users/sign_in'); - if (!ENABLE_DEPLOY_FEATURE) { - return notFound(); - } - return ( { + const ownershipCondition = + owner.type === 'user' + ? eq(deployments.owned_by_user_id, owner.id) + : eq(deployments.owned_by_organization_id, owner.id); + + const [row] = await db + .select({ id: deployments.id }) + .from(deployments) + .where(and(ownershipCondition, eq(deployments.created_from, 'deploy'))) + .limit(1); + + return row !== undefined; +} + /** * Get a single deployment with its latest build */ diff --git a/apps/web/src/lib/user-deployments/feature-access.test.ts b/apps/web/src/lib/user-deployments/feature-access.test.ts new file mode 100644 index 0000000000..3ba7761668 --- /dev/null +++ b/apps/web/src/lib/user-deployments/feature-access.test.ts @@ -0,0 +1,43 @@ +import { shouldShowDeployFeature } from './feature-access'; + +describe('shouldShowDeployFeature', () => { + it('shows deploy in development even without the flag or existing deployments', () => { + expect( + shouldShowDeployFeature({ + isDevelopment: true, + isFlagEnabled: false, + hasExistingDeployments: false, + }) + ).toBe(true); + }); + + it('shows deploy when the feature flag is enabled', () => { + expect( + shouldShowDeployFeature({ + isDevelopment: false, + isFlagEnabled: true, + hasExistingDeployments: false, + }) + ).toBe(true); + }); + + it('shows deploy for owners who already have deployments', () => { + expect( + shouldShowDeployFeature({ + isDevelopment: false, + isFlagEnabled: false, + hasExistingDeployments: true, + }) + ).toBe(true); + }); + + it('hides deploy for other users while flags are loading', () => { + expect( + shouldShowDeployFeature({ + isDevelopment: false, + isFlagEnabled: undefined, + hasExistingDeployments: undefined, + }) + ).toBe(false); + }); +}); diff --git a/apps/web/src/lib/user-deployments/feature-access.ts b/apps/web/src/lib/user-deployments/feature-access.ts new file mode 100644 index 0000000000..1e4f96cebc --- /dev/null +++ b/apps/web/src/lib/user-deployments/feature-access.ts @@ -0,0 +1,14 @@ +export const DEPLOY_FEATURE_FLAG = 'deploy-feature'; +export const DEPLOY_FEATURE_QUERY_STALE_TIME_MS = 5 * 60_000; + +export function shouldShowDeployFeature(options: { + isDevelopment: boolean; + isFlagEnabled: boolean | undefined; + hasExistingDeployments: boolean | undefined; +}): boolean { + return ( + options.isDevelopment || + options.isFlagEnabled === true || + options.hasExistingDeployments === true + ); +} diff --git a/apps/web/src/lib/user-deployments/is-deploy-feature-enabled.ts b/apps/web/src/lib/user-deployments/is-deploy-feature-enabled.ts new file mode 100644 index 0000000000..c955924ae0 --- /dev/null +++ b/apps/web/src/lib/user-deployments/is-deploy-feature-enabled.ts @@ -0,0 +1,27 @@ +import 'server-only'; + +import type { Owner } from '@/lib/integrations/core/types'; +import { isFeatureFlagEnabled } from '@/lib/posthog-feature-flags'; +import { hasExistingDeployments } from '@/lib/user-deployments/deployments-service'; +import { + DEPLOY_FEATURE_FLAG, + shouldShowDeployFeature, +} from '@/lib/user-deployments/feature-access'; + +export async function isDeployFeatureEnabled(userId: string, owner: Owner): Promise { + const isDevelopment = process.env.NODE_ENV === 'development'; + if (isDevelopment) { + return true; + } + + const [isFlagEnabled, hasExisting] = await Promise.all([ + isFeatureFlagEnabled(DEPLOY_FEATURE_FLAG, userId), + hasExistingDeployments(owner), + ]); + + return shouldShowDeployFeature({ + isDevelopment: false, + isFlagEnabled, + hasExistingDeployments: hasExisting, + }); +} diff --git a/apps/web/src/routers/deployments-router.ts b/apps/web/src/routers/deployments-router.ts index d141618785..3da9e7afa5 100644 --- a/apps/web/src/routers/deployments-router.ts +++ b/apps/web/src/routers/deployments-router.ts @@ -22,6 +22,10 @@ export const deploymentsRouter = createTRPCRouter({ return deploymentsService.listDeployments({ type: 'user', id: ctx.user.id }); }), + hasExistingDeployments: baseProcedure.query(async ({ ctx }) => { + return deploymentsService.hasExistingDeployments({ type: 'user', id: ctx.user.id }); + }), + getDeployment: baseProcedure .input(z.object({ id: z.string().uuid() })) .query(async ({ ctx, input }) => { diff --git a/apps/web/src/routers/organizations/organization-deployments-router.ts b/apps/web/src/routers/organizations/organization-deployments-router.ts index bae576a73f..6589e99150 100644 --- a/apps/web/src/routers/organizations/organization-deployments-router.ts +++ b/apps/web/src/routers/organizations/organization-deployments-router.ts @@ -27,6 +27,13 @@ export const organizationDeploymentsRouter = createTRPCRouter({ }); }), + hasExistingDeployments: organizationMemberProcedure.query(async ({ input }) => { + return deploymentsService.hasExistingDeployments({ + type: 'org', + id: input.organizationId, + }); + }), + getDeployment: organizationMemberProcedure .input( z.object({ From 1c7f35688b58331080017d707b83ed4d7d069d03 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 08:49:06 +0000 Subject: [PATCH 2/2] refactor(ui): simplify deployment feature access control Remove the complex logic that checked for existing deployments to determine feature access. Instead, the deployment feature is now controlled strictly by the 'deploy-feature' PostHog flag, while still allowing access in development environments. This change involves: - Deleting `feature-access.ts`, `is-deploy-feature-enabled.ts`, and `feature-access.test.ts`. - Removing `hasExistingDeployments` from `deployments-service.ts` and the corresponding TRPC routers. - Updating sidebar components and deployment pages to use the simplified feature flag check. --- .../components/OrganizationAppSidebar.tsx | 26 +---------- .../(app)/components/PersonalAppSidebar.tsx | 20 +-------- .../app/(app)/deploy/[deploymentId]/page.tsx | 7 ++- apps/web/src/app/(app)/deploy/page.tsx | 7 ++- .../[id]/deploy/[deploymentId]/page.tsx | 11 +++-- .../(app)/organizations/[id]/deploy/page.tsx | 9 ++-- .../user-deployments/deployments-service.ts | 15 ------- .../user-deployments/feature-access.test.ts | 43 ------------------- .../lib/user-deployments/feature-access.ts | 14 ------ .../is-deploy-feature-enabled.ts | 27 ------------ apps/web/src/routers/deployments-router.ts | 4 -- .../organization-deployments-router.ts | 7 --- 12 files changed, 26 insertions(+), 164 deletions(-) delete mode 100644 apps/web/src/lib/user-deployments/feature-access.test.ts delete mode 100644 apps/web/src/lib/user-deployments/feature-access.ts delete mode 100644 apps/web/src/lib/user-deployments/is-deploy-feature-enabled.ts diff --git a/apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx b/apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx index 35d0a004a0..e3edec625b 100644 --- a/apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx +++ b/apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx @@ -32,7 +32,6 @@ import { } from 'lucide-react'; import { usePathname } from 'next/navigation'; import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; import OrganizationSwitcher from './OrganizationSwitcher'; import { useRoleTesting } from '@/contexts/RoleTestingContext'; import HeaderLogo from '@/components/HeaderLogo'; @@ -41,12 +40,6 @@ import { useOrgKiloClawNavState } from '@/hooks/useOrgKiloClaw'; import SidebarMenuList from './SidebarMenuList'; import SidebarUserFooter from './SidebarUserFooter'; import { useFeatureFlagEnabled } from 'posthog-js/react'; -import { useTRPC } from '@/lib/trpc/utils'; -import { - DEPLOY_FEATURE_FLAG, - DEPLOY_FEATURE_QUERY_STALE_TIME_MS, - shouldShowDeployFeature, -} from '@/lib/user-deployments/feature-access'; import { canManageOrganizationBilling } from '@kilocode/app-shared/organizations'; type OrganizationAppSidebarProps = React.ComponentProps & { @@ -57,7 +50,6 @@ export default function OrganizationAppSidebar({ organizationId, ...props }: OrganizationAppSidebarProps) { - const trpc = useTRPC(); const { data: user, isLoading } = useUser(); const pathname = usePathname(); const { assumedRole, setAssumedRole, setOriginalRole } = useRoleTesting(); @@ -68,22 +60,8 @@ export default function OrganizationAppSidebar({ // Feature flags const isAutoTriageFeatureEnabled = useFeatureFlagEnabled('auto-triage-feature'); const isAppBuilderEnabled = useFeatureFlagEnabled('app-builder-feature'); - const isDeployFlagEnabled = useFeatureFlagEnabled(DEPLOY_FEATURE_FLAG); + const isDeployEnabled = useFeatureFlagEnabled('deploy-feature'); const isDevelopment = process.env.NODE_ENV === 'development'; - const { data: hasExistingDeployments } = useQuery( - trpc.organizations.deployments.hasExistingDeployments.queryOptions( - { organizationId }, - { - enabled: !isDevelopment && isDeployFlagEnabled !== true, - staleTime: DEPLOY_FEATURE_QUERY_STALE_TIME_MS, - } - ) - ); - const isDeployEnabled = shouldShowDeployFeature({ - isDevelopment, - isFlagEnabled: isDeployFlagEnabled, - hasExistingDeployments, - }); // Get current organization role and data const currentOrg = organizationData; @@ -255,7 +233,7 @@ export default function OrganizationAppSidebar({ { title: 'Auto Fix', icon: Wrench, url: `/organizations/${organizationId}/auto-fix` }, ] : []), - ...(isDeployEnabled + ...(isDeployEnabled || isDevelopment ? [ { title: 'Deploy', diff --git a/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx b/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx index 45623f89d9..0fe0fefe22 100644 --- a/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx +++ b/apps/web/src/app/(app)/components/PersonalAppSidebar.tsx @@ -40,11 +40,6 @@ import { useFeatureFlagEnabled } from 'posthog-js/react'; import { usePathname } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; import { useTRPC } from '@/lib/trpc/utils'; -import { - DEPLOY_FEATURE_FLAG, - DEPLOY_FEATURE_QUERY_STALE_TIME_MS, - shouldShowDeployFeature, -} from '@/lib/user-deployments/feature-access'; const SIDEBAR_PROMO_ELIGIBILITY_STALE_TIME_MS = 5 * 60_000; @@ -63,19 +58,8 @@ export default function PersonalAppSidebar(props: React.ComponentProps; }) { const user = await getUserFromAuthOrRedirect('/users/sign_in'); - const { id, deploymentId } = await params; - const organizationId = decodeURIComponent(id); - if (!(await isDeployFeatureEnabled(user.id, { type: 'org', id: organizationId }))) { + const isDeployEnabled = await isFeatureFlagEnabled('deploy-feature', user.id); + const isDevelopment = process.env.NODE_ENV === 'development'; + + if (!isDeployEnabled && !isDevelopment) { return notFound(); } + const { deploymentId } = await params; + return ( ; }) { const user = await getUserFromAuthOrRedirect('/users/sign_in'); - const { id } = await params; - const organizationId = decodeURIComponent(id); - if (!(await isDeployFeatureEnabled(user.id, { type: 'org', id: organizationId }))) { + const isDeployEnabled = await isFeatureFlagEnabled('deploy-feature', user.id); + const isDevelopment = process.env.NODE_ENV === 'development'; + + if (!isDeployEnabled && !isDevelopment) { return notFound(); } diff --git a/apps/web/src/lib/user-deployments/deployments-service.ts b/apps/web/src/lib/user-deployments/deployments-service.ts index f96396f5c3..924d005244 100644 --- a/apps/web/src/lib/user-deployments/deployments-service.ts +++ b/apps/web/src/lib/user-deployments/deployments-service.ts @@ -124,21 +124,6 @@ export async function listDeployments(owner: Owner) { }; } -export async function hasExistingDeployments(owner: Owner): Promise { - const ownershipCondition = - owner.type === 'user' - ? eq(deployments.owned_by_user_id, owner.id) - : eq(deployments.owned_by_organization_id, owner.id); - - const [row] = await db - .select({ id: deployments.id }) - .from(deployments) - .where(and(ownershipCondition, eq(deployments.created_from, 'deploy'))) - .limit(1); - - return row !== undefined; -} - /** * Get a single deployment with its latest build */ diff --git a/apps/web/src/lib/user-deployments/feature-access.test.ts b/apps/web/src/lib/user-deployments/feature-access.test.ts deleted file mode 100644 index 3ba7761668..0000000000 --- a/apps/web/src/lib/user-deployments/feature-access.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { shouldShowDeployFeature } from './feature-access'; - -describe('shouldShowDeployFeature', () => { - it('shows deploy in development even without the flag or existing deployments', () => { - expect( - shouldShowDeployFeature({ - isDevelopment: true, - isFlagEnabled: false, - hasExistingDeployments: false, - }) - ).toBe(true); - }); - - it('shows deploy when the feature flag is enabled', () => { - expect( - shouldShowDeployFeature({ - isDevelopment: false, - isFlagEnabled: true, - hasExistingDeployments: false, - }) - ).toBe(true); - }); - - it('shows deploy for owners who already have deployments', () => { - expect( - shouldShowDeployFeature({ - isDevelopment: false, - isFlagEnabled: false, - hasExistingDeployments: true, - }) - ).toBe(true); - }); - - it('hides deploy for other users while flags are loading', () => { - expect( - shouldShowDeployFeature({ - isDevelopment: false, - isFlagEnabled: undefined, - hasExistingDeployments: undefined, - }) - ).toBe(false); - }); -}); diff --git a/apps/web/src/lib/user-deployments/feature-access.ts b/apps/web/src/lib/user-deployments/feature-access.ts deleted file mode 100644 index 1e4f96cebc..0000000000 --- a/apps/web/src/lib/user-deployments/feature-access.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const DEPLOY_FEATURE_FLAG = 'deploy-feature'; -export const DEPLOY_FEATURE_QUERY_STALE_TIME_MS = 5 * 60_000; - -export function shouldShowDeployFeature(options: { - isDevelopment: boolean; - isFlagEnabled: boolean | undefined; - hasExistingDeployments: boolean | undefined; -}): boolean { - return ( - options.isDevelopment || - options.isFlagEnabled === true || - options.hasExistingDeployments === true - ); -} diff --git a/apps/web/src/lib/user-deployments/is-deploy-feature-enabled.ts b/apps/web/src/lib/user-deployments/is-deploy-feature-enabled.ts deleted file mode 100644 index c955924ae0..0000000000 --- a/apps/web/src/lib/user-deployments/is-deploy-feature-enabled.ts +++ /dev/null @@ -1,27 +0,0 @@ -import 'server-only'; - -import type { Owner } from '@/lib/integrations/core/types'; -import { isFeatureFlagEnabled } from '@/lib/posthog-feature-flags'; -import { hasExistingDeployments } from '@/lib/user-deployments/deployments-service'; -import { - DEPLOY_FEATURE_FLAG, - shouldShowDeployFeature, -} from '@/lib/user-deployments/feature-access'; - -export async function isDeployFeatureEnabled(userId: string, owner: Owner): Promise { - const isDevelopment = process.env.NODE_ENV === 'development'; - if (isDevelopment) { - return true; - } - - const [isFlagEnabled, hasExisting] = await Promise.all([ - isFeatureFlagEnabled(DEPLOY_FEATURE_FLAG, userId), - hasExistingDeployments(owner), - ]); - - return shouldShowDeployFeature({ - isDevelopment: false, - isFlagEnabled, - hasExistingDeployments: hasExisting, - }); -} diff --git a/apps/web/src/routers/deployments-router.ts b/apps/web/src/routers/deployments-router.ts index 3da9e7afa5..d141618785 100644 --- a/apps/web/src/routers/deployments-router.ts +++ b/apps/web/src/routers/deployments-router.ts @@ -22,10 +22,6 @@ export const deploymentsRouter = createTRPCRouter({ return deploymentsService.listDeployments({ type: 'user', id: ctx.user.id }); }), - hasExistingDeployments: baseProcedure.query(async ({ ctx }) => { - return deploymentsService.hasExistingDeployments({ type: 'user', id: ctx.user.id }); - }), - getDeployment: baseProcedure .input(z.object({ id: z.string().uuid() })) .query(async ({ ctx, input }) => { diff --git a/apps/web/src/routers/organizations/organization-deployments-router.ts b/apps/web/src/routers/organizations/organization-deployments-router.ts index 6589e99150..bae576a73f 100644 --- a/apps/web/src/routers/organizations/organization-deployments-router.ts +++ b/apps/web/src/routers/organizations/organization-deployments-router.ts @@ -27,13 +27,6 @@ export const organizationDeploymentsRouter = createTRPCRouter({ }); }), - hasExistingDeployments: organizationMemberProcedure.query(async ({ input }) => { - return deploymentsService.hasExistingDeployments({ - type: 'org', - id: input.organizationId, - }); - }), - getDeployment: organizationMemberProcedure .input( z.object({