diff --git a/src/application/services/js-services/http/http_api.ts b/src/application/services/js-services/http/http_api.ts index e08e0682..990dfa53 100644 --- a/src/application/services/js-services/http/http_api.ts +++ b/src/application/services/js-services/http/http_api.ts @@ -71,6 +71,7 @@ import { RepeatedChatMessage } from '@/components/chat'; import { database_blob } from '@/proto/database_blob'; import { getAppFlowyFileUploadUrl, getAppFlowyFileUrl } from '@/utils/file-storage-url'; import { Log } from '@/utils/log'; +import { hasProAccessFromPlans } from '@/utils/subscription'; export * from './gotrue'; @@ -1671,7 +1672,7 @@ export async function uploadFile( if (file.size > 7 * 1024 * 1024) { const plan = await getActiveSubscription(workspaceId); - if (plan?.length === 0 || plan?.[0] === SubscriptionPlan.Free) { + if (!hasProAccessFromPlans(plan)) { notify.error('Your file is over 7 MB limit of the Free plan. Upgrade for unlimited uploads.'); return Promise.reject({ diff --git a/src/components/app/hooks/useSubscriptionPlan.ts b/src/components/app/hooks/useSubscriptionPlan.ts index ef998570..0d079280 100644 --- a/src/components/app/hooks/useSubscriptionPlan.ts +++ b/src/components/app/hooks/useSubscriptionPlan.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { Subscription, SubscriptionPlan } from '@/application/types'; -import { isAppFlowyHosted } from '@/utils/subscription'; +import { getProAccessPlanFromSubscriptions, isAppFlowyHosted } from '@/utils/subscription'; /** * Hook to manage subscription plan loading and Pro feature detection @@ -34,9 +34,7 @@ export function useSubscriptionPlan( return; } - const subscription = subscriptions[0]; - - setActiveSubscriptionPlan(subscription?.plan || SubscriptionPlan.Free); + setActiveSubscriptionPlan(getProAccessPlanFromSubscriptions(subscriptions)); } catch (e: unknown) { // Silently handle expected errors (API not initialized, no response data, etc.) // These are normal scenarios when the service isn't available or there's no subscription data @@ -67,4 +65,3 @@ export function useSubscriptionPlan( isPro, }; } - diff --git a/src/components/app/landing-pages/ApproveRequestPage.tsx b/src/components/app/landing-pages/ApproveRequestPage.tsx index e20c3119..29f55ae8 100644 --- a/src/components/app/landing-pages/ApproveRequestPage.tsx +++ b/src/components/app/landing-pages/ApproveRequestPage.tsx @@ -19,7 +19,7 @@ import { NormalModal } from '@/components/_shared/modal'; import { useService } from '@/components/main/app.hooks'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { cn } from '@/lib/utils'; -import { isAppFlowyHosted } from '@/utils/subscription'; +import { hasProAccessFromPlans, isAppFlowyHosted } from '@/utils/subscription'; const GuestLimitExceededCode = 1070; const REPEAT_REQUEST_CODE = 1122; @@ -29,7 +29,7 @@ function ApproveRequestPage() { const [requestInfo, setRequestInfo] = useState(null); const [currentPlans, setCurrentPlans] = useState([]); - const isPro = useMemo(() => currentPlans.includes(SubscriptionPlan.Pro), [currentPlans]); + const isPro = useMemo(() => hasProAccessFromPlans(currentPlans), [currentPlans]); const requestId = searchParams.get('request_id'); const service = useService(); const { t } = useTranslation(); diff --git a/src/components/app/publish-manage/PublishManage.tsx b/src/components/app/publish-manage/PublishManage.tsx index ac221d22..5db57c52 100644 --- a/src/components/app/publish-manage/PublishManage.tsx +++ b/src/components/app/publish-manage/PublishManage.tsx @@ -13,7 +13,7 @@ import PublishedPages from '@/components/app/publish-manage/PublishedPages'; import PublishPagesSkeleton from '@/components/app/publish-manage/PublishPagesSkeleton'; import UpdateNamespace from '@/components/app/publish-manage/UpdateNamespace'; import { useCurrentUser, useService } from '@/components/main/app.hooks'; -import { isAppFlowyHosted } from '@/utils/subscription'; +import { getProAccessPlanFromSubscriptions, isAppFlowyHosted } from '@/utils/subscription'; import { openUrl } from '@/utils/url'; export function PublishManage({ onClose }: { onClose?: () => void }) { @@ -190,9 +190,7 @@ export function PublishManage({ onClose }: { onClose?: () => void }) { return; } - const subscription = subscriptions[0]; - - setActiveSubscription(subscription?.plan || SubscriptionPlan.Free); + setActiveSubscription(getProAccessPlanFromSubscriptions(subscriptions)); } catch (e) { console.error(e); } diff --git a/src/components/app/share/SharePanel.tsx b/src/components/app/share/SharePanel.tsx index 683d3675..a7f06a92 100644 --- a/src/components/app/share/SharePanel.tsx +++ b/src/components/app/share/SharePanel.tsx @@ -10,7 +10,7 @@ import { InviteGuest } from '@/components/app/share/InviteGuest'; import { PeopleWithAccess } from '@/components/app/share/PeopleWithAccess'; import { UpgradeBanner } from '@/components/app/share/UpgradeBanner'; import { useCurrentUser, useService } from '@/components/main/app.hooks'; -import { isAppFlowyHosted } from '@/utils/subscription'; +import { getProAccessPlanFromSubscriptions, isAppFlowyHosted } from '@/utils/subscription'; function SharePanel({ viewId }: { viewId: string }) { const currentUser = useCurrentUser(); @@ -113,9 +113,7 @@ function SharePanel({ viewId }: { viewId: string }) { return; } - const subscription = subscriptions[0]; - - setActiveSubscriptionPaln(subscription?.plan || SubscriptionPlan.Free); + setActiveSubscriptionPaln(getProAccessPlanFromSubscriptions(subscriptions)); } catch (e) { setActiveSubscriptionPaln(SubscriptionPlan.Free); console.error(e); diff --git a/src/components/app/workspaces/InviteMember.tsx b/src/components/app/workspaces/InviteMember.tsx index 9658d2ee..f34eb03f 100644 --- a/src/components/app/workspaces/InviteMember.tsx +++ b/src/components/app/workspaces/InviteMember.tsx @@ -12,7 +12,7 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from ' import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Progress } from '@/components/ui/progress'; -import { isAppFlowyHosted } from '@/utils/subscription'; +import { getProAccessPlanFromSubscriptions, isAppFlowyHosted } from '@/utils/subscription'; function InviteMember({ workspace, @@ -63,9 +63,7 @@ function InviteMember({ return; } - const subscription = subscriptions[0]; - - setActiveSubscriptionPaln(subscription?.plan || SubscriptionPlan.Free); + setActiveSubscriptionPaln(getProAccessPlanFromSubscriptions(subscriptions)); } catch (e) { setActiveSubscriptionPaln(SubscriptionPlan.Free); console.error(e); diff --git a/src/components/billing/UpgradePlan.tsx b/src/components/billing/UpgradePlan.tsx index 28abebd4..7740d8fd 100644 --- a/src/components/billing/UpgradePlan.tsx +++ b/src/components/billing/UpgradePlan.tsx @@ -51,9 +51,24 @@ function UpgradePlan({ open, onClose, onOpen }: { open: boolean; onClose: () => return; } - const subscription = subscriptions[0]; + const proSubscription = subscriptions.find( + (item) => item.plan === SubscriptionPlan.Pro || item.plan === SubscriptionPlan.Team + ); - setActiveSubscription(subscription); + if (proSubscription) { + setActiveSubscription({ + ...proSubscription, + plan: SubscriptionPlan.Pro, + }); + return; + } + + setActiveSubscription({ + plan: SubscriptionPlan.Free, + currency: '', + recurring_interval: SubscriptionInterval.Month, + price_cents: 0, + }); } catch (e) { console.error(e); } diff --git a/src/components/editor/components/toolbar/block-controls/Color.tsx b/src/components/editor/components/toolbar/block-controls/Color.tsx index 19a2d2cb..1a11b922 100644 --- a/src/components/editor/components/toolbar/block-controls/Color.tsx +++ b/src/components/editor/components/toolbar/block-controls/Color.tsx @@ -13,7 +13,7 @@ import { useEditorContext } from '@/components/editor/EditorContext'; import { Button } from '@/components/ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { ColorEnum, renderColor } from '@/utils/color'; -import { isAppFlowyHosted } from '@/utils/subscription'; +import { getProAccessPlanFromSubscriptions, isAppFlowyHosted } from '@/utils/subscription'; const origins: Origins = { anchorOrigin: { @@ -50,9 +50,7 @@ function Color({ node, onSelectColor }: { node: BlockNode; onSelectColor: () => return; } - const subscription = subscriptions[0]; - - setActiveSubscriptionPlan(subscription?.plan || SubscriptionPlan.Free); + setActiveSubscriptionPlan(getProAccessPlanFromSubscriptions(subscriptions)); } catch (e) { setActiveSubscriptionPlan(SubscriptionPlan.Free); console.error(e); diff --git a/src/utils/subscription.ts b/src/utils/subscription.ts index 5feab970..44e1805b 100644 --- a/src/utils/subscription.ts +++ b/src/utils/subscription.ts @@ -4,9 +4,11 @@ * Include localhost:8000 to cover the default dev backend when APPFLOWY_BASE_URL isn't updated. * Self-hosted instances are not official hosts. */ +import { Subscription, SubscriptionPlan } from '@/application/types'; import { getConfigValue } from '@/utils/runtime-config'; const OFFICIAL_HOSTNAMES = new Set(['beta.appflowy.cloud', 'test.appflowy.cloud', 'localhost']); +const PRO_ACCESS_PLANS = new Set([SubscriptionPlan.Pro, SubscriptionPlan.Team]); function getBaseUrlHostname(): string | null { const baseUrl = getConfigValue('APPFLOWY_BASE_URL', '').trim(); @@ -60,3 +62,15 @@ export function isAppFlowyHosted(): boolean { return isOfficialHostname(resolveHostname()); } + +export function hasProAccessFromPlans(plans?: SubscriptionPlan[] | null): boolean { + if (!plans || plans.length === 0) return false; + return plans.some((plan) => PRO_ACCESS_PLANS.has(plan)); +} + +export function getProAccessPlanFromSubscriptions(subscriptions?: Subscription[] | null): SubscriptionPlan { + if (!subscriptions || subscriptions.length === 0) return SubscriptionPlan.Free; + return subscriptions.some((subscription) => PRO_ACCESS_PLANS.has(subscription.plan)) + ? SubscriptionPlan.Pro + : SubscriptionPlan.Free; +}