parent
53beb84c01
commit
9c4bec1411
@ -72,7 +72,7 @@ class RequestVerify extends LitElement {
|
|||||||
this.isRequesting = false;
|
this.isRequesting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("bt-request-verify", RequestVerify);
|
customElements.define("btrix-request-verify", RequestVerify);
|
||||||
|
|
||||||
type FormContext = {
|
type FormContext = {
|
||||||
successMessage?: string;
|
successMessage?: string;
|
||||||
@ -224,7 +224,9 @@ export class AccountSettings extends LiteElement {
|
|||||||
})}</sl-tag
|
})}</sl-tag
|
||||||
>
|
>
|
||||||
|
|
||||||
<bt-request-verify email=${this.userInfo.email}></bt-request-verify>
|
<btrix-request-verify
|
||||||
|
email=${this.userInfo.email}
|
||||||
|
></btrix-request-verify>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
128
frontend/src/components/archive-invite-form.ts
Normal file
128
frontend/src/components/archive-invite-form.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { state, property } from "lit/decorators.js";
|
||||||
|
import { msg, localized, str } from "@lit/localize";
|
||||||
|
|
||||||
|
import type { AuthState } from "../utils/AuthService";
|
||||||
|
import LiteElement, { html } from "../utils/LiteElement";
|
||||||
|
import { AccessCode } from "../utils/archives";
|
||||||
|
|
||||||
|
@localized()
|
||||||
|
export class ArchiveInviteForm extends LiteElement {
|
||||||
|
@property({ type: String })
|
||||||
|
archiveId?: string;
|
||||||
|
|
||||||
|
@property({ type: Object })
|
||||||
|
authState?: AuthState;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private isSubmitting: boolean = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private serverError?: string;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let formError;
|
||||||
|
|
||||||
|
if (this.serverError) {
|
||||||
|
formError = html`
|
||||||
|
<div class="mb-5">
|
||||||
|
<btrix-alert id="formError" type="danger"
|
||||||
|
>${this.serverError}</btrix-alert
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<sl-form
|
||||||
|
class="max-w-md"
|
||||||
|
@sl-submit=${this.onSubmit}
|
||||||
|
aria-describedby="formError"
|
||||||
|
>
|
||||||
|
<div class="mb-5">
|
||||||
|
<sl-input
|
||||||
|
id="inviteEmail"
|
||||||
|
name="inviteEmail"
|
||||||
|
type="email"
|
||||||
|
label=${msg("Email")}
|
||||||
|
placeholder=${msg("team-member@email.com", {
|
||||||
|
desc: "Placeholder text for email to invite",
|
||||||
|
})}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</sl-input>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5">
|
||||||
|
<sl-radio-group label="Select an option">
|
||||||
|
<sl-radio name="role" value=${AccessCode.owner}>
|
||||||
|
${msg("Admin")}
|
||||||
|
<span class="text-gray-500">
|
||||||
|
- ${msg("Can start & configure crawls and invite others")}</span
|
||||||
|
>
|
||||||
|
</sl-radio>
|
||||||
|
<sl-radio name="role" value=${AccessCode.viewer} checked>
|
||||||
|
${msg("Viewer")}
|
||||||
|
<span class="text-gray-500"> - ${msg("Can view crawls")}</span>
|
||||||
|
</sl-radio>
|
||||||
|
</sl-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${formError}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<sl-button
|
||||||
|
type="primary"
|
||||||
|
submit
|
||||||
|
?loading=${this.isSubmitting}
|
||||||
|
?disabled=${this.isSubmitting}
|
||||||
|
>${msg("Invite")}</sl-button
|
||||||
|
>
|
||||||
|
<sl-button
|
||||||
|
type="text"
|
||||||
|
@click=${() => this.dispatchEvent(new CustomEvent("cancel"))}
|
||||||
|
>${msg("Cancel")}</sl-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</sl-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onSubmit(event: { detail: { formData: FormData } }) {
|
||||||
|
if (!this.authState) return;
|
||||||
|
|
||||||
|
this.isSubmitting = true;
|
||||||
|
|
||||||
|
const { formData } = event.detail;
|
||||||
|
const inviteEmail = formData.get("inviteEmail") as string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.apiFetch(
|
||||||
|
`/archives/${this.archiveId}/invite`,
|
||||||
|
this.authState,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: inviteEmail,
|
||||||
|
role: Number(formData.get("role")),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("success", {
|
||||||
|
detail: {
|
||||||
|
inviteEmail,
|
||||||
|
isExistingUser: data.invited === "existing_user",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.isApiError) {
|
||||||
|
this.serverError = e?.message;
|
||||||
|
} else {
|
||||||
|
this.serverError = msg("Something unexpected went wrong");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,11 +5,17 @@ import("./locale-picker").then(({ LocalePicker }) => {
|
|||||||
import("./account-settings").then(({ AccountSettings }) => {
|
import("./account-settings").then(({ AccountSettings }) => {
|
||||||
customElements.define("btrix-account-settings", AccountSettings);
|
customElements.define("btrix-account-settings", AccountSettings);
|
||||||
});
|
});
|
||||||
|
import("./archive-invite-form").then(({ ArchiveInviteForm }) => {
|
||||||
|
customElements.define("btrix-archive-invite-form", ArchiveInviteForm);
|
||||||
|
});
|
||||||
import("./invite-form").then(({ InviteForm }) => {
|
import("./invite-form").then(({ InviteForm }) => {
|
||||||
customElements.define("btrix-invite-form", InviteForm);
|
customElements.define("btrix-invite-form", InviteForm);
|
||||||
});
|
});
|
||||||
import("./sign-up-form").then(({ SignUpForm }) => {
|
import("./sign-up-form").then(({ SignUpForm }) => {
|
||||||
customElements.define("btrix-sign-up-form", SignUpForm);
|
customElements.define("btrix-sign-up-form", SignUpForm);
|
||||||
});
|
});
|
||||||
|
import("./not-found").then(({ NotFound }) => {
|
||||||
|
customElements.define("btrix-not-found", NotFound);
|
||||||
|
});
|
||||||
|
|
||||||
customElements.define("btrix-alert", Alert);
|
customElements.define("btrix-alert", Alert);
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import { state, property } from "lit/decorators.js";
|
import { state, property } from "lit/decorators.js";
|
||||||
import { msg, localized, str } from "@lit/localize";
|
import { msg, localized } from "@lit/localize";
|
||||||
|
|
||||||
import type { AuthState } from "../utils/AuthService";
|
import type { AuthState } from "../utils/AuthService";
|
||||||
import LiteElement, { html } from "../utils/LiteElement";
|
import LiteElement, { html } from "../utils/LiteElement";
|
||||||
import { AccessCode } from "../utils/archives";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @event success
|
||||||
|
*/
|
||||||
@localized()
|
@localized()
|
||||||
export class InviteForm extends LiteElement {
|
export class InviteForm extends LiteElement {
|
||||||
@property({ type: String })
|
|
||||||
archiveId?: string;
|
|
||||||
|
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
authState?: AuthState;
|
authState?: AuthState;
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ export class InviteForm extends LiteElement {
|
|||||||
return html`
|
return html`
|
||||||
<sl-form
|
<sl-form
|
||||||
class="max-w-md"
|
class="max-w-md"
|
||||||
@sl-submit=${this.onSubmitInvite}
|
@sl-submit=${this.onSubmit}
|
||||||
aria-describedby="formError"
|
aria-describedby="formError"
|
||||||
>
|
>
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
@ -43,26 +42,14 @@ export class InviteForm extends LiteElement {
|
|||||||
id="inviteEmail"
|
id="inviteEmail"
|
||||||
name="inviteEmail"
|
name="inviteEmail"
|
||||||
type="email"
|
type="email"
|
||||||
label="${msg("Email")}"
|
label=${msg("Email")}
|
||||||
placeholder="team-member@email.com"
|
placeholder=${msg("person@email.com", {
|
||||||
|
desc: "Placeholder text for email to invite",
|
||||||
|
})}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
</sl-input>
|
</sl-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-5">
|
|
||||||
<sl-radio-group label="Select an option">
|
|
||||||
<sl-radio name="role" value=${AccessCode.owner}>
|
|
||||||
${msg("Admin")}
|
|
||||||
<span class="text-gray-500">
|
|
||||||
- ${msg("Can start & configure crawls and invite others")}</span
|
|
||||||
>
|
|
||||||
</sl-radio>
|
|
||||||
<sl-radio name="role" value=${AccessCode.viewer} checked>
|
|
||||||
${msg("Viewer")}
|
|
||||||
<span class="text-gray-500"> - ${msg("Can view crawls")}</span>
|
|
||||||
</sl-radio>
|
|
||||||
</sl-radio-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${formError}
|
${formError}
|
||||||
|
|
||||||
@ -74,17 +61,12 @@ export class InviteForm extends LiteElement {
|
|||||||
?disabled=${this.isSubmitting}
|
?disabled=${this.isSubmitting}
|
||||||
>${msg("Invite")}</sl-button
|
>${msg("Invite")}</sl-button
|
||||||
>
|
>
|
||||||
<sl-button
|
|
||||||
type="text"
|
|
||||||
@click=${() => this.dispatchEvent(new CustomEvent("cancel"))}
|
|
||||||
>${msg("Cancel")}</sl-button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</sl-form>
|
</sl-form>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSubmitInvite(event: { detail: { formData: FormData } }) {
|
async onSubmit(event: { detail: { formData: FormData } }) {
|
||||||
if (!this.authState) return;
|
if (!this.authState) return;
|
||||||
|
|
||||||
this.isSubmitting = true;
|
this.isSubmitting = true;
|
||||||
@ -94,7 +76,8 @@ export class InviteForm extends LiteElement {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.apiFetch(
|
const data = await this.apiFetch(
|
||||||
`/archives/${this.archiveId}/invite`,
|
// TODO actual path
|
||||||
|
`/invite`,
|
||||||
this.authState,
|
this.authState,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
14
frontend/src/components/not-found.ts
Normal file
14
frontend/src/components/not-found.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { LitElement, html } from "lit";
|
||||||
|
import { msg, localized } from "@lit/localize";
|
||||||
|
|
||||||
|
@localized()
|
||||||
|
export class NotFound extends LitElement {
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div class="text-2xl text-gray-400">${msg("Page not found")}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ describe("browsertrix-app", () => {
|
|||||||
email: "test-user@example.com",
|
email: "test-user@example.com",
|
||||||
name: "Test User",
|
name: "Test User",
|
||||||
is_verified: false,
|
is_verified: false,
|
||||||
|
is_superuser: false,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -55,6 +56,7 @@ describe("browsertrix-app", () => {
|
|||||||
email: "test-user@example.com",
|
email: "test-user@example.com",
|
||||||
name: "Test User",
|
name: "Test User",
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
|
isAdmin: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,30 +14,12 @@ import type { ViewState, NavigateEvent } from "./utils/APIRouter";
|
|||||||
import type { CurrentUser } from "./types/user";
|
import type { CurrentUser } from "./types/user";
|
||||||
import type { AuthState } from "./utils/AuthService";
|
import type { AuthState } from "./utils/AuthService";
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
|
import { ROUTES, DASHBOARD_ROUTE } from "./routes";
|
||||||
import "./shoelace";
|
import "./shoelace";
|
||||||
import "./components";
|
import "./components";
|
||||||
import "./pages";
|
import "./pages";
|
||||||
|
|
||||||
const REGISTRATION_ENABLED = process.env.REGISTRATION_ENABLED === "true";
|
const REGISTRATION_ENABLED = process.env.REGISTRATION_ENABLED === "true";
|
||||||
const ROUTES = {
|
|
||||||
home: "/",
|
|
||||||
join: "/join/:token?email",
|
|
||||||
verify: "/verify?token",
|
|
||||||
login: "/log-in",
|
|
||||||
forgotPassword: "/log-in/forgot-password",
|
|
||||||
resetPassword: "/reset-password?token",
|
|
||||||
myAccount: "/my-account",
|
|
||||||
accountSettings: "/account/settings",
|
|
||||||
archives: "/archives",
|
|
||||||
archive: "/archives/:id/:tab",
|
|
||||||
archiveAddMember: "/archives/:id/:tab/add-member",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
if (REGISTRATION_ENABLED) {
|
|
||||||
(ROUTES as any).signUp = "/sign-up";
|
|
||||||
}
|
|
||||||
|
|
||||||
const DASHBOARD_ROUTE = ROUTES.archives;
|
|
||||||
|
|
||||||
type DialogContent = {
|
type DialogContent = {
|
||||||
label?: TemplateResult | string;
|
label?: TemplateResult | string;
|
||||||
@ -126,6 +108,7 @@ export class App extends LiteElement {
|
|||||||
email: data.email,
|
email: data.email,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
isVerified: data.is_verified,
|
isVerified: data.is_verified,
|
||||||
|
isAdmin: data.is_superuser,
|
||||||
};
|
};
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err?.message === "Unauthorized") {
|
if (err?.message === "Unauthorized") {
|
||||||
@ -159,6 +142,10 @@ export class App extends LiteElement {
|
|||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
|
.uppercase {
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
${theme}
|
${theme}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -249,23 +236,43 @@ export class App extends LiteElement {
|
|||||||
${navLink({
|
${navLink({
|
||||||
activeRoutes: ["archives", "archive"],
|
activeRoutes: ["archives", "archive"],
|
||||||
href: DASHBOARD_ROUTE,
|
href: DASHBOARD_ROUTE,
|
||||||
label: "Archives",
|
label: msg("Archives"),
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
${this.userInfo?.isAdmin
|
||||||
|
? html` <span class="uppercase text-sm font-medium"
|
||||||
|
>${msg("Admin", {
|
||||||
|
desc: "Heading for links to administrative pages",
|
||||||
|
})}</span
|
||||||
|
>
|
||||||
|
<ul class="flex md:flex-col">
|
||||||
|
${navLink({
|
||||||
|
// activeRoutes: ["users", "usersInvite"],
|
||||||
|
activeRoutes: ["usersInvite"],
|
||||||
|
href: ROUTES.usersInvite,
|
||||||
|
label: msg("Invite Users"),
|
||||||
|
})}
|
||||||
|
</ul>`
|
||||||
|
: ""}
|
||||||
</nav>
|
</nav>
|
||||||
<div class="p-4 md:p-8 flex-1">${template}</div>
|
<div class="p-4 md:p-8 flex-1">${template}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
switch (this.viewState.route) {
|
switch (this.viewState.route) {
|
||||||
case "signUp":
|
case "signUp": {
|
||||||
return html`<btrix-sign-up
|
if (REGISTRATION_ENABLED) {
|
||||||
class="w-full md:bg-gray-100 flex items-center justify-center"
|
return html`<btrix-sign-up
|
||||||
@navigate="${this.onNavigateTo}"
|
class="w-full md:bg-gray-100 flex items-center justify-center"
|
||||||
@logged-in="${this.onLoggedIn}"
|
@navigate="${this.onNavigateTo}"
|
||||||
@log-out="${this.onLogOut}"
|
@logged-in="${this.onLoggedIn}"
|
||||||
.authState="${this.authService.authState}"
|
@log-out="${this.onLogOut}"
|
||||||
></btrix-sign-up>`;
|
.authState="${this.authService.authState}"
|
||||||
|
></btrix-sign-up>`;
|
||||||
|
} else {
|
||||||
|
return this.renderNotFoundPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case "verify":
|
case "verify":
|
||||||
return html`<btrix-verify
|
return html`<btrix-verify
|
||||||
@ -359,11 +366,31 @@ export class App extends LiteElement {
|
|||||||
tab="${this.viewState.tab || "running"}"
|
tab="${this.viewState.tab || "running"}"
|
||||||
></btrix-archive>`);
|
></btrix-archive>`);
|
||||||
|
|
||||||
|
case "usersInvite": {
|
||||||
|
if (this.userInfo?.isAdmin) {
|
||||||
|
return appLayout(html`<btrix-users-invite
|
||||||
|
class="w-full"
|
||||||
|
@navigate="${this.onNavigateTo}"
|
||||||
|
@need-login="${this.onNeedLogin}"
|
||||||
|
.authState="${this.authService.authState}"
|
||||||
|
.userInfo="${this.userInfo}"
|
||||||
|
></btrix-users-invite>`);
|
||||||
|
} else {
|
||||||
|
return this.renderNotFoundPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return html`<div>Not Found!</div>`;
|
return this.renderNotFoundPage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderNotFoundPage() {
|
||||||
|
return html`<btrix-not-found
|
||||||
|
class="w-full md:bg-gray-100 flex items-center justify-center"
|
||||||
|
></btrix-not-found>`;
|
||||||
|
}
|
||||||
|
|
||||||
onLogOut(event: CustomEvent<{ redirect?: boolean } | null>) {
|
onLogOut(event: CustomEvent<{ redirect?: boolean } | null>) {
|
||||||
const detail = event.detail || {};
|
const detail = event.detail || {};
|
||||||
const redirect = detail.redirect !== false;
|
const redirect = detail.redirect !== false;
|
||||||
|
|||||||
@ -182,12 +182,12 @@ export class Archive extends LiteElement {
|
|||||||
|
|
||||||
<div class="mt-3 border rounded-lg p-4 md:p-8 md:pt-6">
|
<div class="mt-3 border rounded-lg p-4 md:p-8 md:pt-6">
|
||||||
<h2 class="text-lg font-medium mb-4">${msg("Add New Member")}</h2>
|
<h2 class="text-lg font-medium mb-4">${msg("Add New Member")}</h2>
|
||||||
<btrix-invite-form
|
<btrix-archive-invite-form
|
||||||
@success=${this.onInviteSuccess}
|
@success=${this.onInviteSuccess}
|
||||||
@cancel=${() => this.navTo(`/archives/${this.archiveId}/members`)}
|
@cancel=${() => this.navTo(`/archives/${this.archiveId}/members`)}
|
||||||
.authState=${this.authState}
|
.authState=${this.authState}
|
||||||
.archiveId=${this.archiveId}
|
.archiveId=${this.archiveId}
|
||||||
></btrix-invite-form>
|
></btrix-archive-invite-form>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,9 @@ export class Archives extends LiteElement {
|
|||||||
${this.userInfo &&
|
${this.userInfo &&
|
||||||
archive.users &&
|
archive.users &&
|
||||||
isOwner(archive.users[this.userInfo.id].role)
|
isOwner(archive.users[this.userInfo.id].role)
|
||||||
? html`<sl-tag size="small" type="primary">Owner</sl-tag>`
|
? html`<sl-tag size="small" type="primary"
|
||||||
|
>${msg("Owner")}</sl-tag
|
||||||
|
>`
|
||||||
: ""}
|
: ""}
|
||||||
</li>
|
</li>
|
||||||
`
|
`
|
||||||
|
|||||||
@ -21,3 +21,8 @@ import(/* webpackChunkName: "reset-password" */ "./reset-password").then(
|
|||||||
customElements.define("btrix-reset-password", ResetPassword);
|
customElements.define("btrix-reset-password", ResetPassword);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
import(/* webpackChunkName: "users-invite" */ "./users-invite").then(
|
||||||
|
({ UsersInvite }) => {
|
||||||
|
customElements.define("btrix-users-invite", UsersInvite);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { state, property } from "lit/decorators.js";
|
|||||||
import { msg, localized, str } from "@lit/localize";
|
import { msg, localized, str } from "@lit/localize";
|
||||||
import { createMachine, interpret, assign } from "@xstate/fsm";
|
import { createMachine, interpret, assign } from "@xstate/fsm";
|
||||||
|
|
||||||
|
import { DASHBOARD_ROUTE } from "../routes";
|
||||||
import type { AuthState } from "../utils/AuthService";
|
import type { AuthState } from "../utils/AuthService";
|
||||||
import LiteElement, { html } from "../utils/LiteElement";
|
import LiteElement, { html } from "../utils/LiteElement";
|
||||||
import type { LoggedInEvent } from "../utils/AuthService";
|
import type { LoggedInEvent } from "../utils/AuthService";
|
||||||
@ -238,7 +239,7 @@ export class Join extends LiteElement {
|
|||||||
try {
|
try {
|
||||||
await this.apiFetch(`/invite/accept/${this.token}`, this.authState);
|
await this.apiFetch(`/invite/accept/${this.token}`, this.authState);
|
||||||
|
|
||||||
this.navTo("/archives");
|
this.navTo(DASHBOARD_ROUTE);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.isApiError && err.message === "Invalid Invite Code") {
|
if (err.isApiError && err.message === "Invalid Invite Code") {
|
||||||
this.joinStateService.send({
|
this.joinStateService.send({
|
||||||
|
|||||||
52
frontend/src/pages/users-invite.ts
Normal file
52
frontend/src/pages/users-invite.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { state, property } from "lit/decorators.js";
|
||||||
|
import { msg, localized, str } from "@lit/localize";
|
||||||
|
|
||||||
|
import type { AuthState } from "../utils/AuthService";
|
||||||
|
import LiteElement, { html } from "../utils/LiteElement";
|
||||||
|
import { needLogin } from "../utils/auth";
|
||||||
|
|
||||||
|
@needLogin
|
||||||
|
@localized()
|
||||||
|
export class UsersInvite extends LiteElement {
|
||||||
|
@property({ type: Object })
|
||||||
|
authState?: AuthState;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private invitedEmail?: string;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let successMessage;
|
||||||
|
|
||||||
|
if (this.invitedEmail) {
|
||||||
|
successMessage = html`
|
||||||
|
<div>
|
||||||
|
<btrix-alert type="success"
|
||||||
|
>${msg(str`Sent invite to ${this.invitedEmail}`)}</btrix-alert
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return html`<div class="grid gap-4">
|
||||||
|
<header class="text-xl font-bold">
|
||||||
|
<h1 class="inline-block mr-2">${msg("Users")}</h1>
|
||||||
|
<sl-tag class="uppercase" type="primary" size="small"
|
||||||
|
>${msg("admin")}</sl-tag
|
||||||
|
>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
${successMessage}
|
||||||
|
|
||||||
|
<main class="border rounded-lg p-4 md:p-8 md:pt-6">
|
||||||
|
<h2 class="text-lg font-medium mb-4">${msg("Invite Users")}</h2>
|
||||||
|
<btrix-invite-form
|
||||||
|
.authState=${this.authState}
|
||||||
|
@success=${this.onSuccess}
|
||||||
|
></btrix-invite-form>
|
||||||
|
</main>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSuccess(event: CustomEvent<{ inviteEmail: string }>) {
|
||||||
|
this.invitedEmail = event.detail.inviteEmail;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
frontend/src/routes.ts
Normal file
18
frontend/src/routes.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const ROUTES = {
|
||||||
|
home: "/",
|
||||||
|
join: "/join/:token?email",
|
||||||
|
signUp: "/sign-up",
|
||||||
|
verify: "/verify?token",
|
||||||
|
login: "/log-in",
|
||||||
|
forgotPassword: "/log-in/forgot-password",
|
||||||
|
resetPassword: "/reset-password?token",
|
||||||
|
myAccount: "/my-account",
|
||||||
|
accountSettings: "/account/settings",
|
||||||
|
archives: "/archives",
|
||||||
|
archive: "/archives/:id/:tab",
|
||||||
|
archiveAddMember: "/archives/:id/:tab/add-member",
|
||||||
|
users: "/users",
|
||||||
|
usersInvite: "/users/invite",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const DASHBOARD_ROUTE = ROUTES.archives;
|
||||||
@ -3,4 +3,5 @@ export type CurrentUser = {
|
|||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
isVerified: boolean;
|
isVerified: boolean;
|
||||||
|
isAdmin: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
import { DASHBOARD_ROUTE } from "../routes";
|
||||||
import LiteElement from "../utils/LiteElement";
|
import LiteElement from "../utils/LiteElement";
|
||||||
import type { AuthState } from "../utils/AuthService";
|
import type { AuthState } from "../utils/AuthService";
|
||||||
|
import type { CurrentUser } from "../types/user";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block rendering and dispatch event if user is not logged in
|
* Block rendering and dispatch event if user is not logged in
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user