-
Notifications
You must be signed in to change notification settings - Fork 114
fix: hide pro plan badge on self-hosted instances #151
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
Conversation
Reviewer's GuideThis PR introduces a host-check utility to distinguish official and self-hosted deployments and integrates it throughout the subscription logic so self-hosted instances are treated as Pro by default, removing Pro badges and upgrade prompts; it also updates UpgradePlan and related UIs to filter out or guard Pro options on self-hosted, and brings in a comprehensive Storybook setup, docs and stories to illustrate these scenarios. Sequence diagram for subscription plan check in UI componentssequenceDiagram
participant UIComponent
participant isOfficialHost
participant SubscriptionPlan
UIComponent->>isOfficialHost: isOfficialHost()
isOfficialHost-->>UIComponent: true/false
UIComponent->>SubscriptionPlan: Check activeSubscriptionPlan
alt Official host
UIComponent->>UIComponent: Show Pro features only if Pro plan
else Self-hosted
UIComponent->>UIComponent: Enable Pro features by default
end
Class diagram for subscription plan logic updateclassDiagram
class UpgradePlan {
+open: boolean
+onClose(): void
+onOpen(): void
+handleUpgrade()
+plans
}
class isOfficialHost {
+isOfficialHost(): boolean
}
UpgradePlan --> isOfficialHost : uses
class UpgradeBanner {
+activeSubscriptionPlan: SubscriptionPlan
+handleUpgrade()
}
UpgradeBanner --> isOfficialHost : uses
class HomePageSetting {
+activePlan: SubscriptionPlan
+isOwner: boolean
}
HomePageSetting --> isOfficialHost : uses
class OptionMenu {
+activeSubscriptionPlan: SubscriptionPlan
}
OptionMenu --> isOfficialHost : uses
class BgColor {
+activeSubscriptionPlan: SubscriptionPlan
}
BgColor --> isOfficialHost : uses
class TextColor {
+activeSubscriptionPlan: SubscriptionPlan
}
TextColor --> isOfficialHost : uses
class CalloutTextColor {
+activeSubscriptionPlan: SubscriptionPlan
}
CalloutTextColor --> isOfficialHost : uses
class Color {
+activeSubscriptionPlan: SubscriptionPlan
}
Color --> isOfficialHost : uses
class CoverPopover {
+activeSubscriptionPlan: SubscriptionPlan
}
CoverPopover --> isOfficialHost : uses
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `.storybook/GUIDE.md:441-443` </location>
<code_context>
+<Story />
+```
+
+### Issue 2: "useUserWorkspaceInfo must be used within an AppProvider"
+
+**Problem**: Component uses `useUserWorkspaceInfo()` or other AppContext hooks but no `AppContext.Provider` is provided.
</code_context>
<issue_to_address>
**suggestion (typo):** Typo: 'AppProvider' should be 'AppContext.Provider'.
Clarify in the documentation or error message that 'AppProvider' refers to 'AppContext.Provider' to avoid confusion.
```suggestion
### Issue 2: "useUserWorkspaceInfo must be used within an AppContext.Provider"
**Problem**: Component uses `useUserWorkspaceInfo()` or other AppContext hooks but no `AppContext.Provider` is provided.
> **Note:** In this context, "AppProvider" refers to `AppContext.Provider`. Make sure to wrap your component with `AppContext.Provider` to avoid this error.
```
</issue_to_address>
### Comment 2
<location> `src/components/editor/components/toolbar/selection-toolbar/actions/BgColor.tsx:189` </location>
<code_context>
const builtinColors = useMemo(() => {
return isPro
? [
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting color definitions into a shared constant with a proOnly flag to eliminate duplication and simplify filtering.
Here’s one way to collapse both lists into a single source of truth. First pull your colors out into a shared constant with a `proOnly` flag:
```ts
// outside your component (e.g. in a constants file)
export const BG_COLOR_DEFINITIONS = [
{ key: 'colors.default', color: '', proOnly: false },
{ key: 'colors.mauve', color: 'bg-color-14', proOnly: false },
{ key: 'colors.lavender',color: 'bg-color-15', proOnly: true },
{ key: 'colors.lilac', color: 'bg-color-16', proOnly: false },
{ key: 'colors.mallow', color: 'bg-color-17', proOnly: true },
{ key: 'colors.camellia',color: 'bg-color-18', proOnly: false },
{ key: 'colors.rose', color: 'bg-color-1', proOnly: true },
// …and so on
];
```
Then inside your component you can collapse the two branches into a single filter+map:
```ts
const builtinColors = useMemo(() => {
return BG_COLOR_DEFINITIONS
.filter(def => !def.proOnly || isPro)
.map(def => ({
label: t(def.key),
color: def.color
}));
}, [isPro, t]);
```
This:
- Removes the duplicated literals
- Keeps ordering
- Makes it easy to add/remove a single color without touching two arrays
</issue_to_address>
### Comment 3
<location> `src/components/editor/components/toolbar/selection-toolbar/actions/TextColor.tsx:189` </location>
<code_context>
const builtinColors = useMemo(() => {
return isPro
? [
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the color arrays into shared base and pro-only lists to avoid duplication and simplify future changes.
You can collapse those two huge arrays into a single source-of-truth + a tiny “pro only” list. For example, at the top of TextColor.tsx (or in a new file):
```ts
// sharedColors.ts
export const baseBuiltinColors = [
{ key: 'colors.default', color: '' },
{ key: 'colors.mauve', color: 'text-color-14' },
{ key: 'colors.lilac', color: 'text-color-16' },
{ key: 'colors.camellia',color: 'text-color-18' },
{ key: 'colors.papaya', color: 'text-color-2' },
{ key: 'colors.mango', color: 'text-color-4' },
{ key: 'colors.olive', color: 'text-color-6' },
{ key: 'colors.grass', color: 'text-color-8' },
{ key: 'colors.jade', color: 'text-color-10' },
{ key: 'colors.azure', color: 'text-color-12' },
];
export const proExtraBuiltinColors = [
{ key: 'colors.lavender', color: 'text-color-15' },
{ key: 'colors.mallow', color: 'text-color-17' },
{ key: 'colors.rose', color: 'text-color-1' },
{ key: 'colors.lemon', color: 'text-color-5' },
{ key: 'colors.iron', color: 'text-color-20' },
];
```
Then in your component:
```ts
import { baseBuiltinColors, proExtraBuiltinColors } from './sharedColors';
const builtinColors = useMemo(() => {
const list = isPro
? [...baseBuiltinColors, ...proExtraBuiltinColors]
: baseBuiltinColors;
return list.map(({ key, color }) => ({
label: t(key),
color,
}));
}, [isPro, t]);
```
This:
- Removes the duplicated literal arrays.
- Keeps the exact same ordering and content.
- Makes it trivial to add/remove pro-only vs free colors.
</issue_to_address>
### Comment 4
<location> `.storybook/decorators.tsx:31` </location>
<code_context>
+ * Decorator that provides AFConfigContext with mock values
+ * Use this for components that need authentication context
+ */
+export const withAFConfig = (Story: React.ComponentType) => (
+ <AFConfigContext.Provider value={mockAFConfigValue}>
+ <Story />
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the decorators by using generic factory functions and a custom hook to eliminate duplication and simplify the code.
You can collapse almost‐identical providers into two small factories, pull your hostname logic into a hook/HOC, and then just call those. For example:
```ts
// 1) Generic single‐context factory
function createContextDecorator<C>(
Context: React.Context<C>,
value: C
) {
return (Story: React.ComponentType) => (
<Context.Provider value={value}>
<Story />
</Context.Provider>
);
}
export const withAFConfig = createContextDecorator(AFConfigContext, mockAFConfigValue);
export const withAFConfigMinimal = createContextDecorator(AFConfigContext, mockAFConfigValueMinimal);
export const withAppContext = createContextDecorator(AppContext, mockAppContextValue);
```
```ts
// 2) Dual‐context factory (minimal vs full)
const createDualContextDecorator = (minimal = false) => {
const v = minimal ? mockAFConfigValueMinimal : mockAFConfigValue;
return (Story: React.ComponentType) => (
<AFConfigContext.Provider value={v}>
<AppContext.Provider value={mockAppContextValue}>
<Story />
</AppContext.Provider>
</AFConfigContext.Provider>
);
};
export const withContexts = createDualContextDecorator();
export const withContextsMinimal = createDualContextDecorator(true);
```
```ts
// 3) Pull hostname logic into a hook/HOC
const useMockHostname = (hostname = 'beta.appflowy.cloud') => {
useEffect(() => {
mockHostname(hostname);
return () => { delete window.__STORYBOOK_MOCK_HOSTNAME__; };
}, [hostname]);
};
export const withHostnameMocking: DecoratorFn = (Story, context) => {
useMockHostname(context.args.hostname);
return <Story />;
};
```
```ts
// 4) Compose for hostname + contexts + padding
export const withHostnameAndContexts = (
{ padding = '20px', maxWidth, minimalAFConfig = false } = {}
) => (Story: React.ComponentType, context) => {
useMockHostname(context.args.hostname);
const value = minimalAFConfig ? mockAFConfigValueMinimal : mockAFConfigValue;
return (
<AFConfigContext.Provider value={value}>
<AppContext.Provider value={mockAppContextValue}>
<div style={{ padding, ...(maxWidth && { maxWidth }) }}>
<Story />
</div>
</AppContext.Provider>
</AFConfigContext.Provider>
);
};
```
This drops 6 near‐duplicate components down to 2 factories + 1 hook/HOC and keeps all existing functionality.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…lication ## Summary Reviewed and refactored all Storybook stories to use shared utilities, eliminating ~200 lines of duplicate code while ensuring stories use current theme, config, and styles consistently. ## Changes ### New Shared Utilities (.storybook/) - **argTypes.ts**: Shared argTypes definitions (hostname, subscription, etc.) - **decorators.tsx**: Reusable decorators (context providers, hostname mocking) - **mocks.ts**: Shared mock context values (AFConfig, AppContext) ### Refactored Story Files - HomePageSetting.stories.tsx: Now uses shared utilities - UpgradeBanner.stories.tsx: Reduced from ~195 to ~80 lines - UpgradePlan.stories.tsx: Reduced from ~180 to ~95 lines - TextColor.stories.tsx: Uses shared argTypes - RecordNotFound.stories.tsx: Reduced from ~235 to ~170 lines ### Configuration Updates - **main.ts**: Added MUI optimizeDeps for proper theme support, removed deprecated buildStoriesJson - **GUIDE.md**: Comprehensive documentation with examples and best practices - **tsconfig.web.json**: Explicitly exclude .storybook/ from production builds ### Lint Fixes & Improvements - **ApproveRequestPage.tsx**: Added missing blank line (lint fix) - **subscription.ts**: Fixed type casting to avoid @typescript-eslint/no-explicit-any - **AppTheme.tsx**: Changed to named imports (better practice) ## Benefits - ✅ Zero code duplication across stories - ✅ Consistent theme, config, and styles across all stories - ✅ Better maintainability (change once, apply everywhere) - ✅ Improved type safety with shared utilities - ✅ Comprehensive documentation for future story development - ✅ Storybook files guaranteed excluded from production builds ## Testing - All linting passes (pnpm run lint) - Storybook configuration verified to not be included in production builds - Multiple layers of protection ensure isolation from main application Co-Authored-By: Claude <[email protected]>
06f2265 to
85ae0cb
Compare
3bda991 to
3991e85
Compare
fix: hide pro plan badge on self-hosted instances
Description
Checklist
General
Testing
Feature-Specific
Summary by Sourcery
Treat self-hosted deployments as Pro by default by implementing an isOfficialHost utility and hiding any Pro upgrade UI for them, and bolster Storybook with hostname mocking, shared utilities, and comprehensive documentation and stories.
New Features:
Bug Fixes:
Enhancements: