@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.
- package/README.md +262 -13
- package/dist/auth/subscription-access-gate.d.ts +1 -1
- package/dist/billing-state/index.d.ts +1 -0
- package/dist/billing-state/index.js +1 -0
- package/dist/billing-state/index.js.map +1 -1
- package/dist/billing-state/past-due-banner.d.ts +4 -0
- package/dist/billing-state/past-due-banner.js +25 -0
- package/dist/billing-state/past-due-banner.js.map +1 -0
- package/dist/config/runtime.d.ts +6 -1
- package/dist/config/runtime.js +50 -1
- package/dist/config/runtime.js.map +1 -1
- package/dist/crons/recompute-billing-state.js +4 -1
- package/dist/crons/recompute-billing-state.js.map +1 -1
- package/dist/data-api/platformOwner/index.d.ts +1 -0
- package/dist/data-api/platformOwner/index.js +1 -0
- package/dist/data-api/platformOwner/index.js.map +1 -1
- package/dist/data-api/platformOwner/invitations.d.ts +22 -0
- package/dist/data-api/platformOwner/invitations.js +121 -0
- package/dist/data-api/platformOwner/invitations.js.map +1 -0
- package/dist/data-api/platformOwner/workspaces.d.ts +3 -0
- package/dist/data-api/platformOwner/workspaces.js +64 -12
- package/dist/data-api/platformOwner/workspaces.js.map +1 -1
- package/dist/data-api/tenant/tenant-invoices.js +2 -10
- package/dist/data-api/tenant/tenant-invoices.js.map +1 -1
- package/dist/hooks/register.js +2 -1
- package/dist/hooks/register.js.map +1 -1
- package/dist/implementations/dms-saas/billing.d.ts +2 -1
- package/dist/implementations/dms-saas/billing.js +6 -0
- package/dist/implementations/dms-saas/billing.js.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/invoice-line-items/injection.d.ts +10 -0
- package/dist/invoice-line-items/injection.js +7 -3
- package/dist/invoice-line-items/injection.js.map +1 -1
- package/dist/notifications/subjects.d.ts +19 -19
- package/dist/operator-actions/index.d.ts +1 -0
- package/dist/operator-actions/index.js +1 -0
- package/dist/operator-actions/index.js.map +1 -1
- package/dist/operator-actions/invitation-commands.d.ts +12 -0
- package/dist/operator-actions/invitation-commands.js +46 -0
- package/dist/operator-actions/invitation-commands.js.map +1 -0
- package/dist/operator-actions/types.d.ts +1 -1
- package/dist/pages/platform/categories.d.ts +6 -6
- package/dist/pages/platform/credit-notes.d.ts +1 -1
- package/dist/pages/platform/dashboard.d.ts +2 -2
- package/dist/pages/platform/dashboard.js +2 -4
- package/dist/pages/platform/dashboard.js.map +1 -1
- package/dist/pages/platform/features.d.ts +1 -1
- package/dist/pages/platform/invoices.d.ts +1 -1
- package/dist/pages/platform/plans/edit.d.ts +2 -2
- package/dist/pages/platform/plans/index.d.ts +1 -1
- package/dist/pages/platform/plans/migrations.d.ts +1 -1
- package/dist/pages/platform/segments/index.d.ts +1 -1
- package/dist/pages/platform/settings.d.ts +1 -1
- package/dist/pages/platform/users/detail.d.ts +1 -1
- package/dist/pages/platform/users/index.d.ts +1 -1
- package/dist/pages/platform/workspaces/detail.d.ts +1 -1
- package/dist/pages/platform/workspaces/detail.js +40 -1
- package/dist/pages/platform/workspaces/detail.js.map +1 -1
- package/dist/pages/platform/workspaces/index.d.ts +4 -10
- package/dist/pages/platform/workspaces/index.js +27 -111
- package/dist/pages/platform/workspaces/index.js.map +1 -1
- package/dist/pages/public/legal/privacy-policy.js +2 -1
- package/dist/pages/public/legal/privacy-policy.js.map +1 -1
- package/dist/pages/public/legal/terms-and-conditions.js +2 -1
- package/dist/pages/public/legal/terms-and-conditions.js.map +1 -1
- package/dist/pages/public/legal/terms-of-use.js +2 -1
- package/dist/pages/public/legal/terms-of-use.js.map +1 -1
- package/dist/pages/tenant/billing.d.ts +1 -1
- package/dist/plans/index.d.ts +1 -0
- package/dist/plans/index.js +1 -0
- package/dist/plans/index.js.map +1 -1
- package/dist/plans/platform-support.d.ts +2 -0
- package/dist/plans/platform-support.js +19 -0
- package/dist/plans/platform-support.js.map +1 -0
- package/dist/plans/seat-capacity.d.ts +9 -2
- package/dist/plans/seat-capacity.js +59 -12
- package/dist/plans/seat-capacity.js.map +1 -1
- package/dist/plans/seat-hooks.js +34 -13
- package/dist/plans/seat-hooks.js.map +1 -1
- package/dist/routes/platformOwner/billing-settings.d.ts +2 -2
- package/dist/routes/platformOwner/dashboard.d.ts +8 -6
- package/dist/routes/platformOwner/dashboard.js +31 -17
- package/dist/routes/platformOwner/dashboard.js.map +1 -1
- package/dist/routes/platformOwner/index.d.ts +1 -0
- package/dist/routes/platformOwner/index.js +1 -0
- package/dist/routes/platformOwner/index.js.map +1 -1
- package/dist/routes/platformOwner/plan-deletion.d.ts +1 -1
- package/dist/routes/platformOwner/plans.d.ts +5 -5
- package/dist/routes/platformOwner/segments.d.ts +1 -1
- package/dist/routes/platformOwner/workspace-invitations.d.ts +13 -0
- package/dist/routes/platformOwner/workspace-invitations.js +73 -0
- package/dist/routes/platformOwner/workspace-invitations.js.map +1 -0
- package/dist/routes/public/legal-documents.d.ts +2 -2
- package/dist/routes/public/register.d.ts +7 -3
- package/dist/routes/public/register.js +42 -27
- package/dist/routes/public/register.js.map +1 -1
- package/dist/routes/tenant/tenant-billing.d.ts +7 -11
- package/dist/routes/tenant/tenant-billing.js +34 -26
- package/dist/routes/tenant/tenant-billing.js.map +1 -1
- package/dist/routes/tenant/tenant-plan.d.ts +1 -0
- package/dist/routes/tenant/tenant-plan.js +27 -2
- package/dist/routes/tenant/tenant-plan.js.map +1 -1
- package/dist/routes/tenant/tenant-seats.d.ts +2 -4
- package/dist/routes/tenant/tenant-seats.js.map +1 -1
- package/dist/routes/tenant/workspaces.d.ts +3 -1
- package/dist/routes/tenant/workspaces.js +10 -4
- package/dist/routes/tenant/workspaces.js.map +1 -1
- package/dist/stripe/upcoming-invoice.d.ts +21 -0
- package/dist/stripe/upcoming-invoice.js +42 -0
- package/dist/stripe/upcoming-invoice.js.map +1 -0
- package/dist/stripe/webhook-dispatch.js +2 -0
- package/dist/stripe/webhook-dispatch.js.map +1 -1
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -1
- package/dist/upcoming-invoice/db/preview-cache.model.d.ts +38 -0
- package/dist/upcoming-invoice/db/preview-cache.model.js +57 -0
- package/dist/upcoming-invoice/db/preview-cache.model.js.map +1 -0
- package/dist/upcoming-invoice/db/preview-cache.table.d.ts +10 -0
- package/dist/upcoming-invoice/db/preview-cache.table.js +42 -0
- package/dist/upcoming-invoice/db/preview-cache.table.js.map +1 -0
- package/dist/upcoming-invoice/eligibility.d.ts +17 -0
- package/dist/upcoming-invoice/eligibility.js +52 -0
- package/dist/upcoming-invoice/eligibility.js.map +1 -0
- package/dist/upcoming-invoice/invalidation.d.ts +2 -0
- package/dist/upcoming-invoice/invalidation.js +39 -0
- package/dist/upcoming-invoice/invalidation.js.map +1 -0
- package/dist/upcoming-invoice/mapping.d.ts +8 -0
- package/dist/upcoming-invoice/mapping.js +82 -0
- package/dist/upcoming-invoice/mapping.js.map +1 -0
- package/dist/upcoming-invoice/outcomes.d.ts +13 -0
- package/dist/upcoming-invoice/outcomes.js +15 -0
- package/dist/upcoming-invoice/outcomes.js.map +1 -0
- package/dist/upcoming-invoice/preview.d.ts +3 -0
- package/dist/upcoming-invoice/preview.js +80 -0
- package/dist/upcoming-invoice/preview.js.map +1 -0
- package/dist/upcoming-invoice/pricing.d.ts +3 -0
- package/dist/upcoming-invoice/pricing.js +84 -0
- package/dist/upcoming-invoice/pricing.js.map +1 -0
- package/dist/upcoming-invoice/usage.d.ts +14 -0
- package/dist/upcoming-invoice/usage.js +53 -0
- package/dist/upcoming-invoice/usage.js.map +1 -0
- package/dist/utils/invoice-lines-type.d.ts +1 -1
- package/dist/workspaces/billing-identity.d.ts +19 -0
- package/dist/workspaces/billing-identity.js +51 -0
- package/dist/workspaces/billing-identity.js.map +1 -0
- package/dist/workspaces/default-plan.d.ts +5 -0
- package/dist/workspaces/default-plan.js +109 -0
- package/dist/workspaces/default-plan.js.map +1 -0
- package/dist/workspaces/index.d.ts +2 -0
- package/dist/workspaces/index.js +2 -0
- package/dist/workspaces/index.js.map +1 -1
- package/dist/workspaces/invitations.d.ts +26 -0
- package/dist/workspaces/invitations.js +112 -0
- package/dist/workspaces/invitations.js.map +1 -0
- package/dist/workspaces/provisioning.d.ts +3 -3
- package/dist/workspaces/provisioning.js +39 -11
- package/dist/workspaces/provisioning.js.map +1 -1
- package/frontend-vue/app/components/BillingIdentityFields.vue +179 -0
- package/frontend-vue/app/components/PastDueBanner.vue +81 -0
- package/frontend-vue/app/components/PaymentMethodCard.vue +94 -38
- package/frontend-vue/app/components/PlanComparisonModal.vue +62 -26
- package/frontend-vue/app/components/PlanUpgradeModal.vue +181 -0
- package/frontend-vue/app/components/PlatformNotes.vue +4 -0
- package/frontend-vue/app/components/RefundButton.vue +10 -1
- package/frontend-vue/app/components/Register.vue +49 -92
- package/frontend-vue/app/components/SeatQuotaBanner.vue +28 -0
- package/frontend-vue/app/components/TenantBillingForm.vue +52 -128
- package/frontend-vue/app/components/TenantPlanCard.vue +68 -10
- package/frontend-vue/app/components/UserSummaryCard.vue +12 -10
- package/frontend-vue/app/components/WorkspaceAdminCreateModal.vue +202 -0
- package/frontend-vue/app/components/WorkspaceCreateModal.vue +46 -9
- package/frontend-vue/app/components/WorkspaceInvitationLink.vue +106 -0
- package/frontend-vue/app/components/WorkspaceSummaryCard.vue +39 -15
- package/frontend-vue/app/components/WorkspaceSuspended.vue +3 -5
- package/frontend-vue/app/components/WorkspaceSwitcherWidget.vue +8 -5
- package/frontend-vue/app/composables/useBillingCountries.ts +25 -5
- package/frontend-vue/app/composables/useBillingIdentity.ts +141 -0
- package/frontend-vue/app/composables/useInvitationLink.ts +69 -0
- package/frontend-vue/app/composables/usePastDueBannerAction.ts +20 -0
- package/frontend-vue/app/composables/usePlanComparison.ts +15 -0
- package/frontend-vue/app/composables/usePlanFeatureFormat.ts +312 -20
- package/frontend-vue/app/composables/usePlanFeatureLabel.ts +24 -0
- package/frontend-vue/app/composables/useSaasRegistration.ts +110 -223
- package/frontend-vue/app/composables/useSeatQuota.ts +8 -0
- package/frontend-vue/app/composables/useStripePaymentElement.ts +3 -3
- package/frontend-vue/app/composables/useWorkspaceAccessCache.ts +29 -2
- package/frontend-vue/app/composables/useWorkspaceFinalization.ts +7 -32
- package/frontend-vue/app/custom-pages/auth/no-workspace.vue +49 -194
- package/frontend-vue/app/plugins/auth-links.ts +0 -4
- package/frontend-vue/app/types/runtime-config.ts +13 -0
- package/frontend-vue/dms.frontend.ts +10 -4
- package/frontend-vue/i18n/locales/saas-en-GB.json +161 -36
- package/frontend-vue/i18n/locales/saas-fr-FR.json +162 -37
- package/frontend-vue/package.json +0 -1
- package/frontend-vue/pnpm-lock.yaml +1 -125
- package/package.json +15 -15
- 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
|
|
6
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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 =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const isRegistrationClosed =
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (
|
|
203
|
-
errorMessage.value = nuxtApp.$i18n.t(
|
|
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
|
|
222
|
-
if (
|
|
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:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
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
|
-
<
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
sdk.registerAccessRedirect(
|
|
68
|
+
WORKSPACE_ACCESS_BLOCKED_CODE,
|
|
69
|
+
WORKSPACE_SUSPENDED_PATH,
|
|
70
|
+
)
|
|
65
71
|
},
|
|
66
72
|
}
|
|
67
73
|
|