diff --git a/backend/btrixcloud/main.py b/backend/btrixcloud/main.py
index d161d75d..3678f49e 100644
--- a/backend/btrixcloud/main.py
+++ b/backend/btrixcloud/main.py
@@ -115,6 +115,8 @@ class SettingsResponse(BaseModel):
billingEnabled: bool
+ signUpUrl: str = ""
+
salesEmail: str = ""
supportEmail: str = ""
@@ -143,6 +145,7 @@ def main() -> None:
maxPagesPerCrawl=int(os.environ.get("MAX_PAGES_PER_CRAWL", 0)),
maxScale=int(os.environ.get("MAX_CRAWL_SCALE", 3)),
billingEnabled=is_bool(os.environ.get("BILLING_ENABLED")),
+ signUpUrl=os.environ.get("SIGN_UP_URL", ""),
salesEmail=os.environ.get("SALES_EMAIL", ""),
supportEmail=os.environ.get("EMAIL_SUPPORT", ""),
)
diff --git a/backend/test/test_api.py b/backend/test/test_api.py
index 5cc0fd74..439bfbff 100644
--- a/backend/test/test_api.py
+++ b/backend/test/test_api.py
@@ -46,6 +46,7 @@ def test_api_settings():
"maxScale": 3,
"defaultPageLoadTimeSeconds": 120,
"billingEnabled": True,
+ "signUpUrl": "",
"salesEmail": "",
"supportEmail": "",
}
diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml
index 3f8e2c6c..fa1c7db6 100644
--- a/chart/templates/configmap.yaml
+++ b/chart/templates/configmap.yaml
@@ -62,6 +62,8 @@ data:
BILLING_ENABLED: "{{ .Values.billing_enabled }}"
+ SIGN_UP_URL: "{{ .Values.sign_up_url }}"
+
SALES_EMAIL: "{{ .Values.sales_email }}"
LOG_SENT_EMAILS: "{{ .Values.email.log_sent_emails }}"
diff --git a/chart/values.yaml b/chart/values.yaml
index 5e7406bc..5966aee7 100644
--- a/chart/values.yaml
+++ b/chart/values.yaml
@@ -60,9 +60,10 @@ volume_storage_class:
# if set, set the node selector 'nodeType' to this crawling pods
# crawler_node_type:
+# if set to "1", enables open registration
registration_enabled: "0"
-# if set, along with 'registration_enabled', will add registrated users to this org
+# if set, along with 'registration_enabled', will add registered users to this org
# registration_org_id: ""
jwt_token_lifetime_minutes: 1440
@@ -113,6 +114,11 @@ profile_browser_idle_seconds: 60
# set to true to enable subscriptions API and Billing tab
billing_enabled: false
+# set URL to external sign-up page
+# the internal sign-up page will take precedence if
+# `registration_enabled` is set to `"1"``
+sign_up_url: ""
+
# set e-mail to show for subscriptions related info
sales_email: ""
diff --git a/frontend/src/controllers/navigate.ts b/frontend/src/controllers/navigate.ts
index 5fa64e43..c826b284 100644
--- a/frontend/src/controllers/navigate.ts
+++ b/frontend/src/controllers/navigate.ts
@@ -51,15 +51,7 @@ export class NavigateController implements ReactiveController {
this.host.dispatchEvent(evt);
};
- /**
- * Bind to anchor tag to prevent full page navigation
- * @example
- * ```ts
- * go
- * ```
- * @param event Click event
- */
- link = (event: MouseEvent, _href?: string, resetScroll = true): void => {
+ handleAnchorClick = (event: MouseEvent) => {
if (
// Detect keypress for opening in a new tab
event.ctrlKey ||
@@ -69,11 +61,27 @@ export class NavigateController implements ReactiveController {
// Account for event prevented on anchor tag
event.defaultPrevented
) {
- return;
+ return false;
}
event.preventDefault();
+ return true;
+ };
+
+ /**
+ * Bind to anchor tag to prevent full page navigation
+ * @example
+ * ```ts
+ * go
+ * ```
+ * @param event Click event
+ */
+ link = (event: MouseEvent, _href?: string, resetScroll = true): void => {
+ if (!this.handleAnchorClick(event)) {
+ return;
+ }
+
const el = event.currentTarget as HTMLAnchorElement | null;
if (el?.ariaDisabled === "true") {
diff --git a/frontend/src/index.ts b/frontend/src/index.ts
index 46db02bf..9fa8a970 100644
--- a/frontend/src/index.ts
+++ b/frontend/src/index.ts
@@ -196,6 +196,7 @@ export class App extends LiteElement {
maxPagesPerCrawl: 0,
maxScale: 0,
billingEnabled: false,
+ signUpUrl: "",
salesEmail: "",
supportEmail: "",
};
@@ -450,11 +451,28 @@ export class App extends LiteElement {
}
private renderSignUpLink() {
- if (!this.appState.settings) return;
+ const { registrationEnabled, signUpUrl } = this.appState.settings || {};
- if (this.appState.settings.registrationEnabled) {
+ if (registrationEnabled) {
return html`
- this.navigate("/sign-up")}">
+ {
+ if (!this.navHandleAnchorClick(e)) {
+ return;
+ }
+ this.navigate("/sign-up");
+ }}"
+ >
+ ${msg("Sign Up")}
+
+ `;
+ }
+
+ if (signUpUrl) {
+ return html`
+
${msg("Sign Up")}
`;
@@ -947,7 +965,7 @@ export class App extends LiteElement {
private clearUser() {
this.authService.logout();
this.authService = new AuthService();
- AppStateService.resetUser();
+ AppStateService.resetAll();
}
private showDialog(content: DialogContent) {
diff --git a/frontend/src/types/app.ts b/frontend/src/types/app.ts
index 5844ed02..3078afea 100644
--- a/frontend/src/types/app.ts
+++ b/frontend/src/types/app.ts
@@ -6,6 +6,7 @@ export type AppSettings = {
maxPagesPerCrawl: number;
maxScale: number;
billingEnabled: boolean;
+ signUpUrl: string;
salesEmail: string;
supportEmail: string;
};
diff --git a/frontend/src/utils/LiteElement.ts b/frontend/src/utils/LiteElement.ts
index 87a1cd25..c79168f1 100644
--- a/frontend/src/utils/LiteElement.ts
+++ b/frontend/src/utils/LiteElement.ts
@@ -51,6 +51,13 @@ export default class LiteElement extends LitElement {
return this;
}
+ /**
+ * @deprecated New components should use NavigateController directly
+ */
+ navHandleAnchorClick = (
+ ...args: Parameters
+ ) => this.navigateController.handleAnchorClick(...args);
+
/**
* @deprecated New components should use NavigateController directly
*/
diff --git a/frontend/src/utils/persist.ts b/frontend/src/utils/persist.ts
index e126499f..bd8ee0bc 100644
--- a/frontend/src/utils/persist.ts
+++ b/frontend/src/utils/persist.ts
@@ -9,14 +9,54 @@ import type {
const STORAGE_KEY_PREFIX = "btrix.app";
-export const persist = (storage: Storage): StateOptions => ({
- set(stateVar: StateVar, v: string) {
- storage.setItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`, JSON.stringify(v));
+type ExpiringValue = {
+ value: unknown;
+ expiry: number;
+};
+
+export const persist = (
+ storage: Storage,
+ ttlMinutes?: number,
+): StateOptions => ({
+ set(stateVar: StateVar, v: string | null | undefined) {
+ if (v === null || v === undefined) {
+ storage.removeItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`);
+ } else {
+ storage.setItem(
+ `${STORAGE_KEY_PREFIX}.${stateVar.key}`,
+ JSON.stringify(
+ ttlMinutes
+ ? ({
+ value: v,
+ expiry: Date.now() + ttlMinutes * 1000 * 60,
+ } as ExpiringValue)
+ : v,
+ ),
+ );
+ }
stateVar.value = v;
},
get(stateVar: ReadonlyStateVar) {
const stored = storage.getItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`);
- return stored ? (JSON.parse(stored) as unknown) : undefined;
+ if (stored) {
+ const data = JSON.parse(stored) as unknown;
+
+ if (
+ data !== null &&
+ typeof data === "object" &&
+ Object.prototype.hasOwnProperty.call(data, "expiry") &&
+ Object.prototype.hasOwnProperty.call(data, "value")
+ ) {
+ if (Date.now() > (data as ExpiringValue).expiry) {
+ storage.removeItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`);
+ return undefined;
+ }
+ return (data as ExpiringValue).value;
+ }
+
+ return data;
+ }
+ return undefined;
},
init(stateVar: ReadonlyStateVar, valueInit?: unknown) {
return stateVar.options.get(stateVar) || valueInit;
diff --git a/frontend/src/utils/state.ts b/frontend/src/utils/state.ts
index 070c68c2..8c954936 100644
--- a/frontend/src/utils/state.ts
+++ b/frontend/src/utils/state.ts
@@ -22,7 +22,8 @@ export function makeAppStateService() {
@state()
class AppState {
- @options(persist(window.localStorage))
+ // @TODO Persist in local storage with expiry
+ @options(persist(window.sessionStorage))
settings: AppSettings | null = null;
@options(persist(window.sessionStorage))
@@ -140,7 +141,6 @@ export function makeAppStateService() {
@unlock()
resetAll() {
appState.settings = null;
- appState.org = undefined;
this._resetUser();
}
@@ -154,6 +154,7 @@ export function makeAppStateService() {
appState.auth = null;
appState.userInfo = null;
appState.orgSlug = null;
+ appState.org = undefined;
}
}