refactor: remove is_temporary flag across the entire codebase

Drop the is_temporary boolean from the DB schema (new migration), types,
API routers/helpers/middleware, and all frontend components and tests.
Access control now relies solely on is_client.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arthur Belleville 2026-04-30 17:04:11 +02:00
parent a60cd7739a
commit c56d5718b8
No known key found for this signature in database
30 changed files with 33 additions and 297 deletions

View file

@ -11,7 +11,6 @@ export interface TestUser {
first_name: string;
last_name: string;
name: string;
is_temporary: boolean;
}
export const TEST_USERS: Record<string, TestUser> = {
@ -21,7 +20,6 @@ export const TEST_USERS: Record<string, TestUser> = {
first_name: "Test",
last_name: "Owner",
name: "Test Owner",
is_temporary: false,
},
temp: {
email: "test_temp@example.com",
@ -29,7 +27,6 @@ export const TEST_USERS: Record<string, TestUser> = {
first_name: "Test",
last_name: "Temporary",
name: "Test Temporary",
is_temporary: true,
},
};

View file

@ -27,14 +27,12 @@ describe("billing helpers", () => {
{
id: "owner-user",
created_at: "2026-01-01T10:00:00.000Z",
is_temporary: false,
is_client: false,
plan: "annual",
},
{
id: "late-user",
created_at: "2026-01-02T10:00:00.000Z",
is_temporary: false,
is_client: false,
plan: "solo",
},
@ -43,26 +41,23 @@ describe("billing helpers", () => {
expect(owner?.id).toBe("owner-user");
});
it("excludes temporary users from billable seat count", () => {
it("excludes client users from billable seat count", () => {
const count = getBillableMemberCount([
{
id: "user-1",
created_at: "2026-01-01T10:00:00.000Z",
is_temporary: false,
is_client: false,
plan: "solo",
},
{
id: "temp-1",
id: "client-1",
created_at: "2026-01-02T10:00:00.000Z",
is_temporary: true,
is_client: false,
is_client: true,
plan: "solo",
},
{
id: "user-2",
created_at: "2026-01-03T10:00:00.000Z",
is_temporary: null,
is_client: false,
plan: "team",
},

View file

@ -129,19 +129,6 @@ export async function setupTestDatabase(): Promise<TestDatabaseData> {
accessToken: signInData.session.access_token,
};
// Update profile with is_temporary flag if needed
if (userData.is_temporary) {
const { error: profileError } = await adminClient
.from("profiles")
.update({ is_temporary: true })
.eq("id", authData.user.id);
if (profileError) {
console.warn(
`Warning: Failed to update profile for ${userData.email}: ${profileError.message}`
);
}
}
}
const tablosToInsert = TEST_TABLOS.map((tablo) => ({

View file

@ -12,7 +12,7 @@ describe("Middleware Tests", () => {
const middlewareManager = MiddlewareManager.getInstance();
const createProfilesSupabaseMock = (result: {
data: { is_temporary?: boolean; is_client?: boolean } | null;
data: { is_client?: boolean } | null;
error: { message: string } | null;
}) => ({
from: vi.fn().mockReturnValue({
@ -314,33 +314,6 @@ describe("Middleware Tests", () => {
expect(res.status).toBe(401);
});
it("should reject temporary users", async () => {
const app = new Hono();
app.use(async (c, next) => {
// biome-ignore lint/suspicious/noExplicitAny: Test-only context injection
(c as any).set(
"supabase",
createProfilesSupabaseMock({
data: { is_temporary: true },
error: null,
})
);
// biome-ignore lint/suspicious/noExplicitAny: Test-only context injection
(c as any).set("user", { id: "temp-user" });
await next();
});
app.use(middlewareManager.regularUserCheck);
app.get("/test", (c) => c.json({ success: true }));
// biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
const client = testClient(app) as any;
const res = await client.test.$get();
const data = await res.json();
expect(res.status).toBe(401);
expect(data.error).toBe("User is read only");
});
it("should return 401 for client users", async () => {
const app = new Hono();
app.use(async (c, next) => {
@ -348,7 +321,7 @@ describe("Middleware Tests", () => {
(c as any).set(
"supabase",
createProfilesSupabaseMock({
data: { is_temporary: false, is_client: true },
data: { is_client: true },
error: null,
})
);
@ -369,34 +342,6 @@ describe("Middleware Tests", () => {
});
});
describe("Billing Checkout Access Middleware", () => {
it("should allow temporary users to continue to billing checkout", async () => {
const app = new Hono();
app.use(async (c, next) => {
// biome-ignore lint/suspicious/noExplicitAny: Test-only context injection
(c as any).set(
"supabase",
createProfilesSupabaseMock({
data: { is_temporary: true },
error: null,
})
);
// biome-ignore lint/suspicious/noExplicitAny: Test-only context injection
(c as any).set("user", { id: "temp-user" });
await next();
});
app.use(middlewareManager.billingCheckoutAccess);
app.get("/test", (c) => c.json({ success: true }));
// biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
const client = testClient(app) as any;
const res = await client.test.$get();
const data = await res.json();
expect(res.status).toBe(200);
expect(data.success).toBe(true);
});
});
describe("Active Plan Access Middleware", () => {
it("should reject requests when the organization has no active plan", async () => {

View file

@ -6,7 +6,6 @@ export type RequiredBillingPlan = "solo" | "team";
type BillingProfileRow = {
id: string;
created_at: string | null;
is_temporary: boolean | null;
is_client: boolean | null;
plan: string | null;
};
@ -88,7 +87,7 @@ export const parseTrialRolloutDate = (
export const getOrganizationOwner = (profiles: BillingProfileRow[]) => profiles[0] ?? null;
export const getBillableMemberCount = (profiles: BillingProfileRow[]) =>
profiles.filter((profile) => profile.is_temporary !== true && profile.is_client !== true).length;
profiles.filter((profile) => profile.is_client !== true).length;
export const getTrialWindow = (input: {
ownerCreatedAt: Date;
@ -180,7 +179,7 @@ const getPlanHint = (price: StripePriceRow | undefined, product: StripeProductRo
const getOrganizationProfiles = async (supabase: SupabaseClient, organizationId: number) => {
const { data, error } = await supabase
.from("profiles")
.select("id, created_at, is_temporary, is_client, plan")
.select("id, created_at, is_client, plan")
.eq("organization_id", organizationId)
.order("created_at", { ascending: true });

View file

@ -292,11 +292,7 @@ export const createInvitedUser = async (
transporter: Transporter,
recipientEmail: 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
@ -322,16 +318,6 @@ 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 };
}
// Send welcome email to the new user
await transporter.sendMail({
from: `${senderEmail} via XTablo <noreply@xtablo.com>`,

View file

@ -82,7 +82,7 @@ export class MiddlewareManager {
result: AdminTokenResult<T>
): result is Extract<AdminTokenResult<T>, { success: false }> => !result.success;
const createProfileAccessMiddleware = (allowTemporaryUsers: boolean) =>
const createProfileAccessMiddleware = () =>
createMiddleware<{
Variables: { supabase: SupabaseClient; user: User };
Bindings: { user: User };
@ -92,7 +92,7 @@ export class MiddlewareManager {
const { data: profile, error } = await supabase
.from("profiles")
.select("is_temporary, is_client")
.select("is_client")
.eq("id", user.id)
.single();
@ -100,7 +100,7 @@ export class MiddlewareManager {
return c.json({ error: error?.message ?? "Profile not found" }, 500);
}
if ((!allowTemporaryUsers && profile.is_temporary) || profile.is_client) {
if (profile.is_client) {
return c.json({ error: "User is read only" }, 401);
}
@ -217,8 +217,8 @@ export class MiddlewareManager {
await next();
});
const regularUserCheckMiddleware = createProfileAccessMiddleware(false);
const billingCheckoutAccessMiddleware = createProfileAccessMiddleware(true);
const regularUserCheckMiddleware = createProfileAccessMiddleware();
const billingCheckoutAccessMiddleware = createProfileAccessMiddleware();
const activePlanAccessMiddleware = createMiddleware<{
Variables: { supabase: SupabaseClient; user: User };
Bindings: { user: User };

View file

@ -35,7 +35,6 @@ export const getAdminOverviewRouter = () => {
recentTablos,
activeAccess,
adminAccess,
temporaryUsers,
inactiveAccess,
] = await Promise.all([
countRows(supabase.from("profiles").select("*", { count: "exact", head: true })),
@ -68,12 +67,6 @@ export const getAdminOverviewRouter = () => {
.eq("is_active", true)
.eq("is_admin", true)
),
countRows(
supabase
.from("profiles")
.select("*", { count: "exact", head: true })
.eq("is_temporary", true)
),
countRows(
supabase
.from("tablo_access")
@ -84,12 +77,6 @@ export const getAdminOverviewRouter = () => {
const response: AdminOverviewResponse = {
alerts: [
{
description: `${temporaryUsers} temporary users still exist in production.`,
id: "temporary-users",
severity: temporaryUsers > 0 ? "warning" : "info",
title: "Temporary Accounts",
},
{
description: `${inactiveAccess} tablo access rows are inactive and may need review.`,
id: "inactive-access",

View file

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

View file

@ -208,9 +208,7 @@ const inviteToTablo = (
if (!recipientUser) {
// Create a new invited user and add them to the tablo
const result = await createInvitedUser(supabase, transporter, recipientEmail, sender.email, {
isTemporary: true,
});
const result = await createInvitedUser(supabase, transporter, recipientEmail, sender.email);
if (!result.success) {
return c.json({ error: result.error }, 500);
@ -340,12 +338,11 @@ const cancelPendingInvite = (middlewareManager: ReturnType<typeof MiddlewareMana
const { data: invitedProfile } = await supabase
.from("profiles")
.select("id, is_temporary")
.select("id")
.eq("email", invite.invited_email)
.maybeSingle();
// Temporary invitees are pre-added to tablo_access. Revoke this access when invite is cancelled.
if (invitedProfile?.id && invitedProfile.is_temporary) {
if (invitedProfile?.id) {
const { error: revokeAccessError } = await supabase
.from("tablo_access")
.update({ is_active: false })

View file

@ -42,84 +42,6 @@ const getMe = factory.createHandlers(async (c) => {
return c.json({ ...userData, plan: effectivePlan });
});
const markTemporary = factory.createHandlers(async (c) => {
const user = c.get("user");
const supabase = c.get("supabase");
const body = await c.req.json();
const { temporary_password } = body;
const { data: profile, error } = await supabase
.from("profiles")
.update({
is_temporary: true,
})
.eq("id", user.id)
.select()
.single();
if (error) {
return c.json({ error: error.message }, 500);
}
const transporter = c.get("transporter");
try {
if (profile?.email && transporter) {
const mailOptions = {
from: "Xtablo <noreply@xtablo.com>",
to: profile.email,
subject: "Bienvenue sur XTablo - Votre mot de passe temporaire",
text: `Bienvenue sur XTablo !
Votre compte a é créé avec succès. Voici vos informations de connexion :
Email : ${profile.email}
Mot de passe temporaire : ${temporary_password}
Pour des raisons de sécurité, nous vous recommandons fortement de changer ce mot de passe temporaire lors de votre première connexion.
Connectez-vous sur : ${process.env.FRONTEND_URL || "https://app.xtablo.com"}
Cordialement,
L'équipe XTablo`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #333;">Bienvenue sur XTablo !</h2>
<p>Votre compte a é créé avec succès. Voici vos informations de connexion :</p>
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;">
<p><strong>Email :</strong> ${profile.email}</p>
<p><strong>Mot de passe temporaire :</strong> <code style="background-color: #e1e1e1; padding: 2px 4px; border-radius: 3px;">${temporary_password}</code></p>
</div>
<p style="color: #d9534f; margin-bottom: 20px;"><strong>Important :</strong> Pour des raisons de sécurité, nous vous recommandons fortement de changer ce mot de passe temporaire lors de votre première connexion.</p>
<p>
<a href="${process.env.FRONTEND_URL || "https://app.tablo.com"}"
style="background-color: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">
Se connecter à XTablo
</a>
</p>
<p style="color: #666; font-size: 14px; margin-top: 30px;">
Cordialement,<br>
L'équipe XTablo
</p>
</div>
`,
};
await transporter.sendMail(mailOptions);
}
} catch (error) {
console.error("Failed to send welcome email:", error);
}
return c.json({
message: "User marked as temporary",
});
});
// userRouter.put("/profile", async (c) => {
// const user = c.get("user");
@ -278,7 +200,7 @@ const getOrganization = factory.createHandlers(async (c) => {
const { data: members, error: membersError } = await supabase
.from("profiles")
.select("id, email, name, first_name, last_name, avatar_url, created_at, is_temporary, plan")
.select("id, email, name, first_name, last_name, avatar_url, created_at, plan")
.eq("organization_id", organizationId)
.order("created_at", { ascending: true });
@ -418,7 +340,7 @@ const updateOrganization = factory.createHandlers(async (c) => {
const { data: profile, error: profileError } = await supabase
.from("profiles")
.select("organization_id, is_temporary")
.select("organization_id")
.eq("id", user.id)
.single();
@ -426,10 +348,6 @@ const updateOrganization = factory.createHandlers(async (c) => {
return c.json({ error: "Failed to resolve your organization" }, 500);
}
if (profile.is_temporary) {
return c.json({ error: "Temporary users cannot update organization settings" }, 403);
}
const organizationId = profile.organization_id;
const updateData: Record<string, unknown> = {};
@ -497,7 +415,7 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
const { data: senderProfile, error: senderError } = await supabase
.from("profiles")
.select("organization_id, email, is_temporary")
.select("organization_id, email")
.eq("id", user.id)
.single();
@ -505,10 +423,6 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
return c.json({ error: "Failed to resolve your organization" }, 500);
}
if (senderProfile.is_temporary) {
return c.json({ error: "Temporary users cannot invite organization members" }, 403);
}
if (senderProfile.email.toLowerCase() === recipientEmail) {
return c.json({ error: "You cannot invite yourself" }, 400);
}
@ -587,7 +501,6 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
transporter,
recipientEmail,
senderProfile.email,
{ isTemporary: false }
);
if (!invitedUser.success || !invitedUser.userId) {
@ -608,7 +521,7 @@ const inviteToOrganization = factory.createHandlers(async (c) => {
const { error: assignOrganizationError } = await supabase
.from("profiles")
.update({ organization_id: organizationId, is_temporary: false })
.update({ organization_id: organizationId })
.eq("id", invitedUser.userId);
if (assignOrganizationError) {
@ -686,7 +599,7 @@ const removeOrganizationMember = factory.createHandlers(async (c) => {
const { data: actorProfile, error: actorProfileError } = await supabase
.from("profiles")
.select("organization_id, is_temporary")
.select("organization_id")
.eq("id", user.id)
.single();
@ -694,10 +607,6 @@ const removeOrganizationMember = factory.createHandlers(async (c) => {
return c.json({ error: "Failed to resolve your organization" }, 500);
}
if (actorProfile.is_temporary) {
return c.json({ error: "Temporary users cannot manage organization members" }, 403);
}
const organizationId = actorProfile.organization_id;
const { data: billingState, error: billingError } = await getOrganizationBillingState(
supabase,
@ -875,7 +784,6 @@ export const getUserRouter = () => {
userRouter.get("/me", ...getMe);
userRouter.delete("/me", ...deleteMe);
userRouter.post("/mark-temporary", ...markTemporary);
userRouter.post("/profile/avatar", ...uploadAvatar);
userRouter.delete("/profile/avatar", ...deleteAvatar);
userRouter.get("/organization", ...getOrganization);

View file

@ -26,7 +26,6 @@ describe("AuthenticationGateway", () => {
last_name: "User",
email: "client@example.com",
avatar_url: null,
is_temporary: false,
is_client: true,
client_onboarded_at: new Date().toISOString(),
last_signed_in: null,

View file

@ -57,7 +57,6 @@ describe("PublicRoute", () => {
last_name: "User",
email: "test@example.com",
avatar_url: null,
is_temporary: false,
is_client: false,
client_onboarded_at: null,
last_signed_in: null,
@ -109,7 +108,6 @@ describe("PublicRoute", () => {
last_name: "User",
email: "client@example.com",
avatar_url: null,
is_temporary: false,
is_client: true,
client_onboarded_at: new Date().toISOString(),
last_signed_in: null,

View file

@ -462,7 +462,6 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) {
organizationData.active_subscription_quantity >= organizationData.required_team_quantity);
const shouldShowSoloUpsell =
!user.is_temporary &&
!!organizationData &&
organizationData.required_plan === "team" &&
!hasCompliantTeamPlan;

View file

@ -92,7 +92,6 @@ describe("ProtectedRoute", () => {
short_user_id: "123",
first_name: "Test",
last_name: "User",
is_temporary: false,
is_client: false,
client_onboarded_at: null,
last_signed_in: null,
@ -148,7 +147,6 @@ describe("ProtectedRoute", () => {
short_user_id: "123",
first_name: "Client",
last_name: "User",
is_temporary: false,
is_client: true,
client_onboarded_at: new Date().toISOString(),
last_signed_in: null,

View file

@ -6,17 +6,12 @@ import { useMaybeUser } from "../providers/UserStoreProvider";
import { LoadingSpinner } from "./LoadingSpinner";
interface ProtectedRouteProps {
// Fallback URL
fallback?: string;
// Only allow regular users (not temporary)
onlyRegularUser?: boolean;
// Redirect to current page
shouldRedirectToCurrentPage?: boolean;
}
export const ProtectedRoute = ({
fallback,
onlyRegularUser,
shouldRedirectToCurrentPage,
}: ProtectedRouteProps) => {
const user = useMaybeUser();
@ -46,8 +41,6 @@ export const ProtectedRoute = ({
status = "should-redirect";
} else if (user.is_client) {
status = "should-redirect-client";
} else if (onlyRegularUser && user.is_temporary) {
status = "should-redirect";
} else {
status = "should-pass";
}

View file

@ -39,7 +39,6 @@ const baseUser: User = {
last_name: "User",
email: "test@example.com",
avatar_url: null,
is_temporary: false,
is_client: false,
client_onboarded_at: null,
last_signed_in: null,

View file

@ -7,7 +7,6 @@ describe("shouldShowTrialUpsell", () => {
shouldShowTrialUpsell({
isTrialExpired: false,
activeSubscriptionPlan: null,
isTemporaryUser: false,
daysRemaining: 14,
})
).toBe(false);
@ -18,7 +17,6 @@ describe("shouldShowTrialUpsell", () => {
shouldShowTrialUpsell({
isTrialExpired: false,
activeSubscriptionPlan: null,
isTemporaryUser: false,
daysRemaining: 3,
})
).toBe(true);
@ -29,7 +27,6 @@ describe("shouldShowTrialUpsell", () => {
shouldShowTrialUpsell({
isTrialExpired: true,
activeSubscriptionPlan: null,
isTemporaryUser: false,
daysRemaining: null,
})
).toBe(false);
@ -40,20 +37,9 @@ describe("shouldShowTrialUpsell", () => {
shouldShowTrialUpsell({
isTrialExpired: false,
activeSubscriptionPlan: "team",
isTemporaryUser: false,
daysRemaining: 2,
})
).toBe(false);
});
it("does not show for temporary users", () => {
expect(
shouldShowTrialUpsell({
isTrialExpired: false,
activeSubscriptionPlan: null,
isTemporaryUser: true,
daysRemaining: 2,
})
).toBe(false);
});
});

View file

@ -21,12 +21,11 @@ const TRIAL_UPSELL_REMINDER_DAYS = 7;
export const shouldShowTrialUpsell = (input: {
isTrialExpired: boolean;
activeSubscriptionPlan: "solo" | "team" | "annual" | null;
isTemporaryUser: boolean;
daysRemaining: number | null;
}) => {
const { isTrialExpired, activeSubscriptionPlan, isTemporaryUser, daysRemaining } = input;
const { isTrialExpired, activeSubscriptionPlan, daysRemaining } = input;
if (isTrialExpired || activeSubscriptionPlan || isTemporaryUser) {
if (isTrialExpired || activeSubscriptionPlan) {
return false;
}
@ -53,7 +52,6 @@ export function TrialUpsellModal() {
shouldShowTrialUpsell({
isTrialExpired: organizationData.is_trial_expired,
activeSubscriptionPlan: organizationData.active_subscription_plan,
isTemporaryUser: Boolean(user?.is_temporary),
daysRemaining,
})
);

View file

@ -34,7 +34,6 @@ const baseUser: User = {
last_name: "User",
email: "test@example.com",
avatar_url: null,
is_temporary: false,
is_client: false,
client_onboarded_at: null,
last_signed_in: null,
@ -108,12 +107,6 @@ describe("UpgradePanel", () => {
expect(container.innerHTML).toBe("");
});
it("renders nothing for temporary users even with no plan", () => {
const tempUser = { ...baseUser, is_temporary: true };
const { container } = renderPanel(tempUser, noPlanOrg);
expect(container.innerHTML).toBe("");
});
it("renders the paywall for regular users with no plan", () => {
renderPanel(baseUser, noPlanOrg);
expect(screen.getByText("Choisissez un abonnement pour continuer")).toBeInTheDocument();

View file

@ -21,7 +21,6 @@ const baseUser: User = {
last_name: "User",
email: "test@example.com",
avatar_url: null,
is_temporary: false,
is_client: false,
client_onboarded_at: null,
last_signed_in: null,
@ -118,19 +117,6 @@ describe("UpgradeBlockProvider", () => {
expect(screen.getByTestId("blocked").textContent).toBe("false");
});
it("is not blocked for temporary users regardless of org billing state", () => {
const temporaryUser = { ...baseUser, is_temporary: true };
renderWithUser(temporaryUser, noPlanOrgData);
expect(screen.getByTestId("blocked").textContent).toBe("false");
expect(screen.getByTestId("reason").textContent).toBe("none");
});
it("is not blocked for temporary users even with expired trial", () => {
const temporaryUser = { ...baseUser, is_temporary: true };
renderWithUser(temporaryUser, trialExpiredOrgData);
expect(screen.getByTestId("blocked").textContent).toBe("false");
});
it("blocks regular users when org has no active plan", () => {
renderWithUser(baseUser, noPlanOrgData);
expect(screen.getByTestId("blocked").textContent).toBe("true");

View file

@ -28,20 +28,16 @@ export const UpgradeBlockProvider: React.FC<UpgradeBlockProviderProps> = ({ chil
const { data: organizationData } = useOrganization();
const user = useMaybeUser();
// Wait for both user and organization data before deciding.
// Temporary users are never blocked — they have read-only access enforced at the API level.
const reason =
!user || !organizationData
? null
: user.is_temporary
? null
: getOrganizationUpgradeBlockReason({
is_trial_expired: organizationData.is_trial_expired,
required_plan: organizationData.required_plan,
required_team_quantity: organizationData.required_team_quantity,
active_subscription_plan: organizationData.active_subscription_plan,
active_subscription_quantity: organizationData.active_subscription_quantity,
});
: getOrganizationUpgradeBlockReason({
is_trial_expired: organizationData.is_trial_expired,
required_plan: organizationData.required_plan,
required_team_quantity: organizationData.required_team_quantity,
active_subscription_plan: organizationData.active_subscription_plan,
active_subscription_quantity: organizationData.active_subscription_quantity,
});
const isBlocked = reason !== null;

View file

@ -20,7 +20,6 @@ export interface OrganizationMember {
last_name: string | null;
avatar_url: string | null;
created_at: string | null;
is_temporary: boolean;
plan: string | null;
}

View file

@ -59,7 +59,6 @@ const testUser: User = {
last_name: "Doe",
email: "john@example.com",
avatar_url: null,
is_temporary: false,
is_client: false,
client_onboarded_at: null,
last_signed_in: null,

View file

@ -61,7 +61,6 @@ describe("TestUserStoreProvider", () => {
avatar_url: null,
email: null,
first_name: null,
is_temporary: false,
is_client: false,
client_onboarded_at: null,
last_name: null,

View file

@ -92,7 +92,7 @@ export const UserStoreProvider = ({ children }: { children: React.ReactNode }) =
email: user.email ?? undefined,
name:
user.first_name && user.last_name ? `${user.first_name} ${user.last_name}` : undefined,
isReadOnly: user.is_temporary,
isReadOnly: false,
});
} else {
datadogRum.clearUser();
@ -144,7 +144,7 @@ export const useIsReadOnlyUser = () => {
if (!store) {
throw new Error("Missing UserStoreProvider");
}
return useStore(store).is_temporary;
return false;
};
// TestUserStoreProvider component

View file

@ -17,7 +17,6 @@ const defaultUser = {
last_name: "Doe",
email: "john@example.com",
avatar_url: "https://example.com/avatar.jpg",
is_temporary: false,
is_client: false,
client_onboarded_at: null,
last_signed_in: null,

View file

@ -447,7 +447,6 @@ export type Database = {
first_name: string | null;
id: string;
is_client: boolean;
is_temporary: boolean;
last_name: string | null;
last_signed_in: string | null;
name: string | null;
@ -462,7 +461,6 @@ export type Database = {
first_name?: string | null;
id: string;
is_client?: boolean;
is_temporary?: boolean;
last_name?: string | null;
last_signed_in?: string | null;
name?: string | null;
@ -477,7 +475,6 @@ export type Database = {
first_name?: string | null;
id?: string;
is_client?: boolean;
is_temporary?: boolean;
last_name?: string | null;
last_signed_in?: string | null;
name?: string | null;

View file

@ -0,0 +1 @@
ALTER TABLE public.profiles DROP COLUMN IF EXISTS is_temporary;

View file

@ -391,7 +391,6 @@ export type Database = {
email: string | null
first_name: string | null
id: string
is_temporary: boolean
last_name: string | null
last_signed_in: string | null
name: string | null
@ -404,7 +403,6 @@ export type Database = {
email?: string | null
first_name?: string | null
id: string
is_temporary?: boolean
last_name?: string | null
last_signed_in?: string | null
name?: string | null
@ -417,7 +415,6 @@ export type Database = {
email?: string | null
first_name?: string | null
id?: string
is_temporary?: boolean
last_name?: string | null
last_signed_in?: string | null
name?: string | null