browsertrix/frontend/src/pages/org/browser-profiles-new.ts
Emma Segal-Grossman b15c5ccddd
ESLint & Typescript fixes (#1407)
Closes #1405

- Properly uses `typescript-eslint`: we were missing the preset from it,
so some of the default `eslint` rules (that don't properly work with
typescript) were being applied and causing false positives
- I also moved the `eslint` config into its own file, and enabled
`typescript-eslint`'s type-awareness, so that we can enable more
type-aware rules in the future if we like
- Adds `ts-lit-plugin` to the typescript config, which _hopefully_ will
allow us to catch issues during build (in CI)
- It looks like `ts-lit-plugin` is sort of abandonware at the moment,
and unfortunately _doesn't_ actually work for this purpose right now,
but the lit team is working on a replacement here:
https://www.npmjs.com/package/@lit-labs/analyzer
- Adds `fork-ts-checker-webpack-plugin`, which allows the typescript
checking process to be run on a separate forked thread in Webpack, which
can help speed up builds & checking
- Enables incremental type checking for better speed
- Fixes a whole bunch of `eslint`-auto-fixable issues (unused imports
and variables, some type issues, etc)
- Fixes a bunch of `lit-analyzer` issues (mostly attribute naming, some
type issues as well)
- Fixes various other type issues:
- Improves type safety in a bunch of places, notably anywhere `apiFetch`
and `APIPaginatedList` are used
  - Removes some `any`s
2023-11-24 12:32:53 -05:00

240 lines
6.7 KiB
TypeScript

import { state, property, customElement } from "lit/decorators.js";
import { msg, localized, str } from "@lit/localize";
import { ifDefined } from "lit/directives/if-defined.js";
import type { AuthState } from "../../utils/AuthService";
import LiteElement, { html } from "../../utils/LiteElement";
/**
* Usage:
* ```ts
* <btrix-browser-profiles-new
* authState=${authState}
* orgId=${orgId}
* browserId=${browserId}
* ></btrix-browser-profiles-new>
* ```
*/
@localized()
@customElement("btrix-browser-profiles-new")
export class BrowserProfilesNew extends LiteElement {
@property({ type: Object })
authState!: AuthState;
@property({ type: String })
orgId!: string;
@property({ type: String })
browserId!: string;
@state()
private isSubmitting = false;
@state()
private isDialogVisible = false;
// URL params can be used to pass name and description
// base ID determines whether this is an edit/extension
@state()
private params: Partial<{
name: string;
description: string;
navigateUrl: string;
profileId: string | null;
}> = {};
firstUpdated() {
const params = new URLSearchParams(window.location.search);
const profileId = params.get("profileId");
this.params = {
name: params.get("name") || "",
description: params.get("description") || "",
navigateUrl: params.get("navigateUrl") || "",
profileId: profileId || null,
};
}
render() {
return html`
<div class="mb-7">
<a
class="text-neutral-500 hover:text-neutral-600 text-sm font-medium"
href=${this.params.profileId
? `${this.orgBasePath}/browser-profiles/profile/${this.params.profileId}`
: `${this.orgBasePath}/browser-profiles`}
@click=${this.navLink}
>
<sl-icon
name="arrow-left"
class="inline-block align-middle"
></sl-icon>
<span class="inline-block align-middle"
>${this.params.profileId
? msg("Back to Profile")
: msg("Back to Browser Profiles")}</span
>
</a>
</div>
${this.params.profileId
? html`
<div class="mb-2">
<btrix-alert class="text-sm" variant="info"
>${msg(
html`Extending <strong>${this.params.name}</strong>`
)}</btrix-alert
>
</div>
`
: ""}
<div class="h-screen flex flex-col">
<div
class="flex-0 flex items-center justify-between mb-3 p-2 bg-slate-100 rounded-lg"
>
<p class="text-sm text-slate-600 mr-3 p-1">
${msg(
"Interact with the browsing tool to record your browser profile. It is highly recommended to create dedicated accounts to use when crawling. For details refer to the best practices on the "
)}
<a
class="text-primary hover:text-indigo-400"
href="https://docs.browsertrix.cloud/user-guide/browser-profiles/"
target="_blank"
>${msg("browser profiles documentation page.")}</a
>
</p>
<sl-button
variant="primary"
size="small"
@click=${() => (this.isDialogVisible = true)}
>
${msg("Finish Browsing")}
</sl-button>
</div>
<btrix-profile-browser
class="flex-1 border rounded-lg overflow-hidden"
.authState=${this.authState}
orgId=${this.orgId}
browserId=${this.browserId}
initialNavigateUrl=${ifDefined(this.params.navigateUrl)}
></btrix-profile-browser>
</div>
<btrix-dialog
.label=${msg(str`Save Browser Profile`)}
.open=${this.isDialogVisible}
@sl-request-close=${() => (this.isDialogVisible = false)}
>
${this.renderForm()}
</btrix-dialog>
`;
}
private renderForm() {
return html`<form @submit=${this.onSubmit}>
<div class="grid gap-5">
<sl-input
name="name"
label=${msg("Name")}
placeholder=${msg("Example (example.com)", {
desc: "Example browser profile name",
})}
autocomplete="off"
value=${this.params.profileId && this.params.name
? msg(str`${this.params.name} Copy`)
: this.params.name || msg("My Profile")}
required
></sl-input>
<sl-textarea
name="description"
label=${msg("Description")}
helpText=${msg("Optional profile description")}
placeholder=${msg("Example (example.com) login profile", {
desc: "Example browser profile name",
})}
rows="2"
autocomplete="off"
value=${this.params.description || ""}
></sl-textarea>
<div class="flex justify-between">
<sl-button
variant="default"
size="small"
@click=${() => (this.isDialogVisible = false)}
>
${msg("Back")}
</sl-button>
<sl-button
variant="primary"
size="small"
type="submit"
?disabled=${this.isSubmitting}
?loading=${this.isSubmitting}
>
${msg("Save Profile")}
</sl-button>
</div>
</div>
</form>`;
}
private async onSubmit(event: SubmitEvent) {
event.preventDefault();
this.isSubmitting = true;
const formData = new FormData(event.target as HTMLFormElement);
const params = {
browserid: this.browserId,
name: formData.get("name"),
description: formData.get("description"),
};
try {
const data = await this.apiFetch<{ id: string }>(
`/orgs/${this.orgId}/profiles`,
this.authState!,
{
method: "POST",
body: JSON.stringify(params),
}
);
this.notify({
message: msg("Successfully created browser profile."),
variant: "success",
icon: "check2-circle",
});
this.navTo(`${this.orgBasePath}/browser-profiles/profile/${data.id}`);
} catch (e: any) {
this.isSubmitting = false;
let message = msg("Sorry, couldn't create browser profile at this time.");
if (e.isApiError && e.statusCode === 403) {
if (e.details === "storage_quota_reached") {
message = msg(
"Your org does not have enough storage to save this browser profile."
);
} else {
message = msg(
"You do not have permission to create browser profiles."
);
}
}
this.notify({
message: message,
variant: "danger",
icon: "exclamation-octagon",
});
}
}
}