1
0
Fork 0
mirror of synced 2024-06-02 18:44:54 +12:00
budibase/packages/builder/src/pages/builder/portal/manage/users/index.svelte
2022-07-21 09:52:01 +01:00

349 lines
8.6 KiB
Svelte

<script>
import {
Heading,
Body,
Button,
ButtonGroup,
Table,
Layout,
Modal,
ModalContent,
Icon,
notifications,
Pagination,
Search,
Label,
} from "@budibase/bbui"
import AddUserModal from "./_components/AddUserModal.svelte"
import { users, groups, auth } from "stores/portal"
import { onMount } from "svelte"
import DeleteRowsButton from "components/backend/DataTable/buttons/DeleteRowsButton.svelte"
import GroupsTableRenderer from "./_components/GroupsTableRenderer.svelte"
import AppsTableRenderer from "./_components/AppsTableRenderer.svelte"
import NameTableRenderer from "./_components/NameTableRenderer.svelte"
import RoleTableRenderer from "./_components/RoleTableRenderer.svelte"
import { goto } from "@roxi/routify"
import OnboardingTypeModal from "./_components/OnboardingTypeModal.svelte"
import PasswordModal from "./_components/PasswordModal.svelte"
import ImportUsersModal from "./_components/ImportUsersModal.svelte"
import { createPaginationStore } from "helpers/pagination"
import { Constants } from "@budibase/frontend-core"
const accessTypes = [
{
icon: "User",
description: "App user - Only has access to published apps",
},
{
icon: "Hammer",
description: "Developer - Access to the app builder",
},
{
icon: "Draw",
description: "Admin - Full access",
},
]
//let email
let enrichedUsers = []
let createUserModal,
inviteConfirmationModal,
onboardingTypeModal,
passwordModal,
importUsersModal
let pageInfo = createPaginationStore()
let prevEmail = undefined,
searchEmail = undefined
let selectedRows = []
let customRenderers = [
{ column: "userGroups", component: GroupsTableRenderer },
{ column: "apps", component: AppsTableRenderer },
{ column: "name", component: NameTableRenderer },
{ column: "role", component: RoleTableRenderer },
]
$: hasGroupsLicense = $auth.user?.license.features.includes(
Constants.Features.USER_GROUPS
)
$: schema = {
name: {},
email: {},
role: {
noPropagation: true,
sortable: false,
},
...(hasGroupsLicense && {
userGroups: { sortable: false, displayName: "User groups" },
}),
apps: { width: "120px" },
settings: {
sortable: false,
width: "60px",
displayName: "",
align: "Right",
},
}
$: userData = []
$: page = $pageInfo.page
$: fetchUsers(page, searchEmail)
$: {
enrichedUsers = $users.data?.map(user => {
let userGroups = []
$groups.forEach(group => {
if (group.users) {
group.users?.forEach(y => {
if (y._id === user._id) {
userGroups.push(group)
}
})
}
})
return {
...user,
name: user.firstName ? user.firstName + " " + user.lastName : "",
userGroups,
apps: [...new Set(Object.keys(user.roles))],
}
})
}
const showOnboardingTypeModal = async addUsersData => {
userData = await removingDuplicities(addUsersData)
if (!userData?.users?.length) return
onboardingTypeModal.show()
}
async function createUserFlow() {
let emails = userData?.users?.map(x => x.email) || []
try {
const res = await users.invite({
emails: emails,
builder: true,
admin: true,
})
notifications.success(res.message)
inviteConfirmationModal.show()
} catch (error) {
notifications.error("Error inviting user")
}
}
const removingDuplicities = async userData => {
const currentUserEmails = (await users.fetch())?.map(x => x.email) || []
const newUsers = []
for (const user of userData?.users) {
const { email } = user
if (
newUsers.find(x => x.email === email) ||
currentUserEmails.includes(email)
)
continue
newUsers.push(user)
}
if (!newUsers.length)
notifications.info("Duplicated! There is no new users to add.")
return { ...userData, users: newUsers }
}
const createUsersFromCsv = async userCsvData => {
const { userEmails, usersRole, userGroups: groups } = userCsvData
const users = []
for (const email of userEmails) {
const newUser = {
email: email,
role: usersRole,
password: Math.random().toString(36).substring(2, 22),
forceResetPassword: true,
}
users.push(newUser)
}
userData = await removingDuplicities({ groups, users })
if (!userData.users.length) return
return createUser()
}
async function createUser() {
try {
await users.create(await removingDuplicities(userData))
notifications.success("Successfully created user")
await groups.actions.init()
passwordModal.show()
} catch (error) {
notifications.error("Error creating user")
}
}
async function chooseCreationType(onboardingType) {
if (onboardingType === "emailOnboarding") {
createUserFlow()
} else {
await createUser()
}
}
onMount(async () => {
try {
await groups.actions.init()
} catch (error) {
notifications.error("Error fetching User Group data")
}
})
const deleteRows = async () => {
try {
let ids = selectedRows.map(user => user._id)
await users.bulkDelete(ids)
notifications.success(`Successfully deleted ${selectedRows.length} rows`)
selectedRows = []
await fetchUsers(page, searchEmail)
} catch (error) {
notifications.error("Error deleting rows")
}
}
async function fetchUsers(page, email) {
if ($pageInfo.loading) {
return
}
// need to remove the page if they've started searching
if (email && !prevEmail) {
pageInfo.reset()
page = undefined
}
prevEmail = email
try {
pageInfo.loading()
await users.search({ page, email })
pageInfo.fetched($users.hasNextPage, $users.nextPage)
} catch (error) {
notifications.error("Error getting user list")
}
}
</script>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading>Users</Heading>
<Body>Add users and control who gets access to your published apps</Body>
<div>
{#each accessTypes as type}
<div class="access-description">
<Icon name={type.icon} />
<div class="access-text">
<Body size="S">{type.description}</Body>
</div>
</div>
{/each}
</div>
</Layout>
<Layout gap="S" noPadding>
<ButtonGroup>
<Button
dataCy="add-user"
on:click={createUserModal.show}
icon="UserAdd"
cta>Add Users</Button
>
<Button on:click={importUsersModal.show} icon="Import" primary
>Import Users</Button
>
<div class="field">
<Label size="L">Search email</Label>
<Search bind:value={searchEmail} placeholder="" />
</div>
{#if selectedRows.length > 0}
<DeleteRowsButton on:updaterows {selectedRows} {deleteRows} />
{/if}
</ButtonGroup>
<Table
on:click={({ detail }) => $goto(`./${detail._id}`)}
{schema}
bind:selectedRows
data={enrichedUsers}
allowEditColumns={false}
allowEditRows={false}
allowSelectRows={true}
showHeaderBorder={false}
{customRenderers}
/>
<div class="pagination">
<Pagination
page={$pageInfo.pageNumber}
hasPrevPage={$pageInfo.loading ? false : $pageInfo.hasPrevPage}
hasNextPage={$pageInfo.loading ? false : $pageInfo.hasNextPage}
goToPrevPage={pageInfo.prevPage}
goToNextPage={pageInfo.nextPage}
/>
</div>
</Layout>
</Layout>
<Modal bind:this={createUserModal}>
<AddUserModal {showOnboardingTypeModal} />
</Modal>
<Modal bind:this={inviteConfirmationModal}>
<ModalContent
showCancelButton={false}
title="Invites sent!"
confirmText="Done"
>
<Body size="S"
>Your users should now recieve an email invite to get access to their
Budibase account</Body
></ModalContent
>
</Modal>
<Modal bind:this={onboardingTypeModal}>
<OnboardingTypeModal {chooseCreationType} />
</Modal>
<Modal bind:this={passwordModal}>
<PasswordModal userData={userData.users} />
</Modal>
<Modal bind:this={importUsersModal}>
<ImportUsersModal {createUsersFromCsv} />
</Modal>
<style>
.field {
display: flex;
align-items: center;
flex-direction: row;
grid-gap: var(--spacing-m);
margin-left: auto;
}
.field > :global(*) + :global(*) {
margin-left: var(--spacing-m);
}
.access-description {
display: flex;
margin-top: var(--spacing-xl);
opacity: 0.8;
}
.access-text {
margin-left: var(--spacing-m);
}
</style>