forked from tsai/budibase
8 changed files with 530 additions and 4 deletions
@ -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