- 创建独立的会员服务模块 services/membership.ts,统一管理会员计划元数据和工具函数 - 新增 membershipSlice Redux状态管理,集中处理会员数据和状态 - 重构个人中心VIP会员卡片,支持动态显示会员计划和有效期 - 优化会员购买弹窗,使用统一的会员计划配置 - 改进会员数据获取流程,确保状态同步和一致性
139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
|
import Purchases from 'react-native-purchases';
|
|
|
|
import {
|
|
extractMembershipProductsFromOfferings,
|
|
getPlanMetaById,
|
|
hasActiveMembership,
|
|
pickActiveProductId,
|
|
resolvePlanDisplayName,
|
|
summarizeProducts,
|
|
type MembershipPlanSummary,
|
|
} from '@/services/membership';
|
|
import type { RootState } from './index';
|
|
import { logout, updateProfile } from './userSlice';
|
|
|
|
export interface MembershipState {
|
|
plans: MembershipPlanSummary[];
|
|
activePlanId: string | null;
|
|
activePlanName: string | null;
|
|
hasActiveMembership: boolean;
|
|
loading: boolean;
|
|
error: string | null;
|
|
lastUpdated: number | null;
|
|
activeProductIds: string[];
|
|
}
|
|
|
|
const initialState: MembershipState = {
|
|
plans: [],
|
|
activePlanId: null,
|
|
activePlanName: null,
|
|
hasActiveMembership: false,
|
|
loading: false,
|
|
error: null,
|
|
lastUpdated: null,
|
|
activeProductIds: [],
|
|
};
|
|
|
|
export const fetchMembershipData = createAsyncThunk<
|
|
{
|
|
plans: MembershipPlanSummary[];
|
|
activePlanId: string | null;
|
|
activePlanName: string | null;
|
|
hasActiveMembership: boolean;
|
|
activeProductIds: string[];
|
|
lastUpdated: number;
|
|
},
|
|
void,
|
|
{ rejectValue: string }
|
|
>('membership/fetchMembershipData', async (_, { rejectWithValue, dispatch }) => {
|
|
try {
|
|
const offerings = await Purchases.getOfferings();
|
|
const products = extractMembershipProductsFromOfferings(offerings);
|
|
const plans = summarizeProducts(products);
|
|
|
|
const customerInfo = await Purchases.getCustomerInfo();
|
|
const { activeProductId, activeProductIds } = pickActiveProductId(
|
|
customerInfo,
|
|
products,
|
|
);
|
|
|
|
const activePlanMeta = activeProductId
|
|
? getPlanMetaById(activeProductId)
|
|
: undefined;
|
|
const activeProduct = activeProductId
|
|
? products.find((product) => product.identifier === activeProductId) ?? null
|
|
: null;
|
|
|
|
const hasActive = hasActiveMembership(customerInfo);
|
|
const activePlanName = hasActive
|
|
? resolvePlanDisplayName(activeProduct, activePlanMeta)
|
|
: null;
|
|
|
|
dispatch(
|
|
updateProfile({
|
|
vipPlanName: activePlanName ?? undefined,
|
|
isVip: hasActive,
|
|
}),
|
|
);
|
|
|
|
return {
|
|
plans,
|
|
activePlanId: activeProductId,
|
|
activePlanName,
|
|
hasActiveMembership: hasActive,
|
|
activeProductIds,
|
|
lastUpdated: Date.now(),
|
|
};
|
|
} catch (error: any) {
|
|
const message =
|
|
error?.message ??
|
|
(typeof error === 'string' ? error : '获取会员信息失败');
|
|
return rejectWithValue(message);
|
|
}
|
|
});
|
|
|
|
const membershipSlice = createSlice({
|
|
name: 'membership',
|
|
initialState,
|
|
reducers: {},
|
|
extraReducers: (builder) => {
|
|
builder
|
|
.addCase(fetchMembershipData.pending, (state) => {
|
|
state.loading = true;
|
|
state.error = null;
|
|
})
|
|
.addCase(fetchMembershipData.fulfilled, (state, action) => {
|
|
state.loading = false;
|
|
state.error = null;
|
|
state.plans = action.payload.plans;
|
|
state.activePlanId = action.payload.activePlanId;
|
|
state.activePlanName = action.payload.activePlanName;
|
|
state.hasActiveMembership = action.payload.hasActiveMembership;
|
|
state.activeProductIds = action.payload.activeProductIds;
|
|
state.lastUpdated = action.payload.lastUpdated;
|
|
})
|
|
.addCase(fetchMembershipData.rejected, (state, action) => {
|
|
state.loading = false;
|
|
state.error =
|
|
(action.payload as string) ?? '获取会员信息失败,请稍后重试';
|
|
})
|
|
.addCase(logout.fulfilled, () => ({
|
|
...initialState,
|
|
plans: [],
|
|
activeProductIds: [],
|
|
}));
|
|
},
|
|
});
|
|
|
|
export const selectMembershipState = (state: RootState): MembershipState =>
|
|
state.membership;
|
|
|
|
export const selectMembershipPlans = (state: RootState) =>
|
|
state.membership.plans;
|
|
|
|
export const selectActiveMembershipPlanName = (state: RootState) =>
|
|
state.membership.activePlanName;
|
|
|
|
export default membershipSlice.reducer;
|