diff --git a/apps/api/src/__tests__/fixtures/testData.ts b/apps/api/src/__tests__/fixtures/testData.ts index 06062c9..1e22bf7 100644 --- a/apps/api/src/__tests__/fixtures/testData.ts +++ b/apps/api/src/__tests__/fixtures/testData.ts @@ -11,7 +11,6 @@ export interface TestUser { first_name: string; last_name: string; name: string; - is_temporary: boolean; } export const TEST_USERS: Record = { @@ -21,7 +20,6 @@ export const TEST_USERS: Record = { 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 = { first_name: "Test", last_name: "Temporary", name: "Test Temporary", - is_temporary: true, }, }; diff --git a/apps/api/src/__tests__/helpers/billing.test.ts b/apps/api/src/__tests__/helpers/billing.test.ts index 62c0d11..f58205d 100644 --- a/apps/api/src/__tests__/helpers/billing.test.ts +++ b/apps/api/src/__tests__/helpers/billing.test.ts @@ -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", }, diff --git a/apps/api/src/__tests__/helpers/dbSetup.ts b/apps/api/src/__tests__/helpers/dbSetup.ts index c2a2215..6b3c1c5 100644 --- a/apps/api/src/__tests__/helpers/dbSetup.ts +++ b/apps/api/src/__tests__/helpers/dbSetup.ts @@ -129,19 +129,6 @@ export async function setupTestDatabase(): Promise { 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) => ({ diff --git a/apps/api/src/__tests__/middlewares/middlewares.test.ts b/apps/api/src/__tests__/middlewares/middlewares.test.ts index 08390ab..b3a6cbe 100644 --- a/apps/api/src/__tests__/middlewares/middlewares.test.ts +++ b/apps/api/src/__tests__/middlewares/middlewares.test.ts @@ -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 () => { diff --git a/apps/api/src/helpers/billing.ts b/apps/api/src/helpers/billing.ts index e0e10da..9e1d8dd 100644 --- a/apps/api/src/helpers/billing.ts +++ b/apps/api/src/helpers/billing.ts @@ -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 }); diff --git a/apps/api/src/helpers/helpers.ts b/apps/api/src/helpers/helpers.ts index 298d6d2..a986d78 100644 --- a/apps/api/src/helpers/helpers.ts +++ b/apps/api/src/helpers/helpers.ts @@ -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 `, diff --git a/apps/api/src/middlewares/middleware.ts b/apps/api/src/middlewares/middleware.ts index b53c15b..3d8db24 100644 --- a/apps/api/src/middlewares/middleware.ts +++ b/apps/api/src/middlewares/middleware.ts @@ -82,7 +82,7 @@ export class MiddlewareManager { result: AdminTokenResult ): result is Extract, { 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 }; diff --git a/apps/api/src/routers/adminOverview.ts b/apps/api/src/routers/adminOverview.ts index 22671d7..42acf73 100644 --- a/apps/api/src/routers/adminOverview.ts +++ b/apps/api/src/routers/adminOverview.ts @@ -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", diff --git a/apps/api/src/routers/invite.ts b/apps/api/src/routers/invite.ts index b2b47b2..a4af7a5 100644 --- a/apps/api/src/routers/invite.ts +++ b/apps/api/src/routers/invite.ts @@ -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) { diff --git a/apps/api/src/routers/tablo.ts b/apps/api/src/routers/tablo.ts index 7b843ae..4e953dc 100644 --- a/apps/api/src/routers/tablo.ts +++ b/apps/api/src/routers/tablo.ts @@ -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 { 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 ", - to: profile.email, - subject: "Bienvenue sur XTablo - Votre mot de passe temporaire", - text: `Bienvenue sur XTablo ! - -Votre compte a été 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: ` -
-

Bienvenue sur XTablo !

- -

Votre compte a été créé avec succès. Voici vos informations de connexion :

- -
-

Email : ${profile.email}

-

Mot de passe temporaire : ${temporary_password}

-
- -

Important : Pour des raisons de sécurité, nous vous recommandons fortement de changer ce mot de passe temporaire lors de votre première connexion.

- -

- - Se connecter à XTablo - -

- -

- Cordialement,
- L'équipe XTablo -

-
- `, - }; - 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 = {}; @@ -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); diff --git a/apps/main/src/components/AuthenticationGateway.test.tsx b/apps/main/src/components/AuthenticationGateway.test.tsx index 655a495..e008b56 100644 --- a/apps/main/src/components/AuthenticationGateway.test.tsx +++ b/apps/main/src/components/AuthenticationGateway.test.tsx @@ -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, diff --git a/apps/main/src/components/AuthenticationGateway.unit.tsx b/apps/main/src/components/AuthenticationGateway.unit.tsx index 77515e0..3a14db9 100644 --- a/apps/main/src/components/AuthenticationGateway.unit.tsx +++ b/apps/main/src/components/AuthenticationGateway.unit.tsx @@ -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, diff --git a/apps/main/src/components/NavigationBar.tsx b/apps/main/src/components/NavigationBar.tsx index 12ed900..50d2d9c 100644 --- a/apps/main/src/components/NavigationBar.tsx +++ b/apps/main/src/components/NavigationBar.tsx @@ -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; diff --git a/apps/main/src/components/ProtectedRoute.test.tsx b/apps/main/src/components/ProtectedRoute.test.tsx index b07a8c1..ba387f2 100644 --- a/apps/main/src/components/ProtectedRoute.test.tsx +++ b/apps/main/src/components/ProtectedRoute.test.tsx @@ -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, diff --git a/apps/main/src/components/ProtectedRoute.tsx b/apps/main/src/components/ProtectedRoute.tsx index c9c64a7..e41b316 100644 --- a/apps/main/src/components/ProtectedRoute.tsx +++ b/apps/main/src/components/ProtectedRoute.tsx @@ -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"; } diff --git a/apps/main/src/components/SubscriptionCard.test.tsx b/apps/main/src/components/SubscriptionCard.test.tsx index 8c779a7..17d029a 100644 --- a/apps/main/src/components/SubscriptionCard.test.tsx +++ b/apps/main/src/components/SubscriptionCard.test.tsx @@ -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, diff --git a/apps/main/src/components/TrialUpsellModal.test.ts b/apps/main/src/components/TrialUpsellModal.test.ts index 9e07e9a..609ab6e 100644 --- a/apps/main/src/components/TrialUpsellModal.test.ts +++ b/apps/main/src/components/TrialUpsellModal.test.ts @@ -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); - }); }); diff --git a/apps/main/src/components/TrialUpsellModal.tsx b/apps/main/src/components/TrialUpsellModal.tsx index ab30614..15c34cd 100644 --- a/apps/main/src/components/TrialUpsellModal.tsx +++ b/apps/main/src/components/TrialUpsellModal.tsx @@ -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, }) ); diff --git a/apps/main/src/components/UpgradePanel.test.tsx b/apps/main/src/components/UpgradePanel.test.tsx index b82f2a7..62175f4 100644 --- a/apps/main/src/components/UpgradePanel.test.tsx +++ b/apps/main/src/components/UpgradePanel.test.tsx @@ -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(); diff --git a/apps/main/src/contexts/UpgradeBlockContext.test.tsx b/apps/main/src/contexts/UpgradeBlockContext.test.tsx index cedf044..c0b3158 100644 --- a/apps/main/src/contexts/UpgradeBlockContext.test.tsx +++ b/apps/main/src/contexts/UpgradeBlockContext.test.tsx @@ -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"); diff --git a/apps/main/src/contexts/UpgradeBlockContext.tsx b/apps/main/src/contexts/UpgradeBlockContext.tsx index 04a8f9e..84e0133 100644 --- a/apps/main/src/contexts/UpgradeBlockContext.tsx +++ b/apps/main/src/contexts/UpgradeBlockContext.tsx @@ -28,20 +28,16 @@ export const UpgradeBlockProvider: React.FC = ({ 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; diff --git a/apps/main/src/hooks/organization.ts b/apps/main/src/hooks/organization.ts index c2706e5..84a3602 100644 --- a/apps/main/src/hooks/organization.ts +++ b/apps/main/src/hooks/organization.ts @@ -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; } diff --git a/apps/main/src/pages/planning.test.tsx b/apps/main/src/pages/planning.test.tsx index d4bf8f6..a0ccb74 100644 --- a/apps/main/src/pages/planning.test.tsx +++ b/apps/main/src/pages/planning.test.tsx @@ -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, diff --git a/apps/main/src/providers/UserStoreProvider.test.tsx b/apps/main/src/providers/UserStoreProvider.test.tsx index 84b9bb2..ac3a470 100644 --- a/apps/main/src/providers/UserStoreProvider.test.tsx +++ b/apps/main/src/providers/UserStoreProvider.test.tsx @@ -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, diff --git a/apps/main/src/providers/UserStoreProvider.tsx b/apps/main/src/providers/UserStoreProvider.tsx index e0d6e5f..3f67f0d 100644 --- a/apps/main/src/providers/UserStoreProvider.tsx +++ b/apps/main/src/providers/UserStoreProvider.tsx @@ -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 diff --git a/apps/main/src/utils/testHelpers.tsx b/apps/main/src/utils/testHelpers.tsx index c3a4cab..3db8043 100644 --- a/apps/main/src/utils/testHelpers.tsx +++ b/apps/main/src/utils/testHelpers.tsx @@ -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, diff --git a/packages/shared-types/src/database.types.ts b/packages/shared-types/src/database.types.ts index 66b30a0..bfde7c8 100644 --- a/packages/shared-types/src/database.types.ts +++ b/packages/shared-types/src/database.types.ts @@ -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; diff --git a/supabase/migrations/20260430120000_drop_is_temporary.sql b/supabase/migrations/20260430120000_drop_is_temporary.sql new file mode 100644 index 0000000..2b1279a --- /dev/null +++ b/supabase/migrations/20260430120000_drop_is_temporary.sql @@ -0,0 +1 @@ +ALTER TABLE public.profiles DROP COLUMN IF EXISTS is_temporary; diff --git a/xtablo-expo/lib/database.types.ts b/xtablo-expo/lib/database.types.ts index 0f057fa..74a7898 100644 --- a/xtablo-expo/lib/database.types.ts +++ b/xtablo-expo/lib/database.types.ts @@ -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