Implement new billing model

This commit is contained in:
Arthur Belleville 2026-03-08 21:11:42 +01:00
parent d0820ebbf1
commit 03e426dd23
No known key found for this signature in database
4 changed files with 49 additions and 15 deletions

View file

@ -291,8 +291,12 @@ export const createInvitedUser = async (
streamServerClient: StreamChat,
transporter: Transporter,
recipientEmail: string,
senderEmail: string
senderEmail: string,
options?: {
isTemporary?: boolean;
}
): Promise<{ success: boolean; error?: string; userId?: string }> => {
const isTemporary = options?.isTemporary ?? true;
const xtabloUrl = process.env.XTABLO_URL || "https://app.xtablo.com";
// Create a new user account for the invited email
@ -318,6 +322,16 @@ export const createInvitedUser = async (
return { success: false, error: createUserError.message };
}
const { error: updateProfileError } = await supabase
.from("profiles")
.update({ is_temporary: isTemporary })
.eq("id", newUser.user.id);
if (updateProfileError) {
console.error("Error setting invited user temporary status:", updateProfileError);
return { success: false, error: updateProfileError.message };
}
await streamServerClient.upsertUser({
id: newUser.user.id,
name: recipientEmail.split("@")[0],

View file

@ -58,7 +58,8 @@ const bookSlot = factory.createHandlers(async (c) => {
streamServerClient,
transporter,
data.user_details.email,
ownerData.email
ownerData.email,
{ isTemporary: true }
);
if (!result.success) {

View file

@ -33,7 +33,11 @@ const upsertStreamUserFromProfile = async (
streamServerClient: AuthEnv["Variables"]["streamServerClient"],
userId: string
) => {
const { data: profile } = await supabase.from("profiles").select("name").eq("id", userId).maybeSingle();
const { data: profile } = await supabase
.from("profiles")
.select("name")
.eq("id", userId)
.maybeSingle();
await streamServerClient.upsertUser({
id: userId,
@ -255,10 +259,7 @@ const deleteTablo = factory.createHandlers(async (c) => {
}
}
const { error } = await supabase
.from("tablos")
.update({ deleted_at: deletedAt })
.eq("id", id);
const { error } = await supabase.from("tablos").update({ deleted_at: deletedAt }).eq("id", id);
if (error) {
return c.json({ error: error.message }, 500);
@ -352,7 +353,8 @@ const inviteToTablo = (
streamServerClient,
transporter,
recipientEmail,
sender.email
sender.email,
{ isTemporary: true }
);
if (!result.success) {
@ -427,9 +429,7 @@ ${introEmail ? `<p>${introEmail}</p>` : ""}
});
});
const cancelPendingInvite = (
middlewareManager: ReturnType<typeof MiddlewareManager.getInstance>
) =>
const cancelPendingInvite = (middlewareManager: ReturnType<typeof MiddlewareManager.getInstance>) =>
factory.createHandlers(middlewareManager.regularUserCheck, async (c) => {
const user = c.get("user");
const supabase = c.get("supabase");

View file

@ -35,7 +35,10 @@ const getMe = factory.createHandlers(async (c) => {
const { data, error } = await supabase.from("profiles").select("*").eq("id", user.id).single();
const userData = data as Tables<"profiles">;
const userData = data as Tables<"profiles"> & {
organization_id: number | null;
plan: string | null;
};
if (!userData) {
return c.json({ error: "User not found" }, 404);
@ -45,11 +48,21 @@ const getMe = factory.createHandlers(async (c) => {
return c.json({ error: error.message }, 500);
}
let effectivePlan: string | null = userData.plan;
if (userData.organization_id) {
const { plan: organizationPlan } = await getOrganizationPlan(
supabase,
userData.organization_id
);
effectivePlan = organizationPlan;
}
const user_id = data.id;
const token = streamServerClient.createToken(user_id);
return c.json({
...userData,
plan: effectivePlan,
streamToken: token,
});
});
@ -313,6 +326,11 @@ const getOrganization = factory.createHandlers(async (c) => {
return c.json({ error: "Failed to resolve organization plan" }, 500);
}
const membersWithEffectivePlan = (members || []).map((member) => ({
...member,
plan,
}));
const { data: billingState, error: billingError } = await getOrganizationBillingState(
supabase,
organizationId
@ -329,7 +347,7 @@ const getOrganization = factory.createHandlers(async (c) => {
member_count: members?.length || 0,
tablo_count: tabloCount || 0,
},
members: members || [],
members: membersWithEffectivePlan,
trial_starts_at: billingState.trial_starts_at,
trial_ends_at: billingState.trial_ends_at,
is_trial_expired: billingState.is_trial_expired,
@ -469,7 +487,8 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
streamServerClient,
transporter,
recipientEmail,
senderProfile.email
senderProfile.email,
{ isTemporary: false }
);
if (!invitedUser.success || !invitedUser.userId) {
@ -490,7 +509,7 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
const { error: assignOrganizationError } = await supabase
.from("profiles")
.update({ organization_id: organizationId })
.update({ organization_id: organizationId, is_temporary: false })
.eq("id", invitedUser.userId);
if (assignOrganizationError) {