Files
digital-pilates/contexts/MembershipModalContext.tsx
richarjiang 2e11f694f8 feat(membership): 实现会员系统和购买流程
- 创建 MembershipModalContext 统一管理会员弹窗
- 优化 MembershipModal 产品套餐展示和购买流程
- 集成 RevenueCat SDK 并初始化内购功能
- 在个人中心添加会员 Banner,引导非会员用户订阅
- 修复日志工具的循环引用问题,确保错误信息正确记录
- 版本更新至 1.0.20

新增了完整的会员购买流程,包括套餐选择、购买确认、购买恢复等功能。会员 Banner 仅对非会员用户展示,已是会员的用户不会看到。同时优化了错误日志记录,避免循环引用导致的序列化失败。
2025-10-24 09:16:04 +08:00

89 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import Purchases from 'react-native-purchases';
import { MembershipModal } from '@/components/model/MembershipModal';
import { logger } from '@/utils/logger';
type MembershipModalOptions = {
onPurchaseSuccess?: () => void;
};
interface MembershipModalContextValue {
openMembershipModal: (options?: MembershipModalOptions) => void;
closeMembershipModal: () => void;
}
const MembershipModalContext = createContext<MembershipModalContextValue | null>(null);
export function MembershipModalProvider({ children }: { children: React.ReactNode }) {
const [visible, setVisible] = useState(false);
const [pendingSuccessCallback, setPendingSuccessCallback] = useState<(() => void) | undefined>();
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
// 直接使用生产环境的 API Key避免环境变量问题
const iosApiKey = 'appl_lmVvuLWFlXlrEsnvxMzTnKapqcc';
const initializeRevenueCat = async () => {
try {
// 检查是否已经配置过,避免重复配置
if (!isInitialized) {
await Purchases.configure({ apiKey: iosApiKey });
setIsInitialized(true);
console.log('[MembershipModalProvider] RevenueCat SDK 初始化成功');
}
} catch (error) {
console.error('[MembershipModalProvider] RevenueCat SDK 初始化失败:', error);
// 初始化失败时不阻止应用正常运行
}
};
initializeRevenueCat();
}, [isInitialized]);
const openMembershipModal = useCallback((options?: MembershipModalOptions) => {
setPendingSuccessCallback(() => options?.onPurchaseSuccess);
setVisible(true);
}, []);
const closeMembershipModal = useCallback(() => {
setVisible(false);
setPendingSuccessCallback(undefined);
}, []);
const handlePurchaseSuccess = useCallback(() => {
pendingSuccessCallback?.();
}, [pendingSuccessCallback]);
const contextValue = useMemo(
() => ({
openMembershipModal,
closeMembershipModal,
}),
[closeMembershipModal, openMembershipModal],
);
return (
<MembershipModalContext.Provider value={contextValue}>
{children}
<MembershipModal
visible={visible}
onClose={closeMembershipModal}
onPurchaseSuccess={handlePurchaseSuccess}
/>
</MembershipModalContext.Provider>
);
}
export function useMembershipModal(): MembershipModalContextValue {
const context = useContext(MembershipModalContext);
if (!context) {
logger.error('useMembershipModal must be used within a MembershipModalProvider');
// 抛出错误而不是返回 undefined确保类型安全
throw new Error('useMembershipModal must be used within a MembershipModalProvider');
}
return context;
}