refactor: plan logic (#217)

This commit is contained in:
Nathan.fooo
2026-01-20 17:25:08 +08:00
committed by GitHub
parent a3709199c9
commit 39fcfe030d
9 changed files with 45 additions and 26 deletions

View File

@@ -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({

View File

@@ -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,
};
}

View File

@@ -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<GetRequestAccessInfoResponse | null>(null);
const [currentPlans, setCurrentPlans] = useState<SubscriptionPlan[]>([]);
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();

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}