feat: Add link to hosted sign-up page (#2045)
Resolves https://github.com/webrecorder/browsertrix/issues/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)
This commit is contained in:
parent
c0725599b2
commit
337454f8c9
@ -115,6 +115,8 @@ class SettingsResponse(BaseModel):
|
|||||||
|
|
||||||
billingEnabled: bool
|
billingEnabled: bool
|
||||||
|
|
||||||
|
signUpUrl: str = ""
|
||||||
|
|
||||||
salesEmail: str = ""
|
salesEmail: str = ""
|
||||||
supportEmail: str = ""
|
supportEmail: str = ""
|
||||||
|
|
||||||
@ -143,6 +145,7 @@ def main() -> None:
|
|||||||
maxPagesPerCrawl=int(os.environ.get("MAX_PAGES_PER_CRAWL", 0)),
|
maxPagesPerCrawl=int(os.environ.get("MAX_PAGES_PER_CRAWL", 0)),
|
||||||
maxScale=int(os.environ.get("MAX_CRAWL_SCALE", 3)),
|
maxScale=int(os.environ.get("MAX_CRAWL_SCALE", 3)),
|
||||||
billingEnabled=is_bool(os.environ.get("BILLING_ENABLED")),
|
billingEnabled=is_bool(os.environ.get("BILLING_ENABLED")),
|
||||||
|
signUpUrl=os.environ.get("SIGN_UP_URL", ""),
|
||||||
salesEmail=os.environ.get("SALES_EMAIL", ""),
|
salesEmail=os.environ.get("SALES_EMAIL", ""),
|
||||||
supportEmail=os.environ.get("EMAIL_SUPPORT", ""),
|
supportEmail=os.environ.get("EMAIL_SUPPORT", ""),
|
||||||
)
|
)
|
||||||
|
@ -46,6 +46,7 @@ def test_api_settings():
|
|||||||
"maxScale": 3,
|
"maxScale": 3,
|
||||||
"defaultPageLoadTimeSeconds": 120,
|
"defaultPageLoadTimeSeconds": 120,
|
||||||
"billingEnabled": True,
|
"billingEnabled": True,
|
||||||
|
"signUpUrl": "",
|
||||||
"salesEmail": "",
|
"salesEmail": "",
|
||||||
"supportEmail": "",
|
"supportEmail": "",
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,8 @@ data:
|
|||||||
|
|
||||||
BILLING_ENABLED: "{{ .Values.billing_enabled }}"
|
BILLING_ENABLED: "{{ .Values.billing_enabled }}"
|
||||||
|
|
||||||
|
SIGN_UP_URL: "{{ .Values.sign_up_url }}"
|
||||||
|
|
||||||
SALES_EMAIL: "{{ .Values.sales_email }}"
|
SALES_EMAIL: "{{ .Values.sales_email }}"
|
||||||
|
|
||||||
LOG_SENT_EMAILS: "{{ .Values.email.log_sent_emails }}"
|
LOG_SENT_EMAILS: "{{ .Values.email.log_sent_emails }}"
|
||||||
|
@ -60,9 +60,10 @@ volume_storage_class:
|
|||||||
# if set, set the node selector 'nodeType' to this crawling pods
|
# if set, set the node selector 'nodeType' to this crawling pods
|
||||||
# crawler_node_type:
|
# crawler_node_type:
|
||||||
|
|
||||||
|
# if set to "1", enables open registration
|
||||||
registration_enabled: "0"
|
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: ""
|
# registration_org_id: ""
|
||||||
|
|
||||||
jwt_token_lifetime_minutes: 1440
|
jwt_token_lifetime_minutes: 1440
|
||||||
@ -113,6 +114,11 @@ profile_browser_idle_seconds: 60
|
|||||||
# set to true to enable subscriptions API and Billing tab
|
# set to true to enable subscriptions API and Billing tab
|
||||||
billing_enabled: false
|
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
|
# set e-mail to show for subscriptions related info
|
||||||
sales_email: ""
|
sales_email: ""
|
||||||
|
|
||||||
|
@ -51,15 +51,7 @@ export class NavigateController implements ReactiveController {
|
|||||||
this.host.dispatchEvent(evt);
|
this.host.dispatchEvent(evt);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
handleAnchorClick = (event: MouseEvent) => {
|
||||||
* Bind to anchor tag to prevent full page navigation
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* <a href="/" @click=${this.navigate.link}>go</a>
|
|
||||||
* ```
|
|
||||||
* @param event Click event
|
|
||||||
*/
|
|
||||||
link = (event: MouseEvent, _href?: string, resetScroll = true): void => {
|
|
||||||
if (
|
if (
|
||||||
// Detect keypress for opening in a new tab
|
// Detect keypress for opening in a new tab
|
||||||
event.ctrlKey ||
|
event.ctrlKey ||
|
||||||
@ -69,11 +61,27 @@ export class NavigateController implements ReactiveController {
|
|||||||
// Account for event prevented on anchor tag
|
// Account for event prevented on anchor tag
|
||||||
event.defaultPrevented
|
event.defaultPrevented
|
||||||
) {
|
) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind to anchor tag to prevent full page navigation
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* <a href="/" @click=${this.navigate.link}>go</a>
|
||||||
|
* ```
|
||||||
|
* @param event Click event
|
||||||
|
*/
|
||||||
|
link = (event: MouseEvent, _href?: string, resetScroll = true): void => {
|
||||||
|
if (!this.handleAnchorClick(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const el = event.currentTarget as HTMLAnchorElement | null;
|
const el = event.currentTarget as HTMLAnchorElement | null;
|
||||||
|
|
||||||
if (el?.ariaDisabled === "true") {
|
if (el?.ariaDisabled === "true") {
|
||||||
|
@ -196,6 +196,7 @@ export class App extends LiteElement {
|
|||||||
maxPagesPerCrawl: 0,
|
maxPagesPerCrawl: 0,
|
||||||
maxScale: 0,
|
maxScale: 0,
|
||||||
billingEnabled: false,
|
billingEnabled: false,
|
||||||
|
signUpUrl: "",
|
||||||
salesEmail: "",
|
salesEmail: "",
|
||||||
supportEmail: "",
|
supportEmail: "",
|
||||||
};
|
};
|
||||||
@ -450,11 +451,28 @@ export class App extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderSignUpLink() {
|
private renderSignUpLink() {
|
||||||
if (!this.appState.settings) return;
|
const { registrationEnabled, signUpUrl } = this.appState.settings || {};
|
||||||
|
|
||||||
if (this.appState.settings.registrationEnabled) {
|
if (registrationEnabled) {
|
||||||
return html`
|
return html`
|
||||||
<sl-button variant="text" @click="${() => this.navigate("/sign-up")}">
|
<sl-button
|
||||||
|
href="/sign-up"
|
||||||
|
size="small"
|
||||||
|
@click="${(e: MouseEvent) => {
|
||||||
|
if (!this.navHandleAnchorClick(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.navigate("/sign-up");
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
${msg("Sign Up")}
|
||||||
|
</sl-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signUpUrl) {
|
||||||
|
return html`
|
||||||
|
<sl-button href=${signUpUrl} size="small">
|
||||||
${msg("Sign Up")}
|
${msg("Sign Up")}
|
||||||
</sl-button>
|
</sl-button>
|
||||||
`;
|
`;
|
||||||
@ -947,7 +965,7 @@ export class App extends LiteElement {
|
|||||||
private clearUser() {
|
private clearUser() {
|
||||||
this.authService.logout();
|
this.authService.logout();
|
||||||
this.authService = new AuthService();
|
this.authService = new AuthService();
|
||||||
AppStateService.resetUser();
|
AppStateService.resetAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private showDialog(content: DialogContent) {
|
private showDialog(content: DialogContent) {
|
||||||
|
@ -6,6 +6,7 @@ export type AppSettings = {
|
|||||||
maxPagesPerCrawl: number;
|
maxPagesPerCrawl: number;
|
||||||
maxScale: number;
|
maxScale: number;
|
||||||
billingEnabled: boolean;
|
billingEnabled: boolean;
|
||||||
|
signUpUrl: string;
|
||||||
salesEmail: string;
|
salesEmail: string;
|
||||||
supportEmail: string;
|
supportEmail: string;
|
||||||
};
|
};
|
||||||
|
@ -51,6 +51,13 @@ export default class LiteElement extends LitElement {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated New components should use NavigateController directly
|
||||||
|
*/
|
||||||
|
navHandleAnchorClick = (
|
||||||
|
...args: Parameters<NavigateController["handleAnchorClick"]>
|
||||||
|
) => this.navigateController.handleAnchorClick(...args);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated New components should use NavigateController directly
|
* @deprecated New components should use NavigateController directly
|
||||||
*/
|
*/
|
||||||
|
@ -9,14 +9,54 @@ import type {
|
|||||||
|
|
||||||
const STORAGE_KEY_PREFIX = "btrix.app";
|
const STORAGE_KEY_PREFIX = "btrix.app";
|
||||||
|
|
||||||
export const persist = (storage: Storage): StateOptions => ({
|
type ExpiringValue = {
|
||||||
set(stateVar: StateVar, v: string) {
|
value: unknown;
|
||||||
storage.setItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`, JSON.stringify(v));
|
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;
|
stateVar.value = v;
|
||||||
},
|
},
|
||||||
get(stateVar: ReadonlyStateVar) {
|
get(stateVar: ReadonlyStateVar) {
|
||||||
const stored = storage.getItem(`${STORAGE_KEY_PREFIX}.${stateVar.key}`);
|
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) {
|
init(stateVar: ReadonlyStateVar, valueInit?: unknown) {
|
||||||
return stateVar.options.get(stateVar) || valueInit;
|
return stateVar.options.get(stateVar) || valueInit;
|
||||||
|
@ -22,7 +22,8 @@ export function makeAppStateService() {
|
|||||||
|
|
||||||
@state()
|
@state()
|
||||||
class AppState {
|
class AppState {
|
||||||
@options(persist(window.localStorage))
|
// @TODO Persist in local storage with expiry
|
||||||
|
@options(persist(window.sessionStorage))
|
||||||
settings: AppSettings | null = null;
|
settings: AppSettings | null = null;
|
||||||
|
|
||||||
@options(persist(window.sessionStorage))
|
@options(persist(window.sessionStorage))
|
||||||
@ -140,7 +141,6 @@ export function makeAppStateService() {
|
|||||||
@unlock()
|
@unlock()
|
||||||
resetAll() {
|
resetAll() {
|
||||||
appState.settings = null;
|
appState.settings = null;
|
||||||
appState.org = undefined;
|
|
||||||
this._resetUser();
|
this._resetUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +154,7 @@ export function makeAppStateService() {
|
|||||||
appState.auth = null;
|
appState.auth = null;
|
||||||
appState.userInfo = null;
|
appState.userInfo = null;
|
||||||
appState.orgSlug = null;
|
appState.orgSlug = null;
|
||||||
|
appState.org = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user