Skip to content

Commit 53391bf

Browse files
authored
Merge pull request #147 from cryptomator/feature/hub-managed-early-access
Add Hub early access and CE routing in billing
2 parents cb240a5 + cca0546 commit 53391bf

6 files changed

Lines changed: 96 additions & 15 deletions

File tree

assets/js/hubsubscription.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,21 @@ class HubSubscription {
1212
constructor(form, subscriptionData, searchParams) {
1313
this._form = form;
1414
this._subscriptionData = subscriptionData;
15-
this._subscriptionData.hubId = searchParams.get('hub_id');
16-
let encodedReturnUrl = searchParams.get('return_url');
17-
if (encodedReturnUrl) {
18-
this._subscriptionData.returnUrl = decodeURIComponent(encodedReturnUrl);
15+
let fragmentParams = new URLSearchParams(location.hash.substring(1));
16+
this._subscriptionData.oldLicense = fragmentParams.get('oldLicense');
17+
if (this._subscriptionData.oldLicense) {
18+
try {
19+
let base64 = this._subscriptionData.oldLicense.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
20+
this._subscriptionData.hubId = JSON.parse(atob(base64)).jti;
21+
} catch (e) {
22+
console.error('Failed to parse hub token:', e);
23+
this._subscriptionData.oldLicense = null;
24+
}
25+
}
26+
this._subscriptionData.hubId = this._subscriptionData.hubId ?? searchParams.get('hub_id');
27+
let returnUrl = fragmentParams.get('returnUrl') ?? searchParams.get('return_url');
28+
if (returnUrl) {
29+
this._subscriptionData.returnUrl = returnUrl;
1930
}
2031
this._subscriptionData.session = searchParams.get('session');
2132
if (this._subscriptionData.hubId && this._subscriptionData.hubId.length > 0 && this._subscriptionData.returnUrl && this._subscriptionData.returnUrl.length > 0) {
@@ -57,7 +68,7 @@ class HubSubscription {
5768
}
5869

5970
onLoadSubscriptionSucceeded(data) {
60-
this._subscriptionData.verificationToken = data.token;
71+
this._subscriptionData.oldLicense = data.token;
6172
this._subscriptionData.details = data.subscription;
6273
if (data.subscription.quantity) {
6374
this._subscriptionData.quantity = data.subscription.quantity;
@@ -313,7 +324,7 @@ class HubSubscription {
313324

314325
onPostSucceeded(data) {
315326
this._subscriptionData.state = 'EXISTING_CUSTOMER';
316-
this._subscriptionData.verificationToken = data.token;
327+
this._subscriptionData.oldLicense = data.token;
317328
this._subscriptionData.details = data.subscription;
318329
this._subscriptionData.session = data.session;
319330
var searchParams = new URLSearchParams(window.location.search)
@@ -480,7 +491,7 @@ class HubSubscription {
480491
}
481492

482493
onPutSucceeded(data, shouldOpenReturnUrl) {
483-
this._subscriptionData.verificationToken = data.token;
494+
this._subscriptionData.oldLicense = data.token;
484495
this._subscriptionData.details = data.subscription;
485496
this._subscriptionData.errorMessage = '';
486497
this._subscriptionData.inProgress = false;
@@ -505,7 +516,7 @@ class HubSubscription {
505516
url: REFRESH_LICENSE_URL,
506517
type: 'POST',
507518
data: {
508-
token: this._subscriptionData.verificationToken,
519+
token: this._subscriptionData.oldLicense,
509520
captcha: this._subscriptionData.captcha
510521
}
511522
}).done(token => {

i18n/de.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
translation: "Ich bin damit einverstanden, Neuigkeiten von Cryptomator Hub zu erhalten, und akzeptiere die <a class=\"text-link\" href=\"/de/privacy/\">Datenschutzerklärung</a>."
3535
- id: accept_hub_newsletter_optional
3636
translation: "Ich bin damit einverstanden, Neuigkeiten von Cryptomator Hub per E-Mail zu erhalten (optional)."
37+
- id: accept_hub_managed_early_access_optional
38+
translation: "<span class=\"inline-flex px-2 py-0.5 rounded-full text-xs font-medium uppercase bg-primary-l2 text-dark\">BETA</span> <a class=\"text-link\" href=\"https://docs.cryptomator.org/hub/early-access/\" target=\"_blank\">Early Access von Cryptomator Hub 1.5.0</a> mit brandneuen Features und einer verlängerten 100-Tage-Testphase testen (optional)."
3739
- id: accept_privacy
3840
translation: "Ich akzeptiere die <a class=\"text-link\" href=\"/de/privacy/\">Datenschutzerklärung</a>."
3941
- id: accept_privacy_implicitly
@@ -613,6 +615,19 @@
613615
- id: hub_billing_checkout_standard_submit
614616
translation: "Zur Zahlung"
615617

618+
- id: hub_billing_checkout_community_title
619+
translation: "Community"
620+
- id: hub_billing_checkout_community_statement
621+
translation: "Kostenlos"
622+
- id: hub_billing_checkout_community_description
623+
translation: "bis zu 5 Sitze inklusive"
624+
- id: hub_billing_checkout_community_subtitle
625+
translation: "Kostenlos starten"
626+
- id: hub_billing_checkout_community_cta
627+
translation: "Deine Hub-Instanz unterstützt die Community Edition. Registriere dich für eine kostenlose Lizenz mit bis zu 5 Sitzen."
628+
- id: hub_billing_checkout_community_action
629+
translation: "Kostenlos registrieren"
630+
616631
- id: hub_billing_checkout_enterprise_title
617632
translation: "Enterprise"
618633
- id: hub_billing_checkout_enterprise_statement

i18n/en.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
translation: "I agree to get updates from Cryptomator Hub and accept the <a class=\"text-link\" href=\"/privacy/\" target=\"_blank\">Privacy Policy</a>."
3535
- id: accept_hub_newsletter_optional
3636
translation: "I agree to get updates from Cryptomator Hub via email (optional)."
37+
- id: accept_hub_managed_early_access_optional
38+
translation: "<span class=\"inline-flex px-2 py-0.5 rounded-full text-xs font-medium uppercase bg-primary-l2 text-dark\">BETA</span> Try out <a class=\"text-link\" href=\"https://docs.cryptomator.org/hub/early-access/\" target=\"_blank\">early access of Cryptomator Hub 1.5.0</a> with brand new features and an extended 100-day trial (optional)."
3739
- id: accept_privacy
3840
translation: "I accept the <a class=\"text-link\" href=\"/privacy/\" target=\"_blank\">Privacy Policy</a>."
3941
- id: accept_privacy_implicitly
@@ -613,6 +615,19 @@
613615
- id: hub_billing_checkout_standard_submit
614616
translation: "Checkout"
615617

618+
- id: hub_billing_checkout_community_title
619+
translation: "Community"
620+
- id: hub_billing_checkout_community_statement
621+
translation: "Free"
622+
- id: hub_billing_checkout_community_description
623+
translation: "up to 5 seats included"
624+
- id: hub_billing_checkout_community_subtitle
625+
translation: "Get Started for Free"
626+
- id: hub_billing_checkout_community_cta
627+
translation: "Your Hub instance supports the Community Edition. Register for a free license with up to 5 seats."
628+
- id: hub_billing_checkout_community_action
629+
translation: "Register for Free"
630+
616631
- id: hub_billing_checkout_enterprise_title
617632
translation: "Enterprise"
618633
- id: hub_billing_checkout_enterprise_statement

layouts/hub-billing/single.html

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{{ end }}
44
{{ define "main" }}
55
<div class="container pt-12 pb-24">
6-
<form x-data="{subscriptionData: {state: 'MISSING_PARAMS', captcha: null, hubId: null, returnUrl: null, session: null, customBilling: null, billingInterval: 'yearly', savingsPercent: null, errorMessage: '', inProgress: false, restartModal: {open: false, nextPayment: null}, changeSeatsModal: {open: false, confirmation: false, immediatePayment: null}, token: null, details: null, quantity: 5, email: '', needsTokenRefresh: false, shouldTransferToHub: false}, acceptTerms: false, hubSubscription: null, captchaState: null}" x-init="hubSubscription = new HubSubscription($refs.form, subscriptionData, new URLSearchParams(location.search))" x-ref="form" @submit.prevent="hubSubscription.createSession(); $refs.captcha.reset()">
6+
<form x-data="{subscriptionData: {state: 'MISSING_PARAMS', captcha: null, hubId: null, returnUrl: null, session: null, oldLicense: null, customBilling: null, billingInterval: 'yearly', savingsPercent: null, errorMessage: '', inProgress: false, restartModal: {open: false, nextPayment: null}, changeSeatsModal: {open: false, confirmation: false, immediatePayment: null}, token: null, details: null, quantity: 5, email: '', needsTokenRefresh: false, shouldTransferToHub: false}, acceptTerms: false, hubSubscription: null, captchaState: null}" x-init="hubSubscription = new HubSubscription($refs.form, subscriptionData, new URLSearchParams(location.search))" x-ref="form" @submit.prevent="hubSubscription.createSession(); $refs.captcha.reset()">
77
<template x-if="subscriptionData.state == 'MISSING_PARAMS'">
88
<div class="text-center max-w-xl mx-auto">
99
<h3 class="font-headline text-xl md:text-2xl leading-relaxed mb-4">
@@ -314,7 +314,8 @@ <h3 class="font-headline text-xl md:text-2xl leading-relaxed mb-4" id="change-se
314314
<h1 class="font-h1 mb-8">{{ .Title }}</h1>
315315
<p class="lead">{{ i18n "hub_billing_checkout_description" . }}</p>
316316
</header>
317-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-12">
317+
<div class="grid grid-cols-1 gap-4 mb-12" :class="subscriptionData.oldLicense && !subscriptionData.customBilling?.managed ? 'md:grid-cols-3' : 'md:grid-cols-2'">
318+
<div x-show="subscriptionData.oldLicense && !subscriptionData.customBilling?.managed" class="hidden md:block"></div>
318319
<div class="flex justify-center">
319320
<button class="w-1/2 py-2 border border-gray-300 rounded-l bg-gray-300 text-gray-700 hover:border-gray-400 hover:bg-gray-400 focus:bg-gray-400 focus:border-secondary" :class="{'border-gray-400 bg-gray-400 text-gray-800 font-medium': audience === 'business'}" @click.prevent="audience = 'business'">
320321
<i class="fa-building" :class="{'fas': audience === 'business', 'far': audience !== 'business'}"></i>
@@ -326,6 +327,40 @@ <h1 class="font-h1 mb-8">{{ .Title }}</h1>
326327
</button>
327328
</div>
328329
<div class="hidden md:block"></div>
330+
<div x-show="subscriptionData.oldLicense && !subscriptionData.customBilling?.managed" class="flex flex-col white-box">
331+
<div class="p-4 border-b border-primary">
332+
<h2 class="inline-flex px-4 py-1 rounded-full text-sm font-medium tracking-wide uppercase bg-primary-l2 text-dark mb-4">
333+
{{ i18n "hub_billing_checkout_community_title" . }}
334+
</h2>
335+
<div x-show="!subscriptionData.customBilling?.override?.prices" class="lg:h-[22px]"></div>
336+
<p class="text-5xl font-bold">
337+
{{ i18n "hub_billing_checkout_community_statement" . }}
338+
</p>
339+
<p class="text-lg text-gray-500 mb-4">
340+
{{ i18n "hub_billing_checkout_community_description" . }}
341+
</p>
342+
</div>
343+
<div class="grow">
344+
<div class="flex flex-col items-center text-center h-full px-4 py-8">
345+
<h3 class="font-h3 mb-4 md:mb-0">
346+
{{ i18n "hub_billing_checkout_community_subtitle" . }}
347+
</h3>
348+
<div class="mb-4 md:my-auto lg:max-w-sm">
349+
<div class="fa-stack text-5xl text-primary-l2 mb-4">
350+
<i class="fa-solid fa-circle fa-stack-2x"></i>
351+
<i class="fa-solid fa-people-group fa-stack-1x text-primary"></i>
352+
</div>
353+
<p class="font-p">
354+
{{ i18n "hub_billing_checkout_community_cta" . }}
355+
</p>
356+
</div>
357+
<a :href="`{{ "hub/register" | relLangURL }}#oldLicense=${encodeURIComponent(subscriptionData.oldLicense)}&returnUrl=${encodeURIComponent(subscriptionData.returnUrl)}`" role="button" class="btn btn-primary w-full">
358+
<i class="fa-solid fa-user-plus"></i>
359+
{{ i18n "hub_billing_checkout_community_action" . }}
360+
</a>
361+
</div>
362+
</div>
363+
</div>
329364
<div class="white-box">
330365
<div class="p-4 border-b border-primary">
331366
<h2 class="inline-flex px-4 py-1 rounded-full text-sm font-medium tracking-wide uppercase bg-primary-l2 text-dark mb-4">
@@ -407,7 +442,7 @@ <h2 class="inline-flex px-4 py-1 rounded-full text-sm font-medium tracking-wide
407442
</p>
408443
<p x-show="subscriptionData.customBilling?.managed" class="font-p mb-4">{{ partial "checkbox.html" (dict "context" . "alpineVariable" "acceptTerms" "label" (i18n "accept_hub_managed_terms_and_privacy" | safeHTML)) }}</p>
409444
<p x-show="!subscriptionData.customBilling?.managed" class="font-p mb-4">{{ partial "checkbox.html" (dict "context" . "alpineVariable" "acceptTerms" "label" (i18n "accept_terms_and_privacy" | safeHTML)) }}</p>
410-
<button :disabled="subscriptionData.inProgress || !acceptTerms" @click.prevent="hubSubscription.checkout('{{ .Site.Language.Lang }}')" type="button" class="btn btn-primary w-full lg:w-1/2">
445+
<button :disabled="subscriptionData.inProgress || !acceptTerms" @click.prevent="hubSubscription.checkout('{{ .Site.Language.Lang }}')" type="button" class="btn btn-primary w-full" :class="!(subscriptionData.oldLicense && !subscriptionData.customBilling?.managed) && 'lg:w-1/2'">
411446
<i :class="{'fa-shopping-cart': !subscriptionData.inProgress, 'fa-check': !subscriptionData.inProgress, 'fa-spinner fa-spin': subscriptionData.inProgress}" class="fa-solid" aria-hidden="true"></i>
412447
{{ i18n "hub_billing_checkout_standard_submit" . }}
413448
</button>
@@ -442,7 +477,7 @@ <h3 class="font-h3 mb-4 md:mb-0">
442477
{{ i18n "hub_billing_checkout_enterprise_cta" . }}
443478
</p>
444479
</div>
445-
<a href="mailto:hub@cryptomator.org" role="button" class="btn btn-primary w-full lg:w-1/2">
480+
<a href="mailto:hub@cryptomator.org" role="button" class="btn btn-primary w-full" :class="!(subscriptionData.oldLicense && !subscriptionData.customBilling?.managed) && 'lg:w-1/2'">
446481
<i class="fa-solid fa-envelope"></i>
447482
{{ i18n "hub_billing_checkout_enterprise_action" . }}
448483
</a>

layouts/hub-managed/single.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{{ partial "altcha-css.html" . }}
33
{{ end }}
44
{{ define "main" }}
5-
<section x-data="{steps: ['{{ i18n "hub_managed_step_1_nav_title" }}', '{{ i18n "hub_managed_step_2_nav_title" }}', '{{ i18n "hub_managed_step_3_nav_title" }}', '{{ i18n "hub_managed_step_4_nav_title" }}'], feedbackData: {currentStep: 0, success: false, inProgress: false, errorMessage: ''}, submitData: {captcha: null, email: '', subdomain: '', quantity: null, message: null, acceptNewsletter: false}, acceptTerms: false, lowQuantityModalIsOpen: false, hubManaged: null, captchaState: null}" x-init="hubManaged = new HubManaged($refs.form, feedbackData, submitData)" class="container py-12">
5+
<section x-data="{steps: ['{{ i18n "hub_managed_step_1_nav_title" }}', '{{ i18n "hub_managed_step_2_nav_title" }}', '{{ i18n "hub_managed_step_3_nav_title" }}', '{{ i18n "hub_managed_step_4_nav_title" }}'], feedbackData: {currentStep: 0, success: false, inProgress: false, errorMessage: ''}, submitData: {captcha: null, email: '', subdomain: '', quantity: null, message: null, acceptNewsletter: false, earlyaccess: false}, acceptTerms: false, lowQuantityModalIsOpen: false, hubManaged: null, captchaState: null}" x-init="hubManaged = new HubManaged($refs.form, feedbackData, submitData)" class="container py-12">
66
<header class="mb-6">
77
<h1 class="font-h1 mb-8">{{ .Title }}</h1>
88
<p class="lead">{{ i18n "hub_managed_description" }}</p>
@@ -181,6 +181,9 @@ <h2 class="font-h2 mb-6">
181181
<p class="font-p text-sm mb-2">
182182
{{ partial "checkbox.html" (dict "context" . "alpineVariable" "submitData.acceptNewsletter" "label" (i18n "accept_hub_newsletter_optional")) }}
183183
</p>
184+
<p class="font-p text-sm mb-2">
185+
{{ partial "checkbox.html" (dict "context" . "alpineVariable" "submitData.earlyaccess" "label" (i18n "accept_hub_managed_early_access_optional")) }}
186+
</p>
184187
<div class="mt-auto">
185188
<p :class="{'hidden': !feedbackData.errorMessage}" class="text-sm text-red-600 mb-2" x-text="feedbackData.errorMessage"></p>
186189
<button :disabled="feedbackData.inProgress || !acceptTerms || captchaState == 'verifying'" type="submit" class="btn btn-primary w-full md:w-64" data-umami-event="hub-managed-form" x-cloak>

layouts/partials/checkbox.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{{ $unique_id := printf "id-%s" (delimit (shuffle (seq 1 9)) "") }}
2-
<input class="mr-1 rounded-sm text-primary focus:ring-0 focus:ring-offset-0 focus:border-secondary" type="checkbox" id="{{ $unique_id }}" x-model="{{ .alpineVariable }}" />
3-
<label for="{{ $unique_id }}">{{ .label }}</label>
2+
{{/* pl-6 + -indent-6: wrapped lines align with text, not checkbox. [&_*]:indent-0: reset for child elements (e.g. pills). */}}
3+
<label class="inline-block pl-6 -indent-6 [&_*]:indent-0" for="{{ $unique_id }}">
4+
<input class="mr-2 rounded-sm text-primary focus:ring-0 focus:ring-offset-0 focus:border-secondary" type="checkbox" id="{{ $unique_id }}" x-model="{{ .alpineVariable }}" />{{ .label | safeHTML }}
5+
</label>

0 commit comments

Comments
 (0)