feat(frontend): read user profile + plan from V2 Identity instead of Supabase
getUserProfile now calls the gateway /v1/users/me and /v1/users/me/plan with the access-token cookie, mapping plan_code → PlanId. Falls back to a free-plan profile when signed out or Identity is unreachable. Stripe ids drop to null (V2 billing runs through the payments service). Signature unchanged so the dashboard plan badge + settings call sites are untouched. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+69
-33
@@ -1,5 +1,6 @@
|
||||
import { gatewayUrl } from "@/lib/api/gateway";
|
||||
import { getAccessToken } from "@/lib/auth/session";
|
||||
import type { PlanId } from "@/lib/plans";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
|
||||
export interface UserProfile {
|
||||
id: string;
|
||||
@@ -10,45 +11,80 @@ export interface UserProfile {
|
||||
stripe_subscription_id: string | null;
|
||||
}
|
||||
|
||||
export async function getUserProfile(userId: string): Promise<UserProfile> {
|
||||
const supabase = await createClient();
|
||||
// ── V2 identity response shapes (snake_case JSON) ────────────────────────────
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("profiles")
|
||||
.select("id, email, plan, billing_period, stripe_customer_id, stripe_subscription_id")
|
||||
.eq("id", userId)
|
||||
.maybeSingle();
|
||||
interface V2User {
|
||||
id: string;
|
||||
email?: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
interface V2UserPlan {
|
||||
id: string;
|
||||
plan_id: string;
|
||||
plan_code: string;
|
||||
plan_name: string;
|
||||
initial_seconds_charge: number;
|
||||
remain_charge_sec: number;
|
||||
monthly_renders_used: number;
|
||||
starts_at: string;
|
||||
expires_at: string;
|
||||
cancelled_at?: string | null;
|
||||
auto_renew: boolean;
|
||||
billing_period?: string | null;
|
||||
}
|
||||
|
||||
function fallbackProfile(userId: string, email: string | null = null): UserProfile {
|
||||
return {
|
||||
id: userId,
|
||||
email: null,
|
||||
email,
|
||||
plan: "free",
|
||||
billing_period: null,
|
||||
// V2 billing runs through the payments service (ZarinPal/Stripe); Stripe ids
|
||||
// are no longer surfaced on the profile. Kept null for shape compatibility.
|
||||
stripe_customer_id: null,
|
||||
stripe_subscription_id: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
id: userId,
|
||||
email: null,
|
||||
plan: "free",
|
||||
billing_period: null,
|
||||
stripe_customer_id: null,
|
||||
stripe_subscription_id: null,
|
||||
};
|
||||
}
|
||||
|
||||
const plan = data.plan as PlanId;
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
email: data.email,
|
||||
plan: plan === "pro" || plan === "business" ? plan : "free",
|
||||
billing_period: data.billing_period,
|
||||
stripe_customer_id: data.stripe_customer_id,
|
||||
stripe_subscription_id: data.stripe_subscription_id,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePlan(code: string | null | undefined): PlanId {
|
||||
const c = (code ?? "").toLowerCase();
|
||||
return c === "pro" || c === "business" ? c : "free";
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the current user's profile + plan from the Identity service via the
|
||||
* gateway, authenticated with the access-token cookie. The `userId` argument is
|
||||
* retained for call-site compatibility but the gateway derives identity from the
|
||||
* JWT. Degrades to a free-plan fallback when signed out or the service is down.
|
||||
*/
|
||||
export async function getUserProfile(userId: string): Promise<UserProfile> {
|
||||
const token = await getAccessToken();
|
||||
if (!token) return fallbackProfile(userId);
|
||||
|
||||
const authHeaders = {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
|
||||
try {
|
||||
const [userRes, planRes] = await Promise.all([
|
||||
fetch(gatewayUrl("/v1/users/me"), { cache: "no-store", headers: authHeaders }),
|
||||
fetch(gatewayUrl("/v1/users/me/plan"), { cache: "no-store", headers: authHeaders }),
|
||||
]);
|
||||
|
||||
const user = userRes.ok ? ((await userRes.json().catch(() => null)) as V2User | null) : null;
|
||||
const plan = planRes.ok ? ((await planRes.json().catch(() => null)) as V2UserPlan | null) : null;
|
||||
|
||||
return {
|
||||
id: user?.id ?? userId,
|
||||
email: user?.email ?? null,
|
||||
plan: normalizePlan(plan?.plan_code),
|
||||
billing_period: plan?.billing_period ?? null,
|
||||
stripe_customer_id: null,
|
||||
stripe_subscription_id: null,
|
||||
};
|
||||
} catch {
|
||||
return fallbackProfile(userId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user