diff --git a/apps/admin/src/app/api/membership/features/route.ts b/apps/admin/src/app/api/membership/features/route.ts new file mode 100644 index 0000000..a577cd3 --- /dev/null +++ b/apps/admin/src/app/api/membership/features/route.ts @@ -0,0 +1,32 @@ +import { NextResponse } from "next/server"; +import { auth } from "@clerk/nextjs/server"; +import { MEMBERSHIP_FEATURES } from "@/lib/membership/features"; +import { getUserMembershipContext } from "@/lib/membership/access"; + +export async function GET() { + try { + const { userId } = await auth(); + if (!userId) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { membershipType, features } = await getUserMembershipContext(userId); + + return NextResponse.json({ + success: true, + data: { + membershipType, + currentFeatures: features, + plans: MEMBERSHIP_FEATURES, + }, + meta: { + timestamp: new Date().toISOString(), + }, + }); + } catch { + return NextResponse.json( + { error: "Failed to load membership features" }, + { status: 500 }, + ); + } +} diff --git a/apps/mobile/src/api/membership.ts b/apps/mobile/src/api/membership.ts index 802ff14..e16d7f2 100644 --- a/apps/mobile/src/api/membership.ts +++ b/apps/mobile/src/api/membership.ts @@ -51,6 +51,15 @@ interface UsersListResponse { }>; } +interface MembershipFeaturesResponse { + success: boolean; + data: { + membershipType: MembershipType; + currentFeatures: MembershipFeatures; + plans: Record; + }; +} + function isMembershipType(value: unknown): value is MembershipType { return value === "basic" || value === "premium" || value === "vip"; } @@ -85,3 +94,35 @@ export function getMembershipFeatures( ): MembershipFeatures { return MEMBERSHIP_FEATURES[membershipType]; } + +export async function getCurrentMembershipFeaturesFromServer( + token: string | null, +): Promise<{ + membershipType: MembershipType; + features: MembershipFeatures; +}> { + if (!token) { + return { + membershipType: "basic", + features: MEMBERSHIP_FEATURES.basic, + }; + } + + const response = await apiClient.get( + API_ENDPOINTS.MEMBERSHIP.FEATURES, + withAuth(token), + ); + + const data = response.data?.data; + if (!data) { + return { + membershipType: "basic", + features: MEMBERSHIP_FEATURES.basic, + }; + } + + return { + membershipType: data.membershipType, + features: data.currentFeatures, + }; +} diff --git a/apps/mobile/src/config/api.ts b/apps/mobile/src/config/api.ts index c698573..cbce81d 100644 --- a/apps/mobile/src/config/api.ts +++ b/apps/mobile/src/config/api.ts @@ -44,6 +44,9 @@ export const API_ENDPOINTS = { HISTORY: "/api/attendance/history", }, RECOMMENDATIONS: "/api/recommendations", + MEMBERSHIP: { + FEATURES: "/api/membership/features", + }, NUTRITION: { BASE: "/api/nutrition", MEALS: "/api/nutrition/meals", diff --git a/apps/mobile/src/hooks/useMembership.ts b/apps/mobile/src/hooks/useMembership.ts index 35fb342..47ff848 100644 --- a/apps/mobile/src/hooks/useMembership.ts +++ b/apps/mobile/src/hooks/useMembership.ts @@ -1,14 +1,18 @@ import { useAuth, useUser } from "@clerk/clerk-expo"; import { useEffect, useState } from "react"; import { - getCurrentMembershipType, - getMembershipFeatures, + getCurrentMembershipFeaturesFromServer, type MembershipFeatures, type MembershipType, } from "../api/membership"; import log from "../utils/logger"; -const BASIC_FEATURES = getMembershipFeatures("basic"); +const BASIC_FEATURES: MembershipFeatures = { + recommendationsPerMonth: 1, + hydrationTracking: false, + nutritionTracking: false, + advancedStatistics: false, +}; interface UseMembershipResult { membershipType: MembershipType; @@ -20,6 +24,7 @@ export function useMembership(): UseMembershipResult { const { user } = useUser(); const { getToken, isSignedIn } = useAuth(); const [membershipType, setMembershipType] = useState("basic"); + const [features, setFeatures] = useState(BASIC_FEATURES); const [loading, setLoading] = useState(true); useEffect(() => { @@ -29,6 +34,7 @@ export function useMembership(): UseMembershipResult { if (!isSignedIn || !user?.id) { if (isMounted) { setMembershipType("basic"); + setFeatures(BASIC_FEATURES); setLoading(false); } return; @@ -37,14 +43,16 @@ export function useMembership(): UseMembershipResult { try { setLoading(true); const token = await getToken(); - const type = await getCurrentMembershipType(user.id, token); + const result = await getCurrentMembershipFeaturesFromServer(token); if (isMounted) { - setMembershipType(type); + setMembershipType(result.membershipType); + setFeatures(result.features); } } catch (error) { log.error("Failed to load membership", error, { userId: user.id }); if (isMounted) { setMembershipType("basic"); + setFeatures(BASIC_FEATURES); } } finally { if (isMounted) { @@ -62,7 +70,7 @@ export function useMembership(): UseMembershipResult { return { membershipType, - features: getMembershipFeatures(membershipType) || BASIC_FEATURES, + features, loading, }; }