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 align = "left"
export let portalTarget
export let portalTarget = undefined
export let openOnHover = false
export let animate
export let offset
export let animate = true
export let offset = undefined
const actionMenuContext = getContext("actionMenu")

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
<script>
import { getContext, setContext } from "svelte"
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 NavItem from "./NavItem.svelte"
import { UserAvatar } from "@budibase/frontend-core"
import UserMenu from "./UserMenu.svelte"
const sdk = getContext("sdk")
const {
@ -14,7 +14,6 @@
builderStore,
sidePanelStore,
modalStore,
authStore,
} = sdk
const context = getContext("context")
const navStateStore = writable({})
@ -159,11 +158,6 @@
return !url.startsWith("http") ? `http://${url}` : url
}
const navigateToPortal = () => {
if ($builderStore.inBuilder) return
window.location.href = "/builder/apps"
}
const getScreenXOffset = (navigation, mobile) => {
if (navigation !== "Left") {
return 0
@ -276,14 +270,9 @@
<Heading size="S" {textAlign}>{title}</Heading>
{/if}
</div>
{#if !embedded && $authStore}
{#if !embedded}
<div class="user top">
<UserAvatar
user={$authStore}
size="M"
tooltipPosition={TooltipPosition.Left}
/>
<Icon name="ChevronDown" />
<UserMenu compact />
</div>
{/if}
</div>
@ -309,16 +298,9 @@
{/each}
</div>
{/if}
{#if !embedded && $authStore}
{#if !embedded}
<div class="user left">
<UserAvatar user={$authStore} size="M" showTooltip={false} />
<div class="text">
<div class="name">
{$authStore.firstName}
{$authStore.lastName}
</div>
</div>
<Icon name="ChevronDown" />
<UserMenu />
</div>
{/if}
</div>
@ -560,34 +542,6 @@
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 */
.nav--left {
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 UpdatingApp from "./components/UpdatingApp.svelte"
import {
authStore,
builderStore,
appStore,
blockStore,
@ -80,6 +81,7 @@ export interface SDK {
ActionTypes: typeof ActionTypes
fetchDatasourceSchema: any
generateGoldenSample: any
authStore: typeof authStore
builderStore: Readable<{
inBuilder: boolean
}> & {

View File

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