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