Add initial user dropdown in nav, improve typing in auth store

This commit is contained in:
Andrew Kingston 2025-03-04 11:49:07 +00:00
parent b20ef58727
commit d7fc0f1b4b
No known key found for this signature in database
8 changed files with 115 additions and 65 deletions

View File

@ -5,10 +5,10 @@
export let disabled = false export let disabled = false
export let align = "left" export let align = "left"
export let portalTarget export let portalTarget = undefined
export let openOnHover = false export let openOnHover = false
export let animate export let animate = true
export let offset export let offset = undefined
const actionMenuContext = getContext("actionMenu") const actionMenuContext = getContext("actionMenu")

View File

@ -14,7 +14,7 @@
export let url = "" export let url = ""
export let disabled = false export let disabled = false
export let initials = "JD" export let initials = "JD"
export let color = null export let color = ""
const DefaultColor = "#3aab87" const DefaultColor = "#3aab87"

View File

@ -105,8 +105,8 @@
<img class="logo" alt="logo" src={$organisation.logoUrl || Logo} /> <img class="logo" alt="logo" src={$organisation.logoUrl || Logo} />
<ActionMenu align="right"> <ActionMenu align="right">
<div slot="control" class="avatar"> <div slot="control" class="avatar">
<UserAvatar user={$auth.user} showTooltip={false} /> <UserAvatar size="M" user={$auth.user} showTooltip={false} />
<Icon size="XL" name="ChevronDown" /> <Icon size="L" name="ChevronDown" />
</div> </div>
<MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}> <MenuItem icon="UserEdit" on:click={() => userInfoModal.show()}>
My profile My profile
@ -239,6 +239,7 @@
grid-template-columns: auto auto; grid-template-columns: auto auto;
place-items: center; place-items: center;
grid-gap: var(--spacing-xs); grid-gap: var(--spacing-xs);
transition: filter 130ms ease-out;
} }
.avatar:hover { .avatar:hover {
cursor: pointer; cursor: pointer;

View File

@ -26,8 +26,8 @@
<ActionMenu align="right"> <ActionMenu align="right">
<div slot="control" class="user-dropdown"> <div slot="control" class="user-dropdown">
<UserAvatar user={$auth.user} showTooltip={false} /> <UserAvatar size="M" user={$auth.user} showTooltip={false} />
<Icon size="XL" name="ChevronDown" /> <Icon size="L" name="ChevronDown" />
</div> </div>
<MenuItem icon="UserEdit" on:click={() => profileModal.show()}> <MenuItem icon="UserEdit" on:click={() => profileModal.show()}>
My profile My profile
@ -75,7 +75,8 @@
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
gap: var(--spacing-s); gap: var(--spacing-xs);
transition: filter 130ms ease-out;
} }
.user-dropdown:hover { .user-dropdown:hover {
cursor: pointer; cursor: pointer;

View File

@ -1,10 +1,10 @@
<script> <script>
import { getContext, setContext } from "svelte" import { getContext, setContext } from "svelte"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { Heading, Icon, clickOutside, TooltipPosition } from "@budibase/bbui" import { Heading, Icon, clickOutside } from "@budibase/bbui"
import { Constants } from "@budibase/frontend-core" import { Constants } from "@budibase/frontend-core"
import NavItem from "./NavItem.svelte" import NavItem from "./NavItem.svelte"
import { UserAvatar } from "@budibase/frontend-core" import UserMenu from "./UserMenu.svelte"
const sdk = getContext("sdk") const sdk = getContext("sdk")
const { const {
@ -14,7 +14,6 @@
builderStore, builderStore,
sidePanelStore, sidePanelStore,
modalStore, modalStore,
authStore,
} = sdk } = sdk
const context = getContext("context") const context = getContext("context")
const navStateStore = writable({}) const navStateStore = writable({})
@ -159,11 +158,6 @@
return !url.startsWith("http") ? `http://${url}` : url return !url.startsWith("http") ? `http://${url}` : url
} }
const navigateToPortal = () => {
if ($builderStore.inBuilder) return
window.location.href = "/builder/apps"
}
const getScreenXOffset = (navigation, mobile) => { const getScreenXOffset = (navigation, mobile) => {
if (navigation !== "Left") { if (navigation !== "Left") {
return 0 return 0
@ -276,14 +270,9 @@
<Heading size="S" {textAlign}>{title}</Heading> <Heading size="S" {textAlign}>{title}</Heading>
{/if} {/if}
</div> </div>
{#if !embedded && $authStore} {#if !embedded}
<div class="user top"> <div class="user top">
<UserAvatar <UserMenu compact />
user={$authStore}
size="M"
tooltipPosition={TooltipPosition.Left}
/>
<Icon name="ChevronDown" />
</div> </div>
{/if} {/if}
</div> </div>
@ -309,16 +298,9 @@
{/each} {/each}
</div> </div>
{/if} {/if}
{#if !embedded && $authStore} {#if !embedded}
<div class="user left"> <div class="user left">
<UserAvatar user={$authStore} size="M" showTooltip={false} /> <UserMenu />
<div class="text">
<div class="name">
{$authStore.firstName}
{$authStore.lastName}
</div>
</div>
<Icon name="ChevronDown" />
</div> </div>
{/if} {/if}
</div> </div>
@ -560,34 +542,6 @@
display: none; display: none;
} }
/* User avatar */
.user {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 8px;
transition: background 130ms ease-out;
border-radius: 4px;
}
.user .text {
flex-direction: column;
align-items: flex-start;
justify-content: center;
color: var(--navTextColor);
display: none;
}
.user .name {
font-weight: 600;
font-size: 14px;
}
.layout--left .user .text {
display: flex;
}
.user:hover {
cursor: pointer;
}
/* Left overrides for both desktop and mobile */ /* Left overrides for both desktop and mobile */
.nav--left { .nav--left {
overflow-y: auto; overflow-y: auto;

View File

@ -0,0 +1,78 @@
<script lang="ts">
import { ActionMenu, Icon, MenuItem } from "@budibase/bbui"
import { UserAvatar } from "@budibase/frontend-core"
import { getContext } from "svelte"
export let compact: boolean = false
const { authStore } = getContext("sdk")
const requestChangeDetails = () => {}
const requestChangePassword = () => {
// if (isOwner) {
// window.location.href = `${$admin.accountPortalUrl}/portal/account`
// } else {
// changePasswordModal.show()
// }
}
const goToPortal = () => {
window.location.href = "/builder/apps"
}
</script>
{#if $authStore}
<ActionMenu align={compact ? "right" : "left"}>
<svelte:fragment slot="control" let:open>
<div class="container" class:open>
<UserAvatar user={$authStore} size="M" showTooltip={false} />
{#if !compact}
<div class="text">
<div class="name">
{$authStore.firstName}
{$authStore.lastName}
</div>
</div>
{/if}
<Icon size="L" name="ChevronDown" />
</div>
</svelte:fragment>
<MenuItem icon="UserEdit" on:click={requestChangeDetails}>
My profile
</MenuItem>
<MenuItem icon="LockClosed" on:click={requestChangePassword}>
Update password
</MenuItem>
<MenuItem icon="Apps" on:click={goToPortal}>Go to portal</MenuItem>
<MenuItem icon="LogOut" on:click={authStore.actions.logOut}>
Log out
</MenuItem>
</ActionMenu>
{/if}
<style>
.container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: var(--spacing-xs);
transition: filter 130ms ease-out;
}
.text {
flex-direction: column;
align-items: flex-start;
justify-content: center;
color: var(--navTextColor);
display: flex;
}
.name {
font-weight: 600;
font-size: 14px;
}
.container:hover {
cursor: pointer;
filter: brightness(110%);
}
</style>

View File

@ -1,6 +1,7 @@
import ClientApp from "./components/ClientApp.svelte" import ClientApp from "./components/ClientApp.svelte"
import UpdatingApp from "./components/UpdatingApp.svelte" import UpdatingApp from "./components/UpdatingApp.svelte"
import { import {
authStore,
builderStore, builderStore,
appStore, appStore,
blockStore, blockStore,
@ -80,6 +81,7 @@ export interface SDK {
ActionTypes: typeof ActionTypes ActionTypes: typeof ActionTypes
fetchDatasourceSchema: any fetchDatasourceSchema: any
generateGoldenSample: any generateGoldenSample: any
authStore: typeof authStore
builderStore: Readable<{ builderStore: Readable<{
inBuilder: boolean inBuilder: boolean
}> & { }> & {

View File

@ -1,10 +1,21 @@
import { API } from "@/api" import { API } from "@/api"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import {
AppSelfResponse,
ContextUserMetadata,
GetGlobalSelfResponse,
} from "@budibase/types"
type AuthState = ContextUserMetadata | GetGlobalSelfResponse | null
const createAuthStore = () => { const createAuthStore = () => {
const store = writable<{ const store = writable<AuthState>(null)
csrfToken?: string
} | null>(null) const hasAppSelfUser = (
user: AppSelfResponse | null
): user is ContextUserMetadata => {
return user != null && "_id" in user
}
// Fetches the user object if someone is logged in and has reloaded the page // Fetches the user object if someone is logged in and has reloaded the page
const fetchUser = async () => { const fetchUser = async () => {
@ -21,7 +32,10 @@ const createAuthStore = () => {
// Then try and get the user for this app to provide via context // Then try and get the user for this app to provide via context
try { try {
appSelf = await API.fetchSelf() const res = await API.fetchSelf()
if (hasAppSelfUser(res)) {
appSelf = res
}
} catch (error) { } catch (error) {
// Swallow // Swallow
} }