budibase/packages/builder/src/pages/builder/apps/index.svelte

196 lines
5.5 KiB
Svelte

<script>
import {
Heading,
Layout,
Divider,
ActionMenu,
MenuItem,
Avatar,
Page,
Icon,
Body,
Modal,
} from "@budibase/bbui"
import { onMount } from "svelte"
import { apps, organisation, auth } from "stores/portal"
import { goto } from "@roxi/routify"
import { AppStatus } from "constants"
import { gradient } from "actions"
import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte"
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
import { processStringSync } from "@budibase/string-templates"
import Logo from "assets/bb-emblem.svg"
let loaded = false
let userInfoModal
let changePasswordModal
onMount(async () => {
await organisation.init()
await apps.load()
loaded = true
})
const publishedAppsOnly = app => app.status === AppStatus.DEPLOYED
$: publishedApps = $apps.filter(publishedAppsOnly)
$: userApps = $auth.user?.builder?.global
? publishedApps
: publishedApps.filter(app =>
Object.keys($auth.user?.roles).includes(app.prodId)
)
function getUrl(app) {
if (app.url) {
return `/app${app.url}`
} else {
return `/${app.prodId}`
}
}
</script>
{#if $auth.user && loaded}
<div class="container">
<Page>
<div class="content">
<Layout noPadding>
<div class="header">
<img alt="logo" src={$organisation.logoUrl || Logo} />
<ActionMenu align="right">
<div slot="control" class="avatar">
<Avatar
size="M"
initials={$auth.initials}
url={$auth.user.pictureUrl}
/>
<Icon size="XL" name="ChevronDown" />
</div>
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
Update user information
</MenuItem>
<MenuItem
icon="LockClosed"
on:click={() => changePasswordModal.show()}
>
Update password
</MenuItem>
{#if $auth.isBuilder}
<MenuItem
icon="UserDeveloper"
on:click={() => $goto("../portal")}
>
Open developer mode
</MenuItem>
{/if}
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
</ActionMenu>
</div>
<Layout noPadding gap="XS">
<Heading size="M">
Hey {$auth.user.firstName || $auth.user.email}
</Heading>
<Body>
Welcome to the {$organisation.company} portal. Below you'll find the
list of apps that you have access to.
</Body>
</Layout>
<Divider />
{#if userApps.length}
<Heading>Apps</Heading>
<div class="group">
<Layout gap="S" noPadding>
{#each userApps as app, idx (app.appId)}
<a class="app" target="_blank" href={getUrl(app)}>
<div class="preview" use:gradient={{ seed: app.name }} />
<div class="app-info">
<Heading size="XS">{app.name}</Heading>
<Body size="S">
{#if app.updatedAt}
{processStringSync(
"Updated {{ duration time 'millisecond' }} ago",
{
time:
new Date().getTime() -
new Date(app.updatedAt).getTime(),
}
)}
{:else}
Never updated
{/if}
</Body>
</div>
<Icon name="ChevronRight" />
</a>
{/each}
</Layout>
</div>
{:else}
<Layout gap="XS" noPadding>
<Heading size="S">You don't have access to any apps yet.</Heading>
<Body size="S">
The apps you have access to will be listed here.
</Body>
</Layout>
{/if}
</Layout>
</div>
</Page>
</div>
<Modal bind:this={userInfoModal}>
<UpdateUserInfoModal />
</Modal>
<Modal bind:this={changePasswordModal}>
<ChangePasswordModal />
</Modal>
{/if}
<style>
.container {
height: 100%;
overflow: auto;
}
.content {
width: 100%;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
img {
width: 40px;
margin-bottom: -12px;
}
.avatar {
display: grid;
grid-template-columns: auto auto;
place-items: center;
grid-gap: var(--spacing-xs);
}
.avatar:hover {
cursor: pointer;
filter: brightness(110%);
}
.app {
display: grid;
grid-template-columns: auto 1fr auto;
background-color: var(--background);
padding: var(--spacing-xl);
border-radius: var(--border-radius-s);
align-items: center;
grid-gap: var(--spacing-xl);
color: inherit;
}
.app:hover {
cursor: pointer;
background: var(--spectrum-global-color-gray-200);
transition: background-color 130ms ease-in-out;
}
.preview {
height: 40px;
width: 60px;
border-radius: var(--border-radius-s);
}
</style>