From f60cb22f11299980fd2feaca22b6651840b131a8 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 18 Aug 2023 20:05:14 +1000 Subject: [PATCH 1/3] feat: feature flags --- .env.example | 7 +- apps/web/package.json | 2 + apps/web/process-env.d.ts | 2 - .../app/(dashboard)/settings/billing/page.tsx | 6 +- apps/web/src/app/layout.tsx | 31 +++-- .../(dashboard)/layout/profile-dropdown.tsx | 13 +- .../settings/layout/desktop-nav.tsx | 9 +- .../settings/layout/mobile-nav.tsx | 9 +- .../forms/edit-document/add-fields.tsx | 2 +- ...entRect.ts => get-bounding-client-rect.ts} | 0 apps/web/src/helpers/get-feature-flag.ts | 79 ++++++++++++ .../src/helpers/get-post-hog-server-client.ts | 16 +++ .../get-server-component-feature-flag.ts | 26 ++++ apps/web/src/hooks/use-field-page-coords.ts | 2 +- apps/web/src/pages/api/feature-flag/all.ts | 44 +++++++ apps/web/src/pages/api/feature-flag/get.ts | 122 ++++++++++++++++++ apps/web/src/providers/feature-flag.tsx | 96 ++++++++++++++ apps/web/src/providers/posthog.tsx | 52 ++++++++ package-lock.json | 116 +++++++++++++++++ packages/lib/constants/feature-flags.ts | 37 ++++++ packages/lib/constants/features.ts | 5 - packages/tsconfig/process-env.d.ts | 2 - turbo.json | 19 ++- 23 files changed, 661 insertions(+), 36 deletions(-) rename apps/web/src/helpers/{getBoundingClientRect.ts => get-bounding-client-rect.ts} (100%) create mode 100644 apps/web/src/helpers/get-feature-flag.ts create mode 100644 apps/web/src/helpers/get-post-hog-server-client.ts create mode 100644 apps/web/src/helpers/get-server-component-feature-flag.ts create mode 100644 apps/web/src/pages/api/feature-flag/all.ts create mode 100644 apps/web/src/pages/api/feature-flag/get.ts create mode 100644 apps/web/src/providers/feature-flag.tsx create mode 100644 apps/web/src/providers/posthog.tsx create mode 100644 packages/lib/constants/feature-flags.ts delete mode 100644 packages/lib/constants/features.ts diff --git a/.env.example b/.env.example index 47dea109e..afeb90343 100644 --- a/.env.example +++ b/.env.example @@ -52,7 +52,12 @@ NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID= NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID= # [[FEATURES]] -NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED=false +# OPTIONAL: Leave blank to disable PostHog and feature flags. +NEXT_PUBLIC_POSTHOG_KEY="" +# OPTIONAL: Defines the host to use for PostHog. +NEXT_PUBLIC_POSTHOG_HOST="https://eu.posthog.com" +# OPTIONAL: Leave blank to disable billing. +NEXT_PUBLIC_FEATURE_BILLING_ENABLED= # This is only required for the marketing site # [[REDIS]] diff --git a/apps/web/package.json b/apps/web/package.json index 31e8ff4f1..94c6e61af 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -27,6 +27,8 @@ "next-plausible": "^3.10.1", "next-themes": "^0.2.1", "perfect-freehand": "^1.2.0", + "posthog-js": "^1.75.3", + "posthog-node": "^3.1.1", "react": "18.2.0", "react-dom": "18.2.0", "react-dropzone": "^14.2.3", diff --git a/apps/web/process-env.d.ts b/apps/web/process-env.d.ts index e91137fe1..1cb0018ac 100644 --- a/apps/web/process-env.d.ts +++ b/apps/web/process-env.d.ts @@ -10,8 +10,6 @@ declare namespace NodeJS { NEXT_PRIVATE_STRIPE_API_KEY: string; NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string; - NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED: string; - NEXT_PRIVATE_GOOGLE_CLIENT_ID: string; NEXT_PRIVATE_GOOGLE_CLIENT_SECRET: string; } diff --git a/apps/web/src/app/(dashboard)/settings/billing/page.tsx b/apps/web/src/app/(dashboard)/settings/billing/page.tsx index 76d9df688..8773f1aa8 100644 --- a/apps/web/src/app/(dashboard)/settings/billing/page.tsx +++ b/apps/web/src/app/(dashboard)/settings/billing/page.tsx @@ -1,15 +1,17 @@ import { redirect } from 'next/navigation'; -import { IS_SUBSCRIPTIONS_ENABLED } from '@documenso/lib/constants/features'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; import { PasswordForm } from '~/components/forms/password'; +import { getServerComponentFlag } from '~/helpers/get-server-component-feature-flag'; export default async function BillingSettingsPage() { const user = await getRequiredServerComponentSession(); + const isBillingEnabled = await getServerComponentFlag('billing'); + // Redirect if subscriptions are not enabled. - if (!IS_SUBSCRIPTIONS_ENABLED) { + if (!isBillingEnabled) { redirect('/settings/profile'); } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 5bf2b9403..1d1e056ae 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,3 +1,5 @@ +import { Suspense } from 'react'; + import { Caveat, Inter } from 'next/font/google'; import { TrpcProvider } from '@documenso/trpc/react'; @@ -5,8 +7,11 @@ import { cn } from '@documenso/ui/lib/utils'; import { Toaster } from '@documenso/ui/primitives/toaster'; import { TooltipProvider } from '@documenso/ui/primitives/tooltip'; +import { getServerComponentAllFlags } from '~/helpers/get-server-component-feature-flag'; +import { FeatureFlagProvider } from '~/providers/feature-flag'; import { ThemeProvider } from '~/providers/next-theme'; import { PlausibleProvider } from '~/providers/plausible'; +import { PostHogPageview } from '~/providers/posthog'; import './globals.css'; @@ -37,7 +42,9 @@ export const metadata = { }, }; -export default function RootLayout({ children }: { children: React.ReactNode }) { +export default async function RootLayout({ children }: { children: React.ReactNode }) { + const flags = await getServerComponentAllFlags(); + return ( + + + + - - - - {children} - - - - + + + + + {children} + + + + + ); diff --git a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx index e07b786c3..fb57beff8 100644 --- a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx +++ b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx @@ -15,7 +15,6 @@ import { import { signOut } from 'next-auth/react'; import { useTheme } from 'next-themes'; -import { IS_SUBSCRIPTIONS_ENABLED } from '@documenso/lib/constants/features'; import { User } from '@documenso/prisma/client'; import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar'; import { Button } from '@documenso/ui/primitives/button'; @@ -28,11 +27,19 @@ import { DropdownMenuTrigger, } from '@documenso/ui/primitives/dropdown-menu'; +import { useFeatureFlags } from '~/providers/feature-flag'; + export type ProfileDropdownProps = { user: User; }; export const ProfileDropdown = ({ user }: ProfileDropdownProps) => { + const { theme, setTheme } = useTheme(); + + const { getFlag } = useFeatureFlags(); + + const isBillingEnabled = getFlag('billing'); + const initials = user.name ?.split(' ') @@ -40,8 +47,6 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => { .slice(0, 2) .join('') ?? 'UK'; - const { theme, setTheme } = useTheme(); - return ( @@ -69,7 +74,7 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => { - {IS_SUBSCRIPTIONS_ENABLED && ( + {isBillingEnabled && ( diff --git a/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx b/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx index 0c9e6eb19..6cce0e62e 100644 --- a/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx +++ b/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx @@ -7,15 +7,20 @@ import { usePathname } from 'next/navigation'; import { CreditCard, Key, User } from 'lucide-react'; -import { IS_SUBSCRIPTIONS_ENABLED } from '@documenso/lib/constants/features'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; +import { useFeatureFlags } from '~/providers/feature-flag'; + export type DesktopNavProps = HTMLAttributes; export const DesktopNav = ({ className, ...props }: DesktopNavProps) => { const pathname = usePathname(); + const { getFlag } = useFeatureFlags(); + + const isBillingEnabled = getFlag('billing'); + return (
@@ -44,7 +49,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => { - {IS_SUBSCRIPTIONS_ENABLED && ( + {isBillingEnabled && ( - {IS_SUBSCRIPTIONS_ENABLED && ( + {isBillingEnabled && (