Compare commits
2 Commits
091cb5ba85
...
7ada05da6a
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ada05da6a | |||
| 50ece15089 |
Binary file not shown.
32
apps/admin/src/app/api/membership/features/route.ts
Normal file
32
apps/admin/src/app/api/membership/features/route.ts
Normal file
@ -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 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -51,6 +51,15 @@ interface UsersListResponse {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MembershipFeaturesResponse {
|
||||||
|
success: boolean;
|
||||||
|
data: {
|
||||||
|
membershipType: MembershipType;
|
||||||
|
currentFeatures: MembershipFeatures;
|
||||||
|
plans: Record<MembershipType, MembershipFeatures>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function isMembershipType(value: unknown): value is MembershipType {
|
function isMembershipType(value: unknown): value is MembershipType {
|
||||||
return value === "basic" || value === "premium" || value === "vip";
|
return value === "basic" || value === "premium" || value === "vip";
|
||||||
}
|
}
|
||||||
@ -85,3 +94,35 @@ export function getMembershipFeatures(
|
|||||||
): MembershipFeatures {
|
): MembershipFeatures {
|
||||||
return MEMBERSHIP_FEATURES[membershipType];
|
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<MembershipFeaturesResponse>(
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -44,6 +44,9 @@ export const API_ENDPOINTS = {
|
|||||||
HISTORY: "/api/attendance/history",
|
HISTORY: "/api/attendance/history",
|
||||||
},
|
},
|
||||||
RECOMMENDATIONS: "/api/recommendations",
|
RECOMMENDATIONS: "/api/recommendations",
|
||||||
|
MEMBERSHIP: {
|
||||||
|
FEATURES: "/api/membership/features",
|
||||||
|
},
|
||||||
NUTRITION: {
|
NUTRITION: {
|
||||||
BASE: "/api/nutrition",
|
BASE: "/api/nutrition",
|
||||||
MEALS: "/api/nutrition/meals",
|
MEALS: "/api/nutrition/meals",
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
import { useAuth, useUser } from "@clerk/clerk-expo";
|
import { useAuth, useUser } from "@clerk/clerk-expo";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
getCurrentMembershipType,
|
getCurrentMembershipFeaturesFromServer,
|
||||||
getMembershipFeatures,
|
|
||||||
type MembershipFeatures,
|
type MembershipFeatures,
|
||||||
type MembershipType,
|
type MembershipType,
|
||||||
} from "../api/membership";
|
} from "../api/membership";
|
||||||
import log from "../utils/logger";
|
import log from "../utils/logger";
|
||||||
|
|
||||||
const BASIC_FEATURES = getMembershipFeatures("basic");
|
const BASIC_FEATURES: MembershipFeatures = {
|
||||||
|
recommendationsPerMonth: 1,
|
||||||
|
hydrationTracking: false,
|
||||||
|
nutritionTracking: false,
|
||||||
|
advancedStatistics: false,
|
||||||
|
};
|
||||||
|
|
||||||
interface UseMembershipResult {
|
interface UseMembershipResult {
|
||||||
membershipType: MembershipType;
|
membershipType: MembershipType;
|
||||||
@ -20,6 +24,7 @@ export function useMembership(): UseMembershipResult {
|
|||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { getToken, isSignedIn } = useAuth();
|
const { getToken, isSignedIn } = useAuth();
|
||||||
const [membershipType, setMembershipType] = useState<MembershipType>("basic");
|
const [membershipType, setMembershipType] = useState<MembershipType>("basic");
|
||||||
|
const [features, setFeatures] = useState<MembershipFeatures>(BASIC_FEATURES);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -29,6 +34,7 @@ export function useMembership(): UseMembershipResult {
|
|||||||
if (!isSignedIn || !user?.id) {
|
if (!isSignedIn || !user?.id) {
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setMembershipType("basic");
|
setMembershipType("basic");
|
||||||
|
setFeatures(BASIC_FEATURES);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -37,14 +43,16 @@ export function useMembership(): UseMembershipResult {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const token = await getToken();
|
const token = await getToken();
|
||||||
const type = await getCurrentMembershipType(user.id, token);
|
const result = await getCurrentMembershipFeaturesFromServer(token);
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setMembershipType(type);
|
setMembershipType(result.membershipType);
|
||||||
|
setFeatures(result.features);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to load membership", error, { userId: user.id });
|
log.error("Failed to load membership", error, { userId: user.id });
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setMembershipType("basic");
|
setMembershipType("basic");
|
||||||
|
setFeatures(BASIC_FEATURES);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
@ -62,7 +70,7 @@ export function useMembership(): UseMembershipResult {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
membershipType,
|
membershipType,
|
||||||
features: getMembershipFeatures(membershipType) || BASIC_FEATURES,
|
features,
|
||||||
loading,
|
loading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user