Skip to content

Commit 337454f

Browse files
authored
feat: Add link to hosted sign-up page (#2045)
Resolves #2043 <!-- Fixes #issue_number --> ### Changes - Shows link to sign up in UI if `sign_up_url` is configured. - Expires settings in session storage (for now)
1 parent c072559 commit 337454f

File tree

10 files changed

+108
-21
lines changed

10 files changed

+108
-21
lines changed

backend/btrixcloud/main.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ class SettingsResponse(BaseModel):
115115

116116
billingEnabled: bool
117117

118+
signUpUrl: str = ""
119+
118120
salesEmail: str = ""
119121
supportEmail: str = ""
120122

@@ -143,6 +145,7 @@ def main() -> None:
143145
maxPagesPerCrawl=int(os.environ.get("MAX_PAGES_PER_CRAWL", 0)),
144146
maxScale=int(os.environ.get("MAX_CRAWL_SCALE", 3)),
145147
billingEnabled=is_bool(os.environ.get("BILLING_ENABLED")),
148+
signUpUrl=os.environ.get("SIGN_UP_URL", ""),
146149
salesEmail=os.environ.get("SALES_EMAIL", ""),
147150
supportEmail=os.environ.get("EMAIL_SUPPORT", ""),
148151
)

backend/test/test_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def test_api_settings():
4646
"maxScale": 3,
4747
"defaultPageLoadTimeSeconds": 120,
4848
"billingEnabled": True,
49+
"signUpUrl": "",
4950
"salesEmail": "",
5051
"supportEmail": "",
5152
}

chart/templates/configmap.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ data:
6262

6363
BILLING_ENABLED: "{{ .Values.billing_enabled }}"
6464

65+
SIGN_UP_URL: "{{ .Values.sign_up_url }}"
66+
6567
SALES_EMAIL: "{{ .Values.sales_email }}"
6668

6769
LOG_SENT_EMAILS: "{{ .Values.email.log_sent_emails }}"

chart/values.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ volume_storage_class:
6060
# if set, set the node selector 'nodeType' to this crawling pods
6161
# crawler_node_type:
6262

63+
# if set to "1", enables open registration
6364
registration_enabled: "0"
6465

65-
# if set, along with 'registration_enabled', will add registrated users to this org
66+
# if set, along with 'registration_enabled', will add registered users to this org
6667
# registration_org_id: ""
6768

6869
jwt_token_lifetime_minutes: 1440
@@ -113,6 +114,11 @@ profile_browser_idle_seconds: 60
113114
# set to true to enable subscriptions API and Billing tab
114115
billing_enabled: false
115116

117+
# set URL to external sign-up page
118+
# the internal sign-up page will take precedence if
119+
# `registration_enabled` is set to `"1"``
120+
sign_up_url: ""
121+
116122
# set e-mail to show for subscriptions related info
117123
sales_email: ""
118124

frontend/src/controllers/navigate.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,7 @@ export class NavigateController implements ReactiveController {
5151
this.host.dispatchEvent(evt);
5252
};
5353

54-
/**
55-
* Bind to anchor tag to prevent full page navigation
56-
* @example
57-
* ```ts
58-
* <a href="/" @click=${this.navigate.link}>go</a>
59-
* ```
60-
* @param event Click event
61-
*/
62-
link = (event: MouseEvent, _href?: string, resetScroll = true): void => {
54+
handleAnchorClick = (event: MouseEvent) => {
6355
if (
6456
// Detect keypress for opening in a new tab
6557
event.ctrlKey ||
@@ -69,11 +61,27 @@ export class NavigateController implements ReactiveController {
6961
// Account for event prevented on anchor tag
7062
event.defaultPrevented
7163
) {
72-
return;
64+
return false;
7365
}
7466

7567
event.preventDefault();
7668

69+
return true;
70+
};
71+
72+
/**
73+
* Bind to anchor tag to prevent full page navigation
74+
* @example
75+
* ```ts
76+
* <a href="/" @click=${this.navigate.link}>go</a>
77+
* ```
78+
* @param event Click event
79+
*/
80+
link = (event: MouseEvent, _href?: string, resetScroll = true): void => {
81+
if (!this.handleAnchorClick(event)) {
82+
return;
83+
}
84+
7785
const el = event.currentTarget as HTMLAnchorElement | null;
7886

7987
if (el?.ariaDisabled === "true") {

frontend/src/index.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export class App extends LiteElement {
196196
maxPagesPerCrawl: 0,
197197
maxScale: 0,
198198
billingEnabled: false,
199+
signUpUrl: "",
199200
salesEmail: "",
200201
supportEmail: "",
201202
};
@@ -450,11 +451,28 @@ export class App extends LiteElement {
450451
}
451452

452453
private renderSignUpLink() {
453-
if (!this.appState.settings) return;
454+
const { registrationEnabled, signUpUrl } = this.appState.settings || {};
454455

455-
if (this.appState.settings.registrationEnabled) {
456+
if (registrationEnabled) {
456457
return html`
457-
<sl-button variant="text" @click="${() => this.navigate("/sign-up")}">
458+
<sl-button
459+
href="/sign-up"
460+
size="small"
461+
@click="${(e: MouseEvent) => {
462+
if (!this.navHandleAnchorClick(e)) {
463+
return;
464+
}
465+
this.navigate("/sign-up");
466+
}}"
467+
>
468+
${msg("Sign Up")}
469+
</sl-button>
470+
`;
471+
}
472+
473+
if (signUpUrl) {
474+
return html`
475+
<sl-button href=${signUpUrl} size="small">
458476
${msg("Sign Up")}
459477
</sl-button>
460478
`;
@@ -947,7 +965,7 @@ export class App extends LiteElement {
947965
private clearUser() {
948966
this.authService.logout();
949967
this.authService = new AuthService();
950-
AppStateService.resetUser();
968+
AppStateService.resetAll();
951969
}
952970

953971
private showDialog(content: DialogContent) {

frontend/src/types/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type AppSettings = {
66
maxPagesPerCrawl: number;
77
maxScale: number;
88
billingEnabled: boolean;
9+
signUpUrl: string;
910
salesEmail: string;
1011
supportEmail: string;
1112
};

frontend/src/utils/LiteElement.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ export default class LiteElement extends LitElement {
5151
return this;
5252
}
5353

54+
/**
55+
* @deprecated New components should use NavigateController directly
56+
*/
57+
navHandleAnchorClick = (
58+
...args: Parameters<NavigateController["handleAnchorClick"]>
59+
) => this.navigateController.handleAnchorClick(...args);
60+
5461
/**
5562
* @deprecated New components should use NavigateController directly
5663
*/

frontend/src/utils/persist.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,54 @@ import type {
99

1010
const STORAGE_KEY_PREFIX = "btrix.app";
1111

12-
export const persist = (storage: Storage): StateOptions => ({
13-
set(stateVar: StateVar, v: string) {
14-
storage.setItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`, JSON.stringify(v));
12+
type ExpiringValue = {
13+
value: unknown;
14+
expiry: number;
15+
};
16+
17+
export const persist = (
18+
storage: Storage,
19+
ttlMinutes?: number,
20+
): StateOptions => ({
21+
set(stateVar: StateVar, v: string | null | undefined) {
22+
if (v === null || v === undefined) {
23+
storage.removeItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`);
24+
} else {
25+
storage.setItem(
26+
`${STORAGE_KEY_PREFIX}.${stateVar.key}`,
27+
JSON.stringify(
28+
ttlMinutes
29+
? ({
30+
value: v,
31+
expiry: Date.now() + ttlMinutes * 1000 * 60,
32+
} as ExpiringValue)
33+
: v,
34+
),
35+
);
36+
}
1537
stateVar.value = v;
1638
},
1739
get(stateVar: ReadonlyStateVar) {
1840
const stored = storage.getItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`);
19-
return stored ? (JSON.parse(stored) as unknown) : undefined;
41+
if (stored) {
42+
const data = JSON.parse(stored) as unknown;
43+
44+
if (
45+
data !== null &&
46+
typeof data === "object" &&
47+
Object.prototype.hasOwnProperty.call(data, "expiry") &&
48+
Object.prototype.hasOwnProperty.call(data, "value")
49+
) {
50+
if (Date.now() > (data as ExpiringValue).expiry) {
51+
storage.removeItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`);
52+
return undefined;
53+
}
54+
return (data as ExpiringValue).value;
55+
}
56+
57+
return data;
58+
}
59+
return undefined;
2060
},
2161
init(stateVar: ReadonlyStateVar, valueInit?: unknown) {
2262
return stateVar.options.get(stateVar) || valueInit;

frontend/src/utils/state.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export function makeAppStateService() {
2222

2323
@state()
2424
class AppState {
25-
@options(persist(window.localStorage))
25+
// @TODO Persist in local storage with expiry
26+
@options(persist(window.sessionStorage))
2627
settings: AppSettings | null = null;
2728

2829
@options(persist(window.sessionStorage))
@@ -140,7 +141,6 @@ export function makeAppStateService() {
140141
@unlock()
141142
resetAll() {
142143
appState.settings = null;
143-
appState.org = undefined;
144144
this._resetUser();
145145
}
146146

@@ -154,6 +154,7 @@ export function makeAppStateService() {
154154
appState.auth = null;
155155
appState.userInfo = null;
156156
appState.orgSlug = null;
157+
appState.org = undefined;
157158
}
158159
}
159160

0 commit comments

Comments
 (0)