@antelopejs/dms-saas 0.1.8 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +262 -13
  2. package/dist/auth/subscription-access-gate.d.ts +1 -1
  3. package/dist/billing-state/index.d.ts +1 -0
  4. package/dist/billing-state/index.js +1 -0
  5. package/dist/billing-state/index.js.map +1 -1
  6. package/dist/billing-state/past-due-banner.d.ts +4 -0
  7. package/dist/billing-state/past-due-banner.js +25 -0
  8. package/dist/billing-state/past-due-banner.js.map +1 -0
  9. package/dist/config/runtime.d.ts +6 -1
  10. package/dist/config/runtime.js +50 -1
  11. package/dist/config/runtime.js.map +1 -1
  12. package/dist/crons/recompute-billing-state.js +4 -1
  13. package/dist/crons/recompute-billing-state.js.map +1 -1
  14. package/dist/data-api/platformOwner/index.d.ts +1 -0
  15. package/dist/data-api/platformOwner/index.js +1 -0
  16. package/dist/data-api/platformOwner/index.js.map +1 -1
  17. package/dist/data-api/platformOwner/invitations.d.ts +22 -0
  18. package/dist/data-api/platformOwner/invitations.js +121 -0
  19. package/dist/data-api/platformOwner/invitations.js.map +1 -0
  20. package/dist/data-api/platformOwner/workspaces.d.ts +3 -0
  21. package/dist/data-api/platformOwner/workspaces.js +64 -12
  22. package/dist/data-api/platformOwner/workspaces.js.map +1 -1
  23. package/dist/data-api/tenant/tenant-invoices.js +2 -10
  24. package/dist/data-api/tenant/tenant-invoices.js.map +1 -1
  25. package/dist/hooks/register.js +2 -1
  26. package/dist/hooks/register.js.map +1 -1
  27. package/dist/implementations/dms-saas/billing.d.ts +2 -1
  28. package/dist/implementations/dms-saas/billing.js +6 -0
  29. package/dist/implementations/dms-saas/billing.js.map +1 -1
  30. package/dist/index.js +5 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/invoice-line-items/injection.d.ts +10 -0
  33. package/dist/invoice-line-items/injection.js +7 -3
  34. package/dist/invoice-line-items/injection.js.map +1 -1
  35. package/dist/notifications/subjects.d.ts +19 -19
  36. package/dist/operator-actions/index.d.ts +1 -0
  37. package/dist/operator-actions/index.js +1 -0
  38. package/dist/operator-actions/index.js.map +1 -1
  39. package/dist/operator-actions/invitation-commands.d.ts +12 -0
  40. package/dist/operator-actions/invitation-commands.js +46 -0
  41. package/dist/operator-actions/invitation-commands.js.map +1 -0
  42. package/dist/operator-actions/types.d.ts +1 -1
  43. package/dist/pages/platform/categories.d.ts +6 -6
  44. package/dist/pages/platform/credit-notes.d.ts +1 -1
  45. package/dist/pages/platform/dashboard.d.ts +2 -2
  46. package/dist/pages/platform/dashboard.js +2 -4
  47. package/dist/pages/platform/dashboard.js.map +1 -1
  48. package/dist/pages/platform/features.d.ts +1 -1
  49. package/dist/pages/platform/invoices.d.ts +1 -1
  50. package/dist/pages/platform/plans/edit.d.ts +2 -2
  51. package/dist/pages/platform/plans/index.d.ts +1 -1
  52. package/dist/pages/platform/plans/migrations.d.ts +1 -1
  53. package/dist/pages/platform/segments/index.d.ts +1 -1
  54. package/dist/pages/platform/settings.d.ts +1 -1
  55. package/dist/pages/platform/users/detail.d.ts +1 -1
  56. package/dist/pages/platform/users/index.d.ts +1 -1
  57. package/dist/pages/platform/workspaces/detail.d.ts +1 -1
  58. package/dist/pages/platform/workspaces/detail.js +40 -1
  59. package/dist/pages/platform/workspaces/detail.js.map +1 -1
  60. package/dist/pages/platform/workspaces/index.d.ts +4 -10
  61. package/dist/pages/platform/workspaces/index.js +27 -111
  62. package/dist/pages/platform/workspaces/index.js.map +1 -1
  63. package/dist/pages/public/legal/privacy-policy.js +2 -1
  64. package/dist/pages/public/legal/privacy-policy.js.map +1 -1
  65. package/dist/pages/public/legal/terms-and-conditions.js +2 -1
  66. package/dist/pages/public/legal/terms-and-conditions.js.map +1 -1
  67. package/dist/pages/public/legal/terms-of-use.js +2 -1
  68. package/dist/pages/public/legal/terms-of-use.js.map +1 -1
  69. package/dist/pages/tenant/billing.d.ts +1 -1
  70. package/dist/plans/index.d.ts +1 -0
  71. package/dist/plans/index.js +1 -0
  72. package/dist/plans/index.js.map +1 -1
  73. package/dist/plans/platform-support.d.ts +2 -0
  74. package/dist/plans/platform-support.js +19 -0
  75. package/dist/plans/platform-support.js.map +1 -0
  76. package/dist/plans/seat-capacity.d.ts +9 -2
  77. package/dist/plans/seat-capacity.js +59 -12
  78. package/dist/plans/seat-capacity.js.map +1 -1
  79. package/dist/plans/seat-hooks.js +34 -13
  80. package/dist/plans/seat-hooks.js.map +1 -1
  81. package/dist/routes/platformOwner/billing-settings.d.ts +2 -2
  82. package/dist/routes/platformOwner/dashboard.d.ts +8 -6
  83. package/dist/routes/platformOwner/dashboard.js +31 -17
  84. package/dist/routes/platformOwner/dashboard.js.map +1 -1
  85. package/dist/routes/platformOwner/index.d.ts +1 -0
  86. package/dist/routes/platformOwner/index.js +1 -0
  87. package/dist/routes/platformOwner/index.js.map +1 -1
  88. package/dist/routes/platformOwner/plan-deletion.d.ts +1 -1
  89. package/dist/routes/platformOwner/plans.d.ts +5 -5
  90. package/dist/routes/platformOwner/segments.d.ts +1 -1
  91. package/dist/routes/platformOwner/workspace-invitations.d.ts +13 -0
  92. package/dist/routes/platformOwner/workspace-invitations.js +73 -0
  93. package/dist/routes/platformOwner/workspace-invitations.js.map +1 -0
  94. package/dist/routes/public/legal-documents.d.ts +2 -2
  95. package/dist/routes/public/register.d.ts +7 -3
  96. package/dist/routes/public/register.js +42 -27
  97. package/dist/routes/public/register.js.map +1 -1
  98. package/dist/routes/tenant/tenant-billing.d.ts +7 -11
  99. package/dist/routes/tenant/tenant-billing.js +34 -26
  100. package/dist/routes/tenant/tenant-billing.js.map +1 -1
  101. package/dist/routes/tenant/tenant-plan.d.ts +1 -0
  102. package/dist/routes/tenant/tenant-plan.js +27 -2
  103. package/dist/routes/tenant/tenant-plan.js.map +1 -1
  104. package/dist/routes/tenant/tenant-seats.d.ts +2 -4
  105. package/dist/routes/tenant/tenant-seats.js.map +1 -1
  106. package/dist/routes/tenant/workspaces.d.ts +3 -1
  107. package/dist/routes/tenant/workspaces.js +10 -4
  108. package/dist/routes/tenant/workspaces.js.map +1 -1
  109. package/dist/stripe/upcoming-invoice.d.ts +21 -0
  110. package/dist/stripe/upcoming-invoice.js +42 -0
  111. package/dist/stripe/upcoming-invoice.js.map +1 -0
  112. package/dist/stripe/webhook-dispatch.js +2 -0
  113. package/dist/stripe/webhook-dispatch.js.map +1 -1
  114. package/dist/types/config.d.ts +9 -0
  115. package/dist/types/config.js +7 -0
  116. package/dist/types/config.js.map +1 -1
  117. package/dist/upcoming-invoice/db/preview-cache.model.d.ts +38 -0
  118. package/dist/upcoming-invoice/db/preview-cache.model.js +57 -0
  119. package/dist/upcoming-invoice/db/preview-cache.model.js.map +1 -0
  120. package/dist/upcoming-invoice/db/preview-cache.table.d.ts +10 -0
  121. package/dist/upcoming-invoice/db/preview-cache.table.js +42 -0
  122. package/dist/upcoming-invoice/db/preview-cache.table.js.map +1 -0
  123. package/dist/upcoming-invoice/eligibility.d.ts +17 -0
  124. package/dist/upcoming-invoice/eligibility.js +52 -0
  125. package/dist/upcoming-invoice/eligibility.js.map +1 -0
  126. package/dist/upcoming-invoice/invalidation.d.ts +2 -0
  127. package/dist/upcoming-invoice/invalidation.js +39 -0
  128. package/dist/upcoming-invoice/invalidation.js.map +1 -0
  129. package/dist/upcoming-invoice/mapping.d.ts +8 -0
  130. package/dist/upcoming-invoice/mapping.js +82 -0
  131. package/dist/upcoming-invoice/mapping.js.map +1 -0
  132. package/dist/upcoming-invoice/outcomes.d.ts +13 -0
  133. package/dist/upcoming-invoice/outcomes.js +15 -0
  134. package/dist/upcoming-invoice/outcomes.js.map +1 -0
  135. package/dist/upcoming-invoice/preview.d.ts +3 -0
  136. package/dist/upcoming-invoice/preview.js +80 -0
  137. package/dist/upcoming-invoice/preview.js.map +1 -0
  138. package/dist/upcoming-invoice/pricing.d.ts +3 -0
  139. package/dist/upcoming-invoice/pricing.js +84 -0
  140. package/dist/upcoming-invoice/pricing.js.map +1 -0
  141. package/dist/upcoming-invoice/usage.d.ts +14 -0
  142. package/dist/upcoming-invoice/usage.js +53 -0
  143. package/dist/upcoming-invoice/usage.js.map +1 -0
  144. package/dist/utils/invoice-lines-type.d.ts +1 -1
  145. package/dist/workspaces/billing-identity.d.ts +19 -0
  146. package/dist/workspaces/billing-identity.js +51 -0
  147. package/dist/workspaces/billing-identity.js.map +1 -0
  148. package/dist/workspaces/default-plan.d.ts +5 -0
  149. package/dist/workspaces/default-plan.js +109 -0
  150. package/dist/workspaces/default-plan.js.map +1 -0
  151. package/dist/workspaces/index.d.ts +2 -0
  152. package/dist/workspaces/index.js +2 -0
  153. package/dist/workspaces/index.js.map +1 -1
  154. package/dist/workspaces/invitations.d.ts +26 -0
  155. package/dist/workspaces/invitations.js +112 -0
  156. package/dist/workspaces/invitations.js.map +1 -0
  157. package/dist/workspaces/provisioning.d.ts +3 -3
  158. package/dist/workspaces/provisioning.js +39 -11
  159. package/dist/workspaces/provisioning.js.map +1 -1
  160. package/frontend-vue/app/components/BillingIdentityFields.vue +179 -0
  161. package/frontend-vue/app/components/PastDueBanner.vue +81 -0
  162. package/frontend-vue/app/components/PaymentMethodCard.vue +94 -38
  163. package/frontend-vue/app/components/PlanComparisonModal.vue +62 -26
  164. package/frontend-vue/app/components/PlanUpgradeModal.vue +181 -0
  165. package/frontend-vue/app/components/PlatformNotes.vue +4 -0
  166. package/frontend-vue/app/components/RefundButton.vue +10 -1
  167. package/frontend-vue/app/components/Register.vue +49 -92
  168. package/frontend-vue/app/components/SeatQuotaBanner.vue +28 -0
  169. package/frontend-vue/app/components/TenantBillingForm.vue +52 -128
  170. package/frontend-vue/app/components/TenantPlanCard.vue +68 -10
  171. package/frontend-vue/app/components/UserSummaryCard.vue +12 -10
  172. package/frontend-vue/app/components/WorkspaceAdminCreateModal.vue +202 -0
  173. package/frontend-vue/app/components/WorkspaceCreateModal.vue +46 -9
  174. package/frontend-vue/app/components/WorkspaceInvitationLink.vue +106 -0
  175. package/frontend-vue/app/components/WorkspaceSummaryCard.vue +39 -15
  176. package/frontend-vue/app/components/WorkspaceSuspended.vue +3 -5
  177. package/frontend-vue/app/components/WorkspaceSwitcherWidget.vue +8 -5
  178. package/frontend-vue/app/composables/useBillingCountries.ts +25 -5
  179. package/frontend-vue/app/composables/useBillingIdentity.ts +141 -0
  180. package/frontend-vue/app/composables/useInvitationLink.ts +69 -0
  181. package/frontend-vue/app/composables/usePastDueBannerAction.ts +20 -0
  182. package/frontend-vue/app/composables/usePlanComparison.ts +15 -0
  183. package/frontend-vue/app/composables/usePlanFeatureFormat.ts +312 -20
  184. package/frontend-vue/app/composables/usePlanFeatureLabel.ts +24 -0
  185. package/frontend-vue/app/composables/useSaasRegistration.ts +110 -223
  186. package/frontend-vue/app/composables/useSeatQuota.ts +8 -0
  187. package/frontend-vue/app/composables/useStripePaymentElement.ts +3 -3
  188. package/frontend-vue/app/composables/useWorkspaceAccessCache.ts +29 -2
  189. package/frontend-vue/app/composables/useWorkspaceFinalization.ts +7 -32
  190. package/frontend-vue/app/custom-pages/auth/no-workspace.vue +49 -194
  191. package/frontend-vue/app/plugins/auth-links.ts +0 -4
  192. package/frontend-vue/app/types/runtime-config.ts +13 -0
  193. package/frontend-vue/dms.frontend.ts +10 -4
  194. package/frontend-vue/i18n/locales/saas-en-GB.json +161 -36
  195. package/frontend-vue/i18n/locales/saas-fr-FR.json +162 -37
  196. package/frontend-vue/package.json +0 -1
  197. package/frontend-vue/pnpm-lock.yaml +1 -125
  198. package/package.json +15 -15
  199. package/frontend-vue/app/middleware/workspace-suspended.global.ts +0 -59
@@ -2,8 +2,9 @@
2
2
  * Completing a self-service registration that entered without a workspace.
3
3
  *
4
4
  * The account already exists — it signed in through OAuth, or through any
5
- * entry point that ends on a tenant assignment token — and everything that is
6
- * still missing is bought here: a workspace, a plan, a card. The API answers
5
+ * entry point that ends on a tenant assignment token — and what is still
6
+ * missing is opened here: a workspace on the free plan, with a card when the
7
+ * deployment asks for one. The API answers
7
8
  * with a session for the workspace it just provisioned, so the visitor lands
8
9
  * signed in rather than back on the login screen.
9
10
  *
@@ -26,34 +27,17 @@ export const SESSION_ESTABLISH_ENDPOINT = "/auth/establish";
26
27
  /** DMS API route that provisions the workspace and mints that token pair. */
27
28
  export const WORKSPACE_FINALIZE_ENDPOINT = "/api/saas/register/finalize";
28
29
 
29
- export interface WorkspaceFinalizationAddress {
30
- country: string;
31
- line1?: string;
32
- postalCode?: string;
33
- city?: string;
34
- }
35
-
36
- /** The completion form as the visitor left it, plus the confirmed card. */
30
+ /** The completion form as the visitor left it, plus the card if one was taken. */
37
31
  export interface WorkspaceFinalization {
38
32
  tenantAssignmentToken: string;
39
33
  workspaceName: string;
40
- planId: string;
41
- customerType: "individual" | "business";
42
- companyName: string;
43
- vatNumber: string;
44
- address: WorkspaceFinalizationAddress;
45
- paymentMethodId: string;
34
+ paymentMethodId?: string;
46
35
  }
47
36
 
48
37
  export interface WorkspaceFinalizationPayload {
49
38
  tenant_assignment_token: string;
50
39
  workspaceName: string;
51
- planId: string;
52
- customerType: "individual" | "business";
53
- companyName?: string;
54
- vatNumber?: string;
55
- address: WorkspaceFinalizationAddress;
56
- paymentMethodId: string;
40
+ paymentMethodId?: string;
57
41
  }
58
42
 
59
43
  /** What `/auth/establish` takes: an endpoint to call, and its body. */
@@ -70,26 +54,17 @@ export interface SessionEstablishRequest {
70
54
  * named in the envelope is the one the operator allowed — a page that inlined
71
55
  * either would drift from the other.
72
56
  *
73
- * Company fields only travel for a business customer: an individual who typed
74
- * a company name and switched back would otherwise be invoiced as one.
75
- *
76
- * @param finalization Completion form and confirmed payment method
57
+ * @param finalization Completion form and the card, when one was confirmed
77
58
  * @returns Body to POST to `/auth/establish`
78
59
  */
79
60
  export function buildSessionEstablishRequest(
80
61
  finalization: WorkspaceFinalization,
81
62
  ): SessionEstablishRequest {
82
- const isBusiness = finalization.customerType === "business";
83
63
  return {
84
64
  endpoint: WORKSPACE_FINALIZE_ENDPOINT,
85
65
  payload: {
86
66
  tenant_assignment_token: finalization.tenantAssignmentToken,
87
67
  workspaceName: finalization.workspaceName,
88
- planId: finalization.planId,
89
- customerType: finalization.customerType,
90
- companyName: isBusiness ? finalization.companyName : undefined,
91
- vatNumber: isBusiness ? finalization.vatNumber : undefined,
92
- address: finalization.address,
93
68
  paymentMethodId: finalization.paymentMethodId,
94
69
  },
95
70
  };
@@ -1,19 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, onMounted, ref } from "vue";
3
3
 
4
- interface PlanOption {
5
- _id: string;
6
- name: string;
7
- description: string;
8
- price: number;
9
- currency: string;
10
- interval: string;
11
- trialDays: number;
12
- audience: string;
13
- borderColor: string | null;
14
- borderLabel: string | null;
15
- }
16
-
17
4
  interface OAuthProviderOption {
18
5
  id: string;
19
6
  label: string;
@@ -25,34 +12,29 @@ interface DmsPublicRuntime {
25
12
  oauthProviders?: OAuthProviderOption[];
26
13
  }
27
14
 
28
- interface DmsSaasPublicRuntimeConfig {
29
- stripePublishableKey?: string;
30
- admissionMode?: "open" | "invitation-only";
31
- }
32
-
33
15
  interface PendingRegistration {
34
16
  email: string;
35
17
  name: string;
36
18
  provider: string | null;
37
19
  }
38
20
 
39
- const PLANS_ENDPOINT = "/api/saas/plans/public";
40
21
  const SETUP_INTENT_ENDPOINT = "/api/saas/register/setup-intent";
41
22
  const PENDING_ENDPOINT = "/api/saas/register/pending";
42
23
  const LOGIN_REDIRECT = "/auth/login";
43
24
  const PAYMENT_ELEMENT_ID = "dms-saas-finalize-payment-element";
44
25
  const HOME_REDIRECT = "/";
45
26
  const FALLBACK_PROVIDER_ICON = "i-ph-shield-check";
27
+ const DEFAULT_WORKSPACE_NAME_KEY = "saas.register.default_workspace_name";
46
28
 
47
29
  const route = useDmsRoute();
48
30
  const config = useDmsRuntimeConfig();
49
31
  const nuxtApp = useDmsApp();
50
- const dmsSaasRuntime = computed(
51
- () => config.public.dmsSaas as DmsSaasPublicRuntimeConfig | undefined,
52
- );
53
- const isRegistrationClosed = computed(
54
- () => dmsSaasRuntime.value?.admissionMode === "invitation-only",
55
- );
32
+ const dmsSaasRuntime = config.public.dmsSaas as
33
+ | DmsSaasPublicRuntimeConfig
34
+ | undefined;
35
+ const isRegistrationClosed = isRegistrationClosedBy(dmsSaasRuntime);
36
+ const paymentMethodPolicy = resolveRegistrationPaymentPolicy(dmsSaasRuntime);
37
+ const canSkipPaymentMethod = paymentMethodPolicy === "optional";
56
38
 
57
39
  const tenantAssignmentToken = computed<string>(() => {
58
40
  const value = route.query.token;
@@ -63,53 +45,24 @@ const dmsRuntime = computed<DmsPublicRuntime>(
63
45
  () => config.public.dms as DmsPublicRuntime,
64
46
  );
65
47
 
66
- const stripePublishableKey = computed<string>(
67
- () => dmsSaasRuntime.value?.stripePublishableKey ?? "",
68
- );
69
-
70
48
  // Only `finalize` goes through the frontend server, and only because it opens
71
49
  // the session cookie on the workspace it provisions; every other public
72
50
  // registration endpoint is called on the DMS API directly.
73
51
  const apiFetch = $fetch.create({ baseURL: dmsRuntime.value.baseURL });
74
52
 
75
- const { countryItems } = useBillingCountries();
76
-
77
- const customerType = ref<"individual" | "business">("individual");
78
- const workspaceName = ref("");
79
- const companyName = ref("");
80
- const vatNumber = ref("");
81
- const country = ref("");
82
- const addressLine1 = ref("");
83
- const postalCode = ref("");
84
- const city = ref("");
85
- const selectedPlanId = ref<string | null>(null);
86
- const plans = ref<PlanOption[]>([]);
87
- const isLoadingPlans = ref(false);
88
53
  const isSubmitting = ref(false);
89
54
  const errorMessage = ref<string | null>(null);
90
55
  const { resolveApiError } = useApiErrorMessage();
91
56
  const pendingRegistration = ref<PendingRegistration | null>(null);
92
57
  const entryErrorMessage = ref<string | null>(null);
93
58
  const hasAcceptedLegal = ref(false);
59
+ const skipsPaymentMethod = ref(false);
94
60
  const stripeHandle = ref<ReturnType<typeof useStripePaymentElement> | null>(
95
61
  null,
96
62
  );
97
63
 
98
- const customerTypeItems = computed(() => [
99
- {
100
- value: "individual",
101
- label: nuxtApp.$i18n.t("saas.register.customer_type.individual"),
102
- },
103
- {
104
- value: "business",
105
- label: nuxtApp.$i18n.t("saas.register.customer_type.business"),
106
- },
107
- ]);
108
-
109
- const filteredPlans = computed(() =>
110
- plans.value.filter(
111
- (plan) => plan.audience === "any" || plan.audience === customerType.value,
112
- ),
64
+ const isPaymentStepVisible = computed(() =>
65
+ isPaymentStepShown(paymentMethodPolicy, skipsPaymentMethod.value),
113
66
  );
114
67
 
115
68
  const tokenMissing = computed(() => tenantAssignmentToken.value.length === 0);
@@ -141,14 +94,16 @@ const entryDescription = computed<string>(() => {
141
94
  });
142
95
  });
143
96
 
97
+ const missingRequirement = computed(() =>
98
+ firstMissingRegistrationRequirement({
99
+ isPaymentRequired: isPaymentStepVisible.value,
100
+ isPaymentReady: stripeHandle.value !== null,
101
+ hasAcceptedLegal: hasAcceptedLegal.value,
102
+ }),
103
+ );
104
+
144
105
  const submitDisabled = computed(
145
- () =>
146
- isSubmitting.value ||
147
- isLoadingPlans.value ||
148
- !selectedPlanId.value ||
149
- !workspaceName.value ||
150
- !country.value ||
151
- !hasAcceptedLegal.value,
106
+ () => isSubmitting.value || !hasAcceptedLegal.value,
152
107
  );
153
108
 
154
109
  const submitLabel = computed(() =>
@@ -167,20 +122,12 @@ async function loadPendingRegistration(): Promise<void> {
167
122
  );
168
123
  }
169
124
 
170
- async function loadPlans(): Promise<void> {
171
- isLoadingPlans.value = true;
172
- try {
173
- plans.value = await apiFetch<PlanOption[]>(PLANS_ENDPOINT);
174
- } finally {
175
- isLoadingPlans.value = false;
176
- }
177
- }
178
-
179
125
  async function initStripeElement(): Promise<void> {
180
- if (!stripePublishableKey.value) return;
126
+ const publishableKey = dmsSaasRuntime?.stripePublishableKey;
127
+ if (!publishableKey || paymentMethodPolicy === "none") return;
181
128
  const setup = await apiFetch<{ clientSecret: string }>(SETUP_INTENT_ENDPOINT);
182
129
  stripeHandle.value = useStripePaymentElement({
183
- publishableKey: stripePublishableKey.value,
130
+ publishableKey,
184
131
  clientSecret: setup.clientSecret,
185
132
  containerId: PAYMENT_ELEMENT_ID,
186
133
  });
@@ -192,6 +139,17 @@ function backToLogin(): void {
192
139
  }
193
140
  }
194
141
 
142
+ /** The confirmed card, undefined when none is collected, null on failure. */
143
+ async function confirmPaymentMethod(): Promise<string | undefined | null> {
144
+ if (!isPaymentStepVisible.value) return undefined;
145
+ const confirm = await stripeHandle.value?.confirmAndGetPaymentMethod();
146
+ if (confirm?.paymentMethodId) return confirm.paymentMethodId;
147
+ errorMessage.value =
148
+ confirm?.error?.message ??
149
+ nuxtApp.$i18n.t("saas.register.error.no_payment");
150
+ return null;
151
+ }
152
+
195
153
  async function submit(): Promise<void> {
196
154
  if (tokenMissing.value) {
197
155
  errorMessage.value = nuxtApp.$i18n.t(
@@ -199,48 +157,23 @@ async function submit(): Promise<void> {
199
157
  );
200
158
  return;
201
159
  }
202
- if (!selectedPlanId.value) {
203
- errorMessage.value = nuxtApp.$i18n.t("saas.register.error.no_plan");
204
- return;
205
- }
206
- if (!stripeHandle.value) {
207
- errorMessage.value = nuxtApp.$i18n.t("saas.register.error.no_payment");
208
- return;
209
- }
210
- if (!hasAcceptedLegal.value) {
211
- errorMessage.value = nuxtApp.$i18n.t("saas.register.error.legal_required");
212
- return;
213
- }
214
- if (!country.value) {
215
- errorMessage.value = nuxtApp.$i18n.t("saas.register.error.no_country");
160
+ if (missingRequirement.value) {
161
+ errorMessage.value = nuxtApp.$i18n.t(missingRequirement.value);
216
162
  return;
217
163
  }
218
164
  errorMessage.value = null;
219
165
  isSubmitting.value = true;
220
166
  try {
221
- const confirm = await stripeHandle.value.confirmAndGetPaymentMethod();
222
- if (confirm.error || !confirm.paymentMethodId) {
223
- errorMessage.value =
224
- confirm.error?.message ??
225
- nuxtApp.$i18n.t("saas.register.error.no_payment");
226
- return;
227
- }
167
+ const paymentMethodId = await confirmPaymentMethod();
168
+ if (paymentMethodId === null) return;
228
169
  await $fetch(SESSION_ESTABLISH_ENDPOINT, {
229
170
  method: "POST",
230
171
  body: buildSessionEstablishRequest({
231
172
  tenantAssignmentToken: tenantAssignmentToken.value,
232
- workspaceName: workspaceName.value,
233
- planId: selectedPlanId.value,
234
- customerType: customerType.value,
235
- companyName: companyName.value,
236
- vatNumber: vatNumber.value,
237
- address: {
238
- country: country.value,
239
- line1: addressLine1.value || undefined,
240
- postalCode: postalCode.value || undefined,
241
- city: city.value || undefined,
242
- },
243
- paymentMethodId: confirm.paymentMethodId,
173
+ workspaceName: nuxtApp.$i18n.t(DEFAULT_WORKSPACE_NAME_KEY, {
174
+ name: pendingRegistration.value?.name ?? "",
175
+ }),
176
+ paymentMethodId,
244
177
  }),
245
178
  });
246
179
  if (typeof window !== "undefined") {
@@ -254,7 +187,7 @@ async function submit(): Promise<void> {
254
187
  }
255
188
 
256
189
  onMounted(async () => {
257
- if (tokenMissing.value || isRegistrationClosed.value) return;
190
+ if (tokenMissing.value || isRegistrationClosed) return;
258
191
  // The identity is resolved before anything is asked of the user: an expired
259
192
  // token or an account that already owns a workspace must fail here, not
260
193
  // after a card has been entered.
@@ -268,7 +201,6 @@ onMounted(async () => {
268
201
  return;
269
202
  }
270
203
  try {
271
- await loadPlans();
272
204
  await initStripeElement();
273
205
  } catch (error) {
274
206
  errorMessage.value = resolveApiError(error, "saas.register.error.failed");
@@ -341,91 +273,14 @@ onMounted(async () => {
341
273
  :description="$t('saas.oauth_registration.identity.hint')"
342
274
  />
343
275
 
344
- <UCard>
345
- <template #header>
346
- <div>
347
- <h2 class="text-lg font-semibold">
348
- {{ $t("saas.no_workspace.section.details") }}
349
- </h2>
350
- <p class="text-muted mt-1 text-sm">
351
- {{ $t("saas.no_workspace.section.details_hint") }}
352
- </p>
353
- </div>
354
- </template>
355
- <div class="flex flex-col gap-4">
356
- <URadioGroup
357
- v-model="customerType"
358
- orientation="horizontal"
359
- :items="customerTypeItems"
360
- />
361
- <UFormField :label="$t('saas.register.field.workspace_name')">
362
- <UInput
363
- v-model="workspaceName"
364
- icon="i-ph-buildings"
365
- required
366
- class="w-full"
367
- />
368
- </UFormField>
369
- <template v-if="customerType === 'business'">
370
- <UFormField :label="$t('saas.register.field.company_name')">
371
- <UInput v-model="companyName" required class="w-full" />
372
- </UFormField>
373
- <UFormField :label="$t('saas.register.field.vat_number')">
374
- <UInput v-model="vatNumber" class="w-full" />
375
- </UFormField>
376
- </template>
377
- <h3 class="font-semibold">
378
- {{ $t("saas.register.billing_address") }}
379
- </h3>
380
- <UFormField :label="$t('saas.register.field.country')">
381
- <USelect v-model="country" :items="countryItems" class="w-full" />
382
- </UFormField>
383
- <UFormField :label="$t('saas.register.field.address_line1')">
384
- <UInput v-model="addressLine1" class="w-full" />
385
- </UFormField>
386
- <div class="grid grid-cols-2 gap-4">
387
- <UFormField :label="$t('saas.register.field.postal_code')">
388
- <UInput v-model="postalCode" class="w-full" />
389
- </UFormField>
390
- <UFormField :label="$t('saas.register.field.city')">
391
- <UInput v-model="city" class="w-full" />
392
- </UFormField>
393
- </div>
394
- </div>
395
- </UCard>
396
-
397
- <UCard>
398
- <template #header>
399
- <div>
400
- <h2 class="text-lg font-semibold">
401
- {{ $t("saas.no_workspace.section.plan") }}
402
- </h2>
403
- <p class="text-muted mt-1 text-sm">
404
- {{ $t("saas.no_workspace.section.plan_hint") }}
405
- </p>
406
- </div>
407
- </template>
408
- <div
409
- v-if="isLoadingPlans"
410
- class="grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-4"
411
- >
412
- <USkeleton v-for="i in 3" :key="i" class="h-40 rounded-lg" />
413
- </div>
414
- <div
415
- v-else
416
- class="grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-4"
417
- >
418
- <DmsSaasPlanCardPublic
419
- v-for="plan in filteredPlans"
420
- :key="plan._id"
421
- :plan="plan"
422
- :selected="selectedPlanId === plan._id"
423
- @select="selectedPlanId = $event"
424
- />
425
- </div>
426
- </UCard>
276
+ <UCheckbox
277
+ v-if="canSkipPaymentMethod"
278
+ v-model="skipsPaymentMethod"
279
+ :label="$t('saas.register.skip_payment_method')"
280
+ />
427
281
 
428
- <UCard>
282
+ <!-- v-show keeps the Stripe element mounted while the step is hidden. -->
283
+ <UCard v-show="isPaymentStepVisible">
429
284
  <template #header>
430
285
  <div>
431
286
  <h2 class="text-lg font-semibold">
@@ -3,10 +3,6 @@ const REGISTER_LINK_LABEL = "saas.auth.login.create_account";
3
3
  const REGISTER_LINK_ROUTE = "/register";
4
4
  const REGISTER_LINK_ORDER = 100;
5
5
 
6
- interface DmsSaasPublicRuntimeConfig {
7
- admissionMode?: "open" | "invitation-only";
8
- }
9
-
10
6
  export default defineDmsPlugin(() => {
11
7
  const config = useDmsRuntimeConfig();
12
8
  const dmsSaas = config.public.dmsSaas as
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Whether registration asks for a card, as the deployment configured it in
3
+ * `registration.paymentMethod`: `required` always does, `optional` lets the
4
+ * visitor skip it, `none` never shows the card step and never calls Stripe.
5
+ */
6
+ export type RegistrationPaymentMethodPolicy = "required" | "optional" | "none";
7
+
8
+ /** What dms-saas publishes to the browser through the frontend module options. */
9
+ export interface DmsSaasPublicRuntimeConfig {
10
+ stripePublishableKey?: string;
11
+ admissionMode?: "open" | "invitation-only";
12
+ registrationPaymentMethod?: RegistrationPaymentMethodPolicy;
13
+ }
@@ -9,7 +9,12 @@ import footerLinks from './app/plugins/footer-links'
9
9
  import planCards from './app/plugins/plan-cards-display.client'
10
10
  import segmentConditions from './app/plugins/segment-conditions-data-type.client'
11
11
  import sidebarWidgets from './app/plugins/sidebar-widgets'
12
- import workspaceSuspended from './app/middleware/workspace-suspended.global'
12
+
13
+ // The tenant access gate's refusal code: the server answers a refused page
14
+ // visit, a reload as much as an Inertia one, with a redirect to the suspended
15
+ // screen instead of its generic 403 page.
16
+ const WORKSPACE_ACCESS_BLOCKED_CODE = 'saas.errors.workspace.access_blocked'
17
+ const WORKSPACE_SUSPENDED_PATH = '/workspace-suspended'
13
18
 
14
19
  interface VueModule {
15
20
  default: Component
@@ -59,9 +64,10 @@ const frontendModule: DmsFrontendModule = {
59
64
  sdk.registerPlugin(planCards, { clientOnly: true })
60
65
  sdk.registerPlugin(segmentConditions, { clientOnly: true })
61
66
  sdk.registerPlugin(sidebarWidgets)
62
- sdk.registerMiddleware('workspace-suspended', workspaceSuspended, {
63
- global: true,
64
- })
67
+ sdk.registerAccessRedirect(
68
+ WORKSPACE_ACCESS_BLOCKED_CODE,
69
+ WORKSPACE_SUSPENDED_PATH,
70
+ )
65
71
  },
66
72
  }
67
73