diff --git a/apps/app/src/actions/organization/initialize-organization-action.ts b/apps/app/src/actions/organization/initialize-organization-action.ts
deleted file mode 100644
index de9b0c3055..0000000000
--- a/apps/app/src/actions/organization/initialize-organization-action.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-'use server';
-
-import { createFleetLabelForOrg } from '@/jobs/tasks/device/create-fleet-label-for-org';
-import { auth } from '@/utils/auth';
-import { db } from '@comp/db';
-import { revalidatePath } from 'next/cache';
-import { headers } from 'next/headers';
-import { authActionClient } from '../safe-action';
-import { organizationSchema } from '../schema';
-import { createStripeCustomer } from './lib/create-stripe-customer';
-import { initializeOrganization } from './lib/initialize-organization';
-
-export const initializeOrganizationAction = authActionClient
- .inputSchema(organizationSchema)
- .metadata({
- name: 'initialize-organization',
- track: {
- event: 'initialize-organization',
- channel: 'server',
- },
- })
- .action(async ({ parsedInput, ctx }) => {
- const { frameworkIds } = parsedInput;
-
- try {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- if (!session?.session.activeOrganizationId) {
- throw new Error('User is not part of an organization');
- }
-
- await db.onboarding.create({
- data: {
- organizationId: session.session.activeOrganizationId,
- completed: false,
- },
- });
-
- const organizationId = session.session.activeOrganizationId;
-
- const stripeCustomerId = await createStripeCustomer({
- name: 'My Organization',
- email: session.user.email,
- organizationId,
- });
-
- if (!stripeCustomerId) {
- throw new Error('Failed to create Stripe customer');
- }
-
- await db.organization.update({
- where: { id: organizationId },
- data: { stripeCustomerId },
- });
-
- await initializeOrganization({ frameworkIds, organizationId });
-
- await auth.api.setActiveOrganization({
- headers: await headers(),
- body: {
- organizationId,
- },
- });
-
- const userOrgs = await db.member.findMany({
- where: {
- userId: session.user.id,
- },
- select: {
- organizationId: true,
- },
- });
-
- for (const org of userOrgs) {
- revalidatePath(`/${org.organizationId}`);
- }
-
- await createFleetLabelForOrg.trigger({
- organizationId,
- });
-
- return {
- success: true,
- organizationId,
- };
- } catch (error) {
- console.error('Error during organization creation/update:', error);
-
- throw new Error('Failed to create or update organization structure');
- }
- });
diff --git a/apps/app/src/app/(app)/[orgId]/layout.tsx b/apps/app/src/app/(app)/[orgId]/layout.tsx
index a7f9b79695..8ce26fb09e 100644
--- a/apps/app/src/app/(app)/[orgId]/layout.tsx
+++ b/apps/app/src/app/(app)/[orgId]/layout.tsx
@@ -83,7 +83,7 @@ export default async function Layout({
},
});
- const isOnboardingRunning = !!onboarding?.triggerJobId && !onboarding.completed;
+ const isOnboardingRunning = !!onboarding?.triggerJobId && !onboarding.triggerJobCompleted;
const navbarHeight = 53 + 1; // 1 for border
const onboardingHeight = 132 + 1; // 1 for border
diff --git a/apps/app/src/app/(app)/[orgId]/settings/billing/page.tsx b/apps/app/src/app/(app)/[orgId]/settings/billing/page.tsx
index 0c75cd6dfb..6a7f2d53b3 100644
--- a/apps/app/src/app/(app)/[orgId]/settings/billing/page.tsx
+++ b/apps/app/src/app/(app)/[orgId]/settings/billing/page.tsx
@@ -80,6 +80,7 @@ export default function BillingPage() {
'Community Support',
],
trialDays: 14,
+ minimumTermMonths: 12,
},
managed: {
displayName: 'Done For You',
@@ -98,10 +99,16 @@ export default function BillingPage() {
const currentPlanConfig = planConfig[planType];
- // Calculate if minimum term has been met for managed plans
+ // Calculate if minimum term has been met for both starter and managed plans
const hasMetMinimumTerm = () => {
+ // Free trials can be cancelled anytime
+ if (isTrialing) {
+ return true;
+ }
+
+ // Only enforce minimum term for starter and managed plans
if (
- planType !== 'managed' ||
+ (planType !== 'starter' && planType !== 'managed') ||
!('currentPeriodStart' in subscription) ||
subscription.currentPeriodStart == null
) {
@@ -113,15 +120,25 @@ export default function BillingPage() {
const monthsElapsed =
(now.getFullYear() - startDate.getFullYear()) * 12 + (now.getMonth() - startDate.getMonth());
- return monthsElapsed >= (planConfig.managed.minimumTermMonths || 12);
+ const minimumTermMonths =
+ planType === 'starter'
+ ? planConfig.starter.minimumTermMonths
+ : planConfig.managed.minimumTermMonths;
+
+ return monthsElapsed >= (minimumTermMonths || 12);
};
const canCancelSubscription = hasMetMinimumTerm();
// Calculate when cancellation will be available
const getCancellationAvailableDate = () => {
+ // Free trials don't have minimum term
+ if (isTrialing) {
+ return null;
+ }
+
if (
- planType !== 'managed' ||
+ (planType !== 'starter' && planType !== 'managed') ||
!('currentPeriodStart' in subscription) ||
subscription.currentPeriodStart == null
) {
@@ -130,9 +147,12 @@ export default function BillingPage() {
const startDate = new Date(subscription.currentPeriodStart * 1000);
const cancellationDate = new Date(startDate);
- cancellationDate.setMonth(
- cancellationDate.getMonth() + (planConfig.managed.minimumTermMonths || 12),
- );
+ const minimumTermMonths =
+ planType === 'starter'
+ ? planConfig.starter.minimumTermMonths
+ : planConfig.managed.minimumTermMonths;
+
+ cancellationDate.setMonth(cancellationDate.getMonth() + (minimumTermMonths || 12));
return cancellationDate;
};
@@ -349,8 +369,8 @@ export default function BillingPage() {
{planType === 'starter' && (
- Add a payment method now to continue after your 14-day trial. You won't be charged
- until the trial ends.
+ Add a payment method now to continue after your trial. You won't be charged until
+ the trial ends. Note: This plan requires a 12-month minimum commitment.