Browse Source

fix(nocohub): Added profile page, added xlarge type to usericon, added useUsers store and now call me api on app startup and overwrite things set by jwt payload, made profile page the default

pull/6376/head
Muhammed Mustafa 1 year ago
parent
commit
12ca937e4c
  1. 113
      packages/nc-gui/components/account/Profile.vue
  2. 4
      packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue
  3. 30
      packages/nc-gui/components/general/UserIcon.vue
  4. 1
      packages/nc-gui/composables/useGlobal/actions.ts
  5. 1
      packages/nc-gui/composables/useGlobal/index.ts
  6. 1
      packages/nc-gui/lang/en.json
  7. 1
      packages/nc-gui/lib/types.ts
  8. 65
      packages/nc-gui/store/users.ts

113
packages/nc-gui/components/account/Profile.vue

@ -0,0 +1,113 @@
<script lang="ts" setup>
const { currentUser } = storeToRefs(useUsers())
const isErrored = ref(false)
const isTitleUpdating = ref(false)
const form = ref({
title: '',
email: '',
})
const { updateUserProfile } = useUsers()
const formValidator = ref()
const formRules = {
title: [
{ required: true, message: 'Name required' },
{ min: 2, message: 'Name must be at least 2 characters long' },
{ max: 60, message: 'Name must be at most 60 characters long' },
],
}
const onSubmit = async () => {
const valid = await formValidator.value.validate()
if (!valid) return
if (isTitleUpdating.value) return
isTitleUpdating.value = true
isErrored.value = false
try {
await updateUserProfile({ attrs: { display_name: form.value.title } })
} catch (e: any) {
console.error(e)
} finally {
isTitleUpdating.value = false
}
}
watch(
() => currentUser.value?.display_name,
() => {
if (!currentUser.value?.display_name) return
form.value.title = currentUser.value.display_name
form.value.email = currentUser.value.email
},
{
immediate: true,
},
)
watch(
() => form.value.title,
async () => {
try {
isErrored.value = !(await formValidator.value.validate())
} catch (e: any) {
isErrored.value = true
}
},
)
</script>
<template>
<div class="flex flex-col items-center">
<div class="flex flex-col w-150">
<div class="flex font-medium text-xl">Profile</div>
<div class="mt-5 flex flex-col border-1 rounded-2xl border-gray-200 p-6 gap-y-2">
<div class="flex font-medium text-base">Account details</div>
<div class="flex text-gray-500">Control your appearance.</div>
<div class="flex flex-row mt-4 gap-x-8">
<div class="flex h-20">
<GeneralUserIcon size="xlarge" />
</div>
<a-form ref="formValidator" layout="vertical" no-style :model="form" class="w-full" @finish="onSubmit">
<div class="text-gray-800 mb-1.5">Name</div>
<a-form-item name="title" :rules="formRules.title">
<a-input
v-model:value="form.title"
class="w-full !rounded-md !py-1.5"
placeholder="Name"
data-testid="nc-account-settings-rename-input"
/>
</a-form-item>
<div class="text-gray-800 mb-1.5">Account Email ID</div>
<a-input
v-model:value="form.email"
class="w-full !rounded-md !py-1.5"
placeholder="Email"
:disabled="true"
data-testid="nc-account-settings-email-input"
/>
<div class="flex flex-row w-full justify-end mt-8">
<NcButton
type="primary"
html-type="submit"
:disabled="isErrored || (form.title && form.title === currentUser?.display_name)"
:loading="isTitleUpdating"
data-testid="nc-account-settings-save"
@click="onSubmit"
>
<template #loading> Saving </template>
Save
</NcButton>
</div>
</a-form>
</div>
</div>
</div>
</div>
</template>

4
packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue

@ -9,7 +9,7 @@ const { leftSidebarState } = storeToRefs(useSidebarStore())
const { copy } = useCopy(true) const { copy } = useCopy(true)
const name = computed(() => `${user.value?.firstname ?? ''} ${user.value?.lastname ?? ''}`.trim()) const name = computed(() => user.value?.display_name?.trim())
const isMenuOpen = ref(false) const isMenuOpen = ref(false)
@ -133,7 +133,7 @@ onMounted(() => {
<template v-if="isAuthTokenCopied"> Copied Auth Token </template> <template v-if="isAuthTokenCopied"> Copied Auth Token </template>
<template v-else> Copy Auth Token </template> <template v-else> Copy Auth Token </template>
</NcMenuItem> </NcMenuItem>
<nuxt-link v-e="['c:navbar:user:email']" class="!no-underline" to="/account/tokens"> <nuxt-link v-e="['c:navbar:user:email']" class="!no-underline" to="/account/profile">
<NcMenuItem><GeneralIcon icon="settings" class="menu-icon" /> Account Settings</NcMenuItem> <NcMenuItem><GeneralIcon icon="settings" class="menu-icon" /> Account Settings</NcMenuItem>
</nuxt-link> </nuxt-link>
</NcMenu> </NcMenu>

30
packages/nc-gui/components/general/UserIcon.vue

@ -1,26 +1,26 @@
<script lang="ts" setup> <script lang="ts" setup>
const props = defineProps<{ const props = defineProps<{
hideLabel?: boolean size?: 'small' | 'medium' | 'large' | 'xlarge'
size?: 'small' | 'medium'
}>() }>()
const { user } = useGlobal() const { currentUser } = storeToRefs(useUsers())
const backgroundColor = computed(() => (user.value?.id ? stringToColour(user.value?.id) : '#FFFFFF')) const backgroundColor = computed(() => (currentUser.value?.id ? stringToColour(currentUser.value?.id) : '#FFFFFF'))
const size = computed(() => props.size || 'medium') const size = computed(() => props.size || 'medium')
const firstName = computed(() => user.value?.firstname ?? '') const displayName = computed(() => currentUser.value?.display_name ?? '')
const lastName = computed(() => user.value?.lastname ?? '')
const email = computed(() => user.value?.email ?? '') const email = computed(() => currentUser.value?.email ?? '')
const usernameInitials = computed(() => { const usernameInitials = computed(() => {
if (firstName.value && lastName.value) { if (displayName.value) {
return firstName.value[0] + lastName.value[0] const displayNameSplit = displayName.value.split(' ')
} else if (firstName.value) { if (displayNameSplit.length > 1) {
return firstName.value[0] + (firstName.value.length > 1 ? firstName.value[1] : '') return displayNameSplit[0][0] + displayNameSplit[1][0]
} else if (lastName.value) { } else {
return lastName.value[0] + (lastName.value.length > 1 ? lastName.value[1] : '') return displayName.value[0] + displayName.value[1]
}
} else { } else {
return email.value[0] + email.value[1] return email.value[0] + email.value[1]
} }
@ -33,12 +33,12 @@ const usernameInitials = computed(() => {
:class="{ :class="{
'min-w-4 min-h-4': size === 'small', 'min-w-4 min-h-4': size === 'small',
'min-w-6 min-h-6': size === 'medium', 'min-w-6 min-h-6': size === 'medium',
'min-w-20 min-h-20 !text-3xl': size === 'large',
'min-w-26 min-h-26 !text-4xl': size === 'xlarge',
}" }"
:style="{ backgroundColor }" :style="{ backgroundColor }"
> >
<template v-if="!props.hideLabel">
{{ usernameInitials }} {{ usernameInitials }}
</template>
</div> </div>
</template> </template>

1
packages/nc-gui/composables/useGlobal/actions.ts

@ -38,6 +38,7 @@ export function useGlobalActions(state: State): Actions {
firstname: state.jwtPayload.value.firstname, firstname: state.jwtPayload.value.firstname,
lastname: state.jwtPayload.value.lastname, lastname: state.jwtPayload.value.lastname,
roles: state.jwtPayload.value.roles, roles: state.jwtPayload.value.roles,
display_name: state.jwtPayload.value.display_name,
} }
} }
} }

1
packages/nc-gui/composables/useGlobal/index.ts

@ -71,6 +71,7 @@ export const useGlobal = createGlobalState((): UseGlobalReturn => {
firstname: nextPayload.firstname, firstname: nextPayload.firstname,
lastname: nextPayload.lastname, lastname: nextPayload.lastname,
roles: nextPayload.roles, roles: nextPayload.roles,
display_name: nextPayload.display_name,
} }
} }
}, },

1
packages/nc-gui/lang/en.json

@ -289,6 +289,7 @@
"createdOn": "Created On", "createdOn": "Created On",
"notifyVia": "Notify Via", "notifyVia": "Notify Via",
"projName": "Project name", "projName": "Project name",
"profile": "Profile",
"tableName": "Table name", "tableName": "Table name",
"dashboardName": "Dashboard name", "dashboardName": "Dashboard name",
"viewName": "View name", "viewName": "View name",

1
packages/nc-gui/lib/types.ts

@ -24,6 +24,7 @@ interface User {
workspace_roles: Roles | string workspace_roles: Roles | string
invite_token?: string invite_token?: string
project_id?: string project_id?: string
display_name?: string | null
} }
interface ProjectMetaInfo { interface ProjectMetaInfo {

65
packages/nc-gui/store/users.ts

@ -0,0 +1,65 @@
import { acceptHMRUpdate, defineStore } from 'pinia'
export const useUsers = defineStore('userStore', () => {
const { api } = useApi()
const { user } = useGlobal()
const currentUser = computed({
get: () => user.value,
set: (value) => {
user.value = value
},
})
const updateUserProfile = async ({
attrs,
}: {
attrs: {
display_name?: string
}
}) => {
if (!user.value) throw new Error('User is not defined')
await api.userProfile.update(attrs)
user.value = {
...user.value,
...attrs,
}
}
const loadCurrentUser = async () => {
const res = await api.auth.me()
user.value = {
...user.value,
...res,
roles: res.roles,
project_roles: res.project_roles,
workspace_roles: res.workspace_roles,
}
}
watch(
() => user.value?.id,
(newId, oldId) => {
if (!newId) return
if (newId === oldId) return
loadCurrentUser()
},
{
immediate: true,
},
)
return {
loadCurrentUser,
updateUserProfile,
currentUser,
}
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUsers as any, import.meta.hot))
}
Loading…
Cancel
Save