mirror of https://github.com/Budibase/budibase.git
83 changed files with 554 additions and 681 deletions
@ -1,62 +1,16 @@ |
|||||
<script> |
<script> |
||||
export let forAttr = "", |
import "@spectrum-css/fieldlabel/dist/index-vars.css" |
||||
extraSmall = false, |
|
||||
small = false, |
export let size = "M" |
||||
medium = false, |
|
||||
large = false, |
|
||||
extraLarge = false, |
|
||||
white = false, |
|
||||
grey = false, |
|
||||
black = false |
|
||||
</script> |
</script> |
||||
|
|
||||
<label |
<label class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}> |
||||
class="bb-label" |
|
||||
class:extraSmall |
|
||||
class:small |
|
||||
class:medium |
|
||||
class:large |
|
||||
class:extraLarge |
|
||||
class:white |
|
||||
class:grey |
|
||||
class:black |
|
||||
for={forAttr} |
|
||||
> |
|
||||
<slot /> |
<slot /> |
||||
</label> |
</label> |
||||
|
|
||||
<style> |
<style> |
||||
.bb-label { |
label { |
||||
font-family: var(--font-sans); |
padding: 0; |
||||
font-weight: 500; |
white-space: nowrap; |
||||
text-rendering: var(--text-render); |
|
||||
color: var(--ink); |
|
||||
font-size: var(--font-size-s); |
|
||||
margin-bottom: var(--spacing-s); |
|
||||
display: block; |
|
||||
} |
|
||||
.extraSmall { |
|
||||
font-size: var(--font-size-xs); |
|
||||
} |
|
||||
.small { |
|
||||
font-size: var(--font-size-s); |
|
||||
} |
|
||||
.medium { |
|
||||
font-size: var(--font-size-m); |
|
||||
} |
|
||||
.large { |
|
||||
font-size: var(--font-size-l); |
|
||||
} |
|
||||
.extraLarge { |
|
||||
font-size: var(--font-size-xl); |
|
||||
} |
|
||||
.white { |
|
||||
color: white; |
|
||||
} |
|
||||
.grey { |
|
||||
color: var(--grey-6); |
|
||||
} |
|
||||
.black { |
|
||||
color: var(--ink); |
|
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -0,0 +1,64 @@ |
|||||
|
export const gradient = (node, config = {}) => { |
||||
|
const defaultConfig = { |
||||
|
points: 10, |
||||
|
saturation: 0.8, |
||||
|
lightness: 0.75, |
||||
|
softness: 0.8, |
||||
|
} |
||||
|
|
||||
|
// Applies a gradient background
|
||||
|
const createGradient = config => { |
||||
|
config = { |
||||
|
...defaultConfig, |
||||
|
...config, |
||||
|
} |
||||
|
const { saturation, lightness, softness, points } = config |
||||
|
|
||||
|
// Generates a random number between min and max
|
||||
|
const rand = (min, max) => { |
||||
|
return Math.round(min + Math.random() * (max - min)) |
||||
|
} |
||||
|
|
||||
|
// Generates a random HSL colour using the options specified
|
||||
|
const randomHSL = () => { |
||||
|
const lowerSaturation = Math.min(100, saturation * 100) |
||||
|
const upperSaturation = Math.min(100, (saturation + 0.2) * 100) |
||||
|
const lowerLightness = Math.min(100, lightness * 100) |
||||
|
const upperLightness = Math.min(100, (lightness + 0.2) * 100) |
||||
|
const hue = rand(0, 360) |
||||
|
const sat = `${rand(lowerSaturation, upperSaturation)}%` |
||||
|
const light = `${rand(lowerLightness, upperLightness)}%` |
||||
|
return `hsl(${hue},${sat},${light})` |
||||
|
} |
||||
|
|
||||
|
// Generates a radial gradient stop point
|
||||
|
const randomGradientPoint = () => { |
||||
|
const lowerTransparency = Math.min(100, softness * 100) |
||||
|
const upperTransparency = Math.min(100, (softness + 0.2) * 100) |
||||
|
const transparency = rand(lowerTransparency, upperTransparency) |
||||
|
return ( |
||||
|
`radial-gradient(` + |
||||
|
`at ${rand(10, 90)}% ${rand(10, 90)}%,` + |
||||
|
`${randomHSL()} 0,` + |
||||
|
`transparent ${transparency}%)` |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
let css = `opacity:0.9;background-color:${randomHSL()};background-image:` |
||||
|
for (let i = 0; i < points - 1; i++) { |
||||
|
css += `${randomGradientPoint()},` |
||||
|
} |
||||
|
css += `${randomGradientPoint()};` |
||||
|
node.style = css |
||||
|
} |
||||
|
|
||||
|
// Apply the initial gradient
|
||||
|
createGradient(config) |
||||
|
|
||||
|
return { |
||||
|
// Apply a new gradient
|
||||
|
update: config => { |
||||
|
createGradient(config) |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
@ -1,50 +1,25 @@ |
|||||
<script> |
<script> |
||||
|
import { onMount } from "svelte" |
||||
import AppCard from "./AppCard.svelte" |
import AppCard from "./AppCard.svelte" |
||||
import { Heading, Divider } from "@budibase/bbui" |
import { apps } from "stores/portal" |
||||
import Spinner from "components/common/Spinner.svelte" |
|
||||
import { get } from "builderStore/api" |
|
||||
|
|
||||
let promise = getApps() |
onMount(apps.load) |
||||
|
|
||||
async function getApps() { |
|
||||
const res = await get("/api/applications") |
|
||||
const json = await res.json() |
|
||||
|
|
||||
if (res.ok) { |
|
||||
return json |
|
||||
} else { |
|
||||
throw new Error(json) |
|
||||
} |
|
||||
} |
|
||||
</script> |
</script> |
||||
|
|
||||
<div class="root"> |
{#if $apps.length} |
||||
<Heading size="M">Your Apps</Heading> |
<div class="appList"> |
||||
<Divider size="M" /> |
{#each $apps as app} |
||||
{#await promise} |
<AppCard {...app} /> |
||||
<div class="spinner-container"> |
{/each} |
||||
<Spinner size="30" /> |
</div> |
||||
</div> |
{:else} |
||||
{:then apps} |
<div>No apps found.</div> |
||||
<div class="apps"> |
{/if} |
||||
{#each apps as app} |
|
||||
<AppCard {...app} /> |
|
||||
{/each} |
|
||||
</div> |
|
||||
{:catch err} |
|
||||
<h1 style="color:red">{err}</h1> |
|
||||
{/await} |
|
||||
</div> |
|
||||
|
|
||||
<style> |
<style> |
||||
.root { |
.appList { |
||||
margin-top: 10px; |
|
||||
} |
|
||||
.apps { |
|
||||
margin-top: var(--layout-m); |
|
||||
display: grid; |
display: grid; |
||||
|
grid-gap: 50px; |
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); |
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); |
||||
grid-gap: var(--layout-s); |
|
||||
justify-content: start; |
|
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -1,15 +0,0 @@ |
|||||
<script> |
|
||||
import { Button, Modal } from "@budibase/bbui" |
|
||||
import BuilderSettingsModal from "./BuilderSettingsModal.svelte" |
|
||||
|
|
||||
let modal |
|
||||
</script> |
|
||||
|
|
||||
<div> |
|
||||
<Button primary quiet icon="Settings" text on:click={modal.show}> |
|
||||
Settings |
|
||||
</Button> |
|
||||
</div> |
|
||||
<Modal bind:this={modal} width="30%"> |
|
||||
<BuilderSettingsModal /> |
|
||||
</Modal> |
|
||||
@ -1,6 +0,0 @@ |
|||||
<script> |
|
||||
import { Button } from "@budibase/bbui" |
|
||||
import { auth } from "stores/backend" |
|
||||
</script> |
|
||||
|
|
||||
<Button primary quiet text icon="LogOut" on:click={auth.logout}>Log Out</Button> |
|
||||
@ -1,29 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount } from "svelte" |
|
||||
import { goto } from "@roxi/routify" |
|
||||
import { |
|
||||
SideNavigation as Navigation, |
|
||||
SideNavigationItem as Item, |
|
||||
} from "@budibase/bbui" |
|
||||
import { admin } from "stores/portal" |
|
||||
import LoginForm from "components/login/LoginForm.svelte" |
|
||||
import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte" |
|
||||
import LogoutButton from "components/start/LogoutButton.svelte" |
|
||||
import Logo from "/assets/budibase-logo.svg" |
|
||||
import api from "builderStore/api" |
|
||||
|
|
||||
let checklist |
|
||||
|
|
||||
onMount(async () => { |
|
||||
await admin.init() |
|
||||
if (!$admin?.checklist?.adminUser) { |
|
||||
$goto("./admin") |
|
||||
} else { |
|
||||
$goto("./portal") |
|
||||
} |
|
||||
}) |
|
||||
</script> |
|
||||
|
|
||||
{#if $admin.checklist} |
|
||||
<slot /> |
|
||||
{/if} |
|
||||
@ -1,69 +0,0 @@ |
|||||
<script> |
|
||||
import { |
|
||||
Button, |
|
||||
Heading, |
|
||||
Label, |
|
||||
notifications, |
|
||||
Layout, |
|
||||
Input, |
|
||||
Body, |
|
||||
} from "@budibase/bbui" |
|
||||
import { goto } from "@roxi/routify" |
|
||||
import { onMount } from "svelte" |
|
||||
import api from "builderStore/api" |
|
||||
|
|
||||
let adminUser = {} |
|
||||
|
|
||||
async function save() { |
|
||||
try { |
|
||||
// Save the admin user |
|
||||
const response = await api.post(`/api/admin/users/init`, adminUser) |
|
||||
|
|
||||
const json = await response.json() |
|
||||
if (response.status !== 200) throw new Error(json.message) |
|
||||
notifications.success(`Admin user created.`) |
|
||||
$goto("../portal") |
|
||||
} catch (err) { |
|
||||
notifications.error(`Failed to create admin user.`) |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<section> |
|
||||
<div class="container"> |
|
||||
<header> |
|
||||
<Heading size="M">Create an admin user</Heading> |
|
||||
<Body size="S">The admin user has access to everything in budibase.</Body> |
|
||||
</header> |
|
||||
<div class="config-form"> |
|
||||
<Layout gap="S"> |
|
||||
<Input label="email" bind:value={adminUser.email} /> |
|
||||
<Input |
|
||||
label="password" |
|
||||
type="password" |
|
||||
bind:value={adminUser.password} |
|
||||
/> |
|
||||
<Button cta on:click={save}>Create super admin user</Button> |
|
||||
</Layout> |
|
||||
</div> |
|
||||
</div> |
|
||||
</section> |
|
||||
|
|
||||
<style> |
|
||||
section { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
header { |
|
||||
text-align: center; |
|
||||
width: 80%; |
|
||||
margin: 0 auto; |
|
||||
} |
|
||||
|
|
||||
.config-form { |
|
||||
margin-bottom: 42px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,116 +1,33 @@ |
|||||
<script> |
<script> |
||||
import { |
import { onMount } from "svelte" |
||||
SideNavigation as Navigation, |
import { goto } from "@roxi/routify" |
||||
SideNavigationItem as Item, |
|
||||
} from "@budibase/bbui" |
|
||||
import { auth } from "stores/backend" |
import { auth } from "stores/backend" |
||||
import LoginForm from "components/login/LoginForm.svelte" |
import { admin } from "stores/portal" |
||||
import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte" |
|
||||
import LogoutButton from "components/start/LogoutButton.svelte" |
|
||||
import Logo from "/assets/budibase-logo.svg" |
|
||||
|
|
||||
let modal |
let loaded = false |
||||
</script> |
$: hasAdminUser = !!$admin?.checklist?.adminUser |
||||
|
|
||||
{#if $auth} |
|
||||
{#if $auth.user} |
|
||||
<div class="root"> |
|
||||
<div class="ui-nav"> |
|
||||
<div class="home-logo"> |
|
||||
<img src={Logo} alt="Budibase icon" /> |
|
||||
</div> |
|
||||
<div class="nav-section"> |
|
||||
<div class="nav-top"> |
|
||||
<Navigation> |
|
||||
<Item href="/builder/" icon="Apps" selected>Apps</Item> |
|
||||
<Item external href="https://portal.budi.live/" icon="Servers"> |
|
||||
Hosting |
|
||||
</Item> |
|
||||
<Item external href="https://docs.budibase.com/" icon="Book"> |
|
||||
Documentation |
|
||||
</Item> |
|
||||
<Item |
|
||||
external |
|
||||
href="https://github.com/Budibase/budibase/discussions" |
|
||||
icon="PeopleGroup" |
|
||||
> |
|
||||
Community |
|
||||
</Item> |
|
||||
<Item |
|
||||
external |
|
||||
href="https://github.com/Budibase/budibase/issues/new/choose" |
|
||||
icon="Bug" |
|
||||
> |
|
||||
Raise an issue |
|
||||
</Item> |
|
||||
</Navigation> |
|
||||
</div> |
|
||||
<div class="nav-bottom"> |
|
||||
<BuilderSettingsButton /> |
|
||||
<LogoutButton /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="main"> |
|
||||
<slot /> |
|
||||
</div> |
|
||||
</div> |
|
||||
{:else} |
|
||||
<section class="login"> |
|
||||
<LoginForm /> |
|
||||
</section> |
|
||||
{/if} |
|
||||
{/if} |
|
||||
|
|
||||
<style> |
|
||||
.root { |
|
||||
display: grid; |
|
||||
grid-template-columns: 260px 1fr; |
|
||||
height: 100%; |
|
||||
width: 100%; |
|
||||
} |
|
||||
|
|
||||
.login { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
height: 100%; |
|
||||
width: 100%; |
|
||||
} |
|
||||
|
|
||||
.main { |
onMount(async () => { |
||||
grid-column: 2; |
await admin.init() |
||||
overflow: auto; |
await auth.checkAuth() |
||||
} |
loaded = true |
||||
|
}) |
||||
|
|
||||
.ui-nav { |
// Force creation of an admin user if one doesn't exist |
||||
grid-column: 1; |
$: { |
||||
background-color: var(--background); |
if (loaded && !hasAdminUser) { |
||||
padding: 20px; |
$goto("./admin") |
||||
display: flex; |
} |
||||
flex-direction: column; |
|
||||
border-right: var(--border-light); |
|
||||
} |
} |
||||
|
|
||||
.home-logo { |
// Redirect to log in at any time if the user isn't authenticated |
||||
cursor: pointer; |
$: { |
||||
height: 40px; |
if (loaded && hasAdminUser && !$auth.user) { |
||||
margin-bottom: 20px; |
$goto("./auth/login") |
||||
} |
} |
||||
|
|
||||
.home-logo img { |
|
||||
height: 40px; |
|
||||
} |
|
||||
|
|
||||
.nav-section { |
|
||||
margin: 20px 0 0 0; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
justify-content: space-between; |
|
||||
height: 100%; |
|
||||
} |
} |
||||
|
</script> |
||||
|
|
||||
.nav-bottom :global(> *) { |
{#if loaded} |
||||
margin-top: 5px; |
<slot /> |
||||
} |
{/if} |
||||
</style> |
|
||||
|
|||||
@ -0,0 +1,78 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
Button, |
||||
|
Heading, |
||||
|
notifications, |
||||
|
Layout, |
||||
|
Input, |
||||
|
Body, |
||||
|
} from "@budibase/bbui" |
||||
|
import { goto } from "@roxi/routify" |
||||
|
import api from "builderStore/api" |
||||
|
import { admin } from "stores/portal" |
||||
|
|
||||
|
let adminUser = {} |
||||
|
|
||||
|
async function save() { |
||||
|
try { |
||||
|
// Save the admin user |
||||
|
const response = await api.post(`/api/admin/users/init`, adminUser) |
||||
|
const json = await response.json() |
||||
|
if (response.status !== 200) { |
||||
|
throw new Error(json.message) |
||||
|
} |
||||
|
notifications.success(`Admin user created`) |
||||
|
await admin.init() |
||||
|
$goto("../portal") |
||||
|
} catch (err) { |
||||
|
notifications.error(`Failed to create admin user`) |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<section> |
||||
|
<div class="container"> |
||||
|
<Layout gap="XS"> |
||||
|
<img src="https://i.imgur.com/ZKyklgF.png" /> |
||||
|
</Layout> |
||||
|
<div class="center"> |
||||
|
<Layout gap="XS"> |
||||
|
<Heading size="M">Create an admin user</Heading> |
||||
|
<Body size="M" |
||||
|
>The admin user has access to everything in Budibase.</Body |
||||
|
> |
||||
|
</Layout> |
||||
|
</div> |
||||
|
<Layout gap="XS"> |
||||
|
<Input label="Email" bind:value={adminUser.email} /> |
||||
|
<Input label="Password" type="password" bind:value={adminUser.password} /> |
||||
|
</Layout> |
||||
|
<Layout gap="S"> |
||||
|
<Button cta on:click={save}>Create super admin user</Button> |
||||
|
</Layout> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
section { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
.container { |
||||
|
margin: 0 auto; |
||||
|
width: 260px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
} |
||||
|
.center { |
||||
|
text-align: center; |
||||
|
} |
||||
|
img { |
||||
|
width: 40px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,4 @@ |
|||||
|
<script> |
||||
|
import { goto } from "@roxi/routify" |
||||
|
$goto("../portal") |
||||
|
</script> |
||||
@ -0,0 +1,4 @@ |
|||||
|
<script> |
||||
|
import { goto } from "@roxi/routify" |
||||
|
$goto("./login") |
||||
|
</script> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<script> |
||||
|
import LoginForm from "components/login/LoginForm.svelte" |
||||
|
</script> |
||||
|
|
||||
|
<LoginForm /> |
||||
@ -0,0 +1,92 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
Heading, |
||||
|
Layout, |
||||
|
Button, |
||||
|
ActionButton, |
||||
|
ActionGroup, |
||||
|
ButtonGroup, |
||||
|
Select, |
||||
|
Modal, |
||||
|
} from "@budibase/bbui" |
||||
|
import AppList from "components/start/AppList.svelte" |
||||
|
import CreateAppModal from "components/start/CreateAppModal.svelte" |
||||
|
import api from "builderStore/api" |
||||
|
import analytics from "analytics" |
||||
|
import { onMount } from "svelte" |
||||
|
|
||||
|
let layout = "grid" |
||||
|
let modal |
||||
|
let template |
||||
|
|
||||
|
async function checkKeys() { |
||||
|
const response = await api.get(`/api/keys/`) |
||||
|
const keys = await response.json() |
||||
|
if (keys.userId) { |
||||
|
analytics.identify(keys.userId) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function initiateAppImport() { |
||||
|
template = { fromFile: true } |
||||
|
modal.show() |
||||
|
} |
||||
|
|
||||
|
onMount(checkKeys) |
||||
|
</script> |
||||
|
|
||||
|
<Layout noPadding> |
||||
|
<div class="title"> |
||||
|
<Heading>Apps</Heading> |
||||
|
<ButtonGroup> |
||||
|
<Button secondary on:click={initiateAppImport}>Import app</Button> |
||||
|
<Button cta on:click={modal.show}>Create new app</Button> |
||||
|
</ButtonGroup> |
||||
|
</div> |
||||
|
<div class="filter"> |
||||
|
<div class="select"> |
||||
|
<Select quiet placeholder="Filter by groups" /> |
||||
|
</div> |
||||
|
<ActionGroup> |
||||
|
<ActionButton |
||||
|
on:click={() => (layout = "grid")} |
||||
|
selected={layout === "grid"} |
||||
|
quiet |
||||
|
icon="ClassicGridView" |
||||
|
/> |
||||
|
<ActionButton |
||||
|
on:click={() => (layout = "table")} |
||||
|
selected={layout === "table"} |
||||
|
quiet |
||||
|
icon="ViewRow" |
||||
|
/> |
||||
|
</ActionGroup> |
||||
|
</div> |
||||
|
{#if layout === "grid"} |
||||
|
<AppList /> |
||||
|
{:else} |
||||
|
Table view. |
||||
|
{/if} |
||||
|
</Layout> |
||||
|
<Modal |
||||
|
bind:this={modal} |
||||
|
padding={false} |
||||
|
width="600px" |
||||
|
on:hide={() => (template = null)} |
||||
|
> |
||||
|
<CreateAppModal {template} /> |
||||
|
</Modal> |
||||
|
|
||||
|
<style> |
||||
|
.title, |
||||
|
.filter { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.select { |
||||
|
width: 110px; |
||||
|
} |
||||
|
</style> |
||||
@ -1 +1,4 @@ |
|||||
Index route |
<script> |
||||
|
import { goto } from "@roxi/routify" |
||||
|
$goto("./builder") |
||||
|
</script> |
||||
|
|||||
@ -1,27 +0,0 @@ |
|||||
<script> |
|
||||
import { Heading, Layout } from "@budibase/bbui" |
|
||||
</script> |
|
||||
|
|
||||
<Layout noPadding> |
|
||||
<div> |
|
||||
<Heading>Apps</Heading> |
|
||||
</div> |
|
||||
<div class="appList"> |
|
||||
{#each new Array(10) as _} |
|
||||
<div class="app" /> |
|
||||
{/each} |
|
||||
</div> |
|
||||
</Layout> |
|
||||
|
|
||||
<style> |
|
||||
.appList { |
|
||||
display: grid; |
|
||||
grid-gap: 50px; |
|
||||
grid-template-columns: repeat(auto-fill, 300px); |
|
||||
} |
|
||||
.app { |
|
||||
height: 130px; |
|
||||
border-radius: 4px; |
|
||||
background-color: var(--spectrum-global-color-gray-200); |
|
||||
} |
|
||||
</style> |
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,27 @@ |
|||||
|
import { writable } from "svelte/store" |
||||
|
import { get } from "builderStore/api" |
||||
|
|
||||
|
export function createAppStore() { |
||||
|
const store = writable([]) |
||||
|
|
||||
|
async function load() { |
||||
|
try { |
||||
|
const res = await get("/api/applications") |
||||
|
const json = await res.json() |
||||
|
if (res.ok && Array.isArray(json)) { |
||||
|
store.set(json) |
||||
|
} else { |
||||
|
store.set([]) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
store.set([]) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
subscribe: store.subscribe, |
||||
|
load, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const apps = createAppStore() |
||||
@ -1,3 +1,4 @@ |
|||||
export { organisation } from "./organisation" |
export { organisation } from "./organisation" |
||||
export { users } from "./users" |
export { users } from "./users" |
||||
export { admin } from "./admin" |
export { admin } from "./admin" |
||||
|
export { apps } from "./apps" |
||||
|
|||||
Loading…
Reference in new issue