add user groups UI
This commit is contained in:
parent
b3227fe51e
commit
d529d7456f
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
//import { createEventDispatcher } from "svelte"
|
||||||
import "@spectrum-css/popover/dist/index-vars.css"
|
import "@spectrum-css/popover/dist/index-vars.css"
|
||||||
import clickOutside from "../Actions/click_outside"
|
import clickOutside from "../Actions/click_outside"
|
||||||
import { fly } from "svelte/transition"
|
import { fly } from "svelte/transition"
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
let open = false
|
let open = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
// const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const iconList = [
|
const iconList = [
|
||||||
{
|
{
|
||||||
|
@ -44,11 +44,12 @@
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
/*
|
||||||
const onChange = value => {
|
const onChange = value => {
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
|
@ -52,6 +52,11 @@
|
||||||
href: "/builder/portal/manage/users",
|
href: "/builder/portal/manage/users",
|
||||||
heading: "Manage",
|
heading: "Manage",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "User Groups",
|
||||||
|
href: "/builder/portal/manage/groups",
|
||||||
|
},
|
||||||
|
|
||||||
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
{ title: "Auth", href: "/builder/portal/manage/auth" },
|
||||||
{ title: "Email", href: "/builder/portal/manage/email" },
|
{ title: "Email", href: "/builder/portal/manage/email" },
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,10 +9,14 @@
|
||||||
$redirect("../")
|
$redirect("../")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: wide =
|
||||||
|
$page.path.includes("email/:template") ||
|
||||||
|
($page.path.includes("groups") && !$page.path.includes(":groupId"))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $auth.isAdmin}
|
{#if $auth.isAdmin}
|
||||||
<Page maxWidth="90ch" wide={$page.path.includes("email/:template")}>
|
<Page maxWidth="90ch" {wide}>
|
||||||
<slot />
|
<slot />
|
||||||
</Page>
|
</Page>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
<script>
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
import {
|
||||||
|
ActionButton,
|
||||||
|
Button,
|
||||||
|
Layout,
|
||||||
|
Heading,
|
||||||
|
Body,
|
||||||
|
Icon,
|
||||||
|
Popover,
|
||||||
|
Search,
|
||||||
|
Divider,
|
||||||
|
Detail,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import UserRow from "./_components/UserRow.svelte"
|
||||||
|
import { users } from "stores/portal"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
let popoverAnchor
|
||||||
|
let popover
|
||||||
|
let searchTerm = ""
|
||||||
|
let selectedUsers = []
|
||||||
|
$: filteredUsers = $users.filter(user =>
|
||||||
|
user?.email?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
)
|
||||||
|
|
||||||
|
let group = {
|
||||||
|
_id: "gr_123456",
|
||||||
|
color: "green",
|
||||||
|
icon: "Anchor",
|
||||||
|
name: "Core Team",
|
||||||
|
userCount: 5,
|
||||||
|
appCount: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
let groupUsers = [
|
||||||
|
{
|
||||||
|
email: "peter@budibase.com",
|
||||||
|
access: "Developer",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/*
|
||||||
|
function getGroup() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
function selectUser(id) {
|
||||||
|
let user = selectedUsers.find(user_id => user_id === id)
|
||||||
|
if (user) {
|
||||||
|
selectedUsers = selectedUsers.filter(id => id !== user)
|
||||||
|
} else {
|
||||||
|
selectedUsers = [...selectedUsers, id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMount(() => {
|
||||||
|
console.log($users)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Layout noPadding>
|
||||||
|
<div>
|
||||||
|
<ActionButton on:click={() => $goto("../groups")} size="S" icon="ArrowLeft">
|
||||||
|
Back
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">
|
||||||
|
<div style="background: {group.color};" class="circle">
|
||||||
|
<div>
|
||||||
|
<Icon size="M" name={group.icon} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-padding">
|
||||||
|
<Heading>{group.name}</Heading>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div bind:this={popoverAnchor}>
|
||||||
|
<Button on:click={popover.show()} icon="UserAdd" cta>Add User</Button>
|
||||||
|
</div>
|
||||||
|
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
|
||||||
|
<div style="padding: var(--spacing-m)">
|
||||||
|
<Search placeholder="Search" bind:value={searchTerm} />
|
||||||
|
<div class="users-header header">
|
||||||
|
<div>
|
||||||
|
<Detail
|
||||||
|
>{filteredUsers.length} User{filteredUsers.length === 1
|
||||||
|
? ""
|
||||||
|
: "s"}</Detail
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ActionButton emphasized size="S">Add all</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Divider noMargin />
|
||||||
|
<div>
|
||||||
|
{#each filteredUsers as user}
|
||||||
|
<div
|
||||||
|
on:click={selectUser(user._id)}
|
||||||
|
style="padding-bottom: var(--spacing-m)"
|
||||||
|
class="user-selection"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{user.email}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if selectedUsers.includes(user._id)}
|
||||||
|
<div>
|
||||||
|
<Icon
|
||||||
|
color="var(--spectrum-global-color-blue-600);"
|
||||||
|
name="Checkmark"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usersTable">
|
||||||
|
{#if groupUsers.length}
|
||||||
|
{#each groupUsers as user}
|
||||||
|
<div>
|
||||||
|
<UserRow {user} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<div>
|
||||||
|
<div class="title header text-padding">
|
||||||
|
<Icon name="UserGroup" />
|
||||||
|
<div class="text-padding">
|
||||||
|
<Body size="S">You have no users in this team</Body>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.text-padding {
|
||||||
|
margin-left: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-header {
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-m) 0 var(--spacing-m) 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-selection {
|
||||||
|
align-items: end;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-selection > :first-child {
|
||||||
|
padding-top: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 30px;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.2em;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle > div {
|
||||||
|
padding: calc(1.5 * var(--spacing-xs)) var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.usersTable {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--spectrum-alias-border-color-mid);
|
||||||
|
border-left: 1px solid var(--spectrum-alias-border-color-mid);
|
||||||
|
background: var(--spectrum-global-color-gray-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.usersTable :global(> div) {
|
||||||
|
background: var(--bg-color);
|
||||||
|
|
||||||
|
height: 70px;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
grid-template-columns: 0.1fr 0.6fr 2fr 0.4fr;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0 var(--spacing-s);
|
||||||
|
border-top: 1px solid var(--spectrum-alias-border-color-mid);
|
||||||
|
border-right: 1px solid var(--spectrum-alias-border-color-mid);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,92 @@
|
||||||
|
<script>
|
||||||
|
import { Button, Icon, Body } from "@budibase/bbui"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
|
export let group
|
||||||
|
|
||||||
|
let { icon, color, name, userCount, appCount } = group
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="title">
|
||||||
|
<div class="name" style="display: flex; margin-left: var(--spacing-xl)">
|
||||||
|
<div style="background: {color};" class="circle">
|
||||||
|
<div>
|
||||||
|
<Icon size="M" name={icon} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="name" data-cy="app-name-link"><Body size="S">{name}</Body></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="desktop tableElement">
|
||||||
|
<Icon name="User" />
|
||||||
|
<div style="margin-left: var(--spacing-l">
|
||||||
|
{parseInt(userCount)} app{parseInt(userCount) === 1 ? "" : "s"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="desktop tableElement">
|
||||||
|
<Icon name="WebPage" />
|
||||||
|
|
||||||
|
<div style="margin-left: var(--spacing-l">
|
||||||
|
{parseInt(appCount)} app{parseInt(appCount) === 1 ? "" : "s"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="app-row-actions">
|
||||||
|
<div>
|
||||||
|
<Button on:click={() => $goto(`./${group._id}`)} size="S" cta
|
||||||
|
>Manage</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.app-row-actions {
|
||||||
|
display: flex;
|
||||||
|
float: right;
|
||||||
|
margin-right: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
grid-template-columns: 75px 75px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 30px;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.2em;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableElement {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle > div {
|
||||||
|
padding: calc(1.5 * var(--spacing-xs)) var(--spacing-xs);
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
text-decoration: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.name :global(.spectrum-Heading) {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-left: calc(1.5 * var(--spacing-xl));
|
||||||
|
}
|
||||||
|
.title :global(h1:hover) {
|
||||||
|
color: var(--spectrum-global-color-blue-600);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 130ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.desktop {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<script>
|
||||||
|
import { Icon, Body, Avatar } from "@budibase/bbui"
|
||||||
|
|
||||||
|
export let user
|
||||||
|
|
||||||
|
function removeFromGroup() {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="title">
|
||||||
|
<div class="name" style="display: flex; margin-left: var(--spacing-xl)">
|
||||||
|
<div>
|
||||||
|
<Avatar size="L" initials="PC" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="desktop">
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<Body size="M">{user.email}</Body>
|
||||||
|
<div style="opacity: 0.5; margin: var(--spacing-xs) 0 0 var(--spacing-m)">
|
||||||
|
<Body size="XS">{user.access}</Body>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="desktop" />
|
||||||
|
<div><Icon on:click={removeFromGroup} hoverable size="L" name="Close" /></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.name {
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
grid-template-columns: 75px 75px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
text-decoration: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.name :global(.spectrum-Heading) {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-left: calc(1.5 * var(--spacing-xl));
|
||||||
|
}
|
||||||
|
.title :global(h1:hover) {
|
||||||
|
color: var(--spectrum-global-color-blue-600);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 130ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.desktop {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div style="float: right;">
|
||||||
|
<slot />
|
||||||
|
</div>
|
|
@ -0,0 +1,155 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Layout,
|
||||||
|
Heading,
|
||||||
|
ColorPicker,
|
||||||
|
Body,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Input,
|
||||||
|
Tag,
|
||||||
|
Tags,
|
||||||
|
IconPicker,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import UserGroupsRow from "./_components/UserGroupsRow.svelte"
|
||||||
|
let modal
|
||||||
|
let selectedColor
|
||||||
|
let selectedIcon
|
||||||
|
let proPlan = true
|
||||||
|
|
||||||
|
let userGroupData = [
|
||||||
|
{
|
||||||
|
_id: "gr_123456",
|
||||||
|
color: "green",
|
||||||
|
icon: "Anchor",
|
||||||
|
name: "Core Team",
|
||||||
|
userCount: 5,
|
||||||
|
appCount: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "gr_45678",
|
||||||
|
color: "red",
|
||||||
|
icon: "Beaker",
|
||||||
|
name: "QA Team",
|
||||||
|
userCount: 3,
|
||||||
|
appCount: 7,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Layout noPadding>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<div style="display: flex;">
|
||||||
|
<Heading size="M">User groups</Heading>
|
||||||
|
{#if !proPlan}
|
||||||
|
<Tags>
|
||||||
|
<div class="tags">
|
||||||
|
<div class="tag">
|
||||||
|
<Tag icon="LockClosed">Pro plan</Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tags>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<Body>Easily assign and manage your users access with User Groups</Body>
|
||||||
|
</Layout>
|
||||||
|
<div class="align-buttons">
|
||||||
|
<Button
|
||||||
|
icon={proPlan ? "UserGroup" : ""}
|
||||||
|
cta={proPlan}
|
||||||
|
on:click={() => modal.show()}
|
||||||
|
>{proPlan ? "Create user group" : "Upgrade Account"}</Button
|
||||||
|
>
|
||||||
|
{#if !proPlan}
|
||||||
|
<Button
|
||||||
|
secondary
|
||||||
|
on:click={() => {
|
||||||
|
window.open("https://budibase.com/pricing/", "_blank")
|
||||||
|
}}>View Plans</Button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="groupTable">
|
||||||
|
{#each userGroupData as group}
|
||||||
|
<div>
|
||||||
|
<UserGroupsRow {group} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<ModalContent size="M" title="Create User Group" confirmText="Save">
|
||||||
|
<Input label="Team name" />
|
||||||
|
<div class="modal-format">
|
||||||
|
<div class="modal-inner">
|
||||||
|
<Body size="XS">Icon</Body>
|
||||||
|
<div class="modal-spacing">
|
||||||
|
<IconPicker bind:value={selectedIcon} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-inner">
|
||||||
|
<Body size="XS">Color</Body>
|
||||||
|
<div class="modal-spacing">
|
||||||
|
<ColorPicker
|
||||||
|
bind:value={selectedColor}
|
||||||
|
on:change={e => (selectedColor = e.detail)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.modal-format {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-spacing {
|
||||||
|
margin-left: var(--spacing-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-buttons {
|
||||||
|
display: flex;
|
||||||
|
column-gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
margin-top: var(--spacing-xs);
|
||||||
|
margin-left: var(--spacing-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupTable {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--spectrum-alias-border-color-mid);
|
||||||
|
border-left: 1px solid var(--spectrum-alias-border-color-mid);
|
||||||
|
background: var(--spectrum-global-color-gray-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupTable :global(> div) {
|
||||||
|
background: var(--bg-color);
|
||||||
|
|
||||||
|
height: 70px;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
grid-gap: var(--spacing-xl);
|
||||||
|
grid-template-columns: 2fr 2fr 2fr auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0 var(--spacing-s);
|
||||||
|
border-top: 1px solid var(--spectrum-alias-border-color-mid);
|
||||||
|
border-right: 1px solid var(--spectrum-alias-border-color-mid);
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue