Browse Source

Merge pull request #7634 from nocodb/nc-feat/empty-state

Nc feat/empty state
pull/7646/head
Raju Udava 9 months ago committed by GitHub
parent
commit
620bd3d94e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. BIN
      packages/nc-gui/assets/img/placeholder/api-tokens.png
  2. BIN
      packages/nc-gui/assets/img/placeholder/invite-team.png
  3. BIN
      packages/nc-gui/assets/img/placeholder/link-records.png
  4. BIN
      packages/nc-gui/assets/img/placeholder/multi-field-editor.png
  5. BIN
      packages/nc-gui/assets/img/placeholder/table.png
  6. BIN
      packages/nc-gui/assets/img/placeholder/webhooks.png
  7. 61
      packages/nc-gui/components/account/Token.vue
  8. 20
      packages/nc-gui/components/account/UserList.vue
  9. 12
      packages/nc-gui/components/project/AllTables.vue
  10. 2
      packages/nc-gui/components/smartsheet/details/Fields.vue
  11. 11
      packages/nc-gui/components/smartsheet/details/Webhooks.vue
  12. 16
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  13. 9
      packages/nc-gui/components/workspace/CollaboratorsList.vue
  14. 18
      packages/nc-gui/lang/en.json

BIN
packages/nc-gui/assets/img/placeholder/api-tokens.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
packages/nc-gui/assets/img/placeholder/invite-team.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 KiB

BIN
packages/nc-gui/assets/img/placeholder/link-records.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
packages/nc-gui/assets/img/placeholder/multi-field-editor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
packages/nc-gui/assets/img/placeholder/table.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
packages/nc-gui/assets/img/placeholder/webhooks.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

61
packages/nc-gui/components/account/Token.vue

@ -45,6 +45,8 @@ const pagination = reactive({
pageSize: 10, pageSize: 10,
}) })
const isLoadingAllTokens = ref(true)
const setDefaultTokenName = () => { const setDefaultTokenName = () => {
selectedTokenData.value.description = extractNextDefaultName( selectedTokenData.value.description = extractNextDefaultName(
[...allTokens.value.map((el) => el?.description || '')], [...allTokens.value.map((el) => el?.description || '')],
@ -94,7 +96,7 @@ const updateAllTokens = (type: 'delete' | 'add', token: IApiTokenInfo) => {
setDefaultTokenName() setDefaultTokenName()
} }
const loadTokens = async (page = currentPage.value, limit = currentLimit.value) => { const loadTokens = async (page = currentPage.value, limit = currentLimit.value, hideShowNewToken = false) => {
currentPage.value = page currentPage.value = page
try { try {
const response: any = await api.orgTokens.list({ const response: any = await api.orgTokens.list({
@ -103,18 +105,30 @@ const loadTokens = async (page = currentPage.value, limit = currentLimit.value)
offset: searchText.value.length === 0 ? (page - 1) * limit : 0, offset: searchText.value.length === 0 ? (page - 1) * limit : 0,
}, },
} as RequestParams) } as RequestParams)
if (!response) return if (!response) {
isLoadingAllTokens.value = false
return
}
pagination.total = response.pageInfo.totalRows ?? 0 pagination.total = response.pageInfo.totalRows ?? 0
pagination.pageSize = 10 pagination.pageSize = 10
tokens.value = response.list as IApiTokenInfo[] tokens.value = response.list as IApiTokenInfo[]
if (hideShowNewToken) {
showNewTokenModal.value = false
selectedTokenData.value = {}
}
if (!allTokens.value.length) { if (!allTokens.value.length) {
await loadAllTokens(pagination.total) await loadAllTokens(pagination.total)
} }
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} finally {
if (isLoadingAllTokens.value) {
isLoadingAllTokens.value = false
}
} }
} }
@ -159,11 +173,10 @@ const generateToken = async () => {
if (!isValidTokenName.value) return if (!isValidTokenName.value) return
try { try {
const token = await api.orgTokens.create(selectedTokenData.value) const token = await api.orgTokens.create(selectedTokenData.value)
showNewTokenModal.value = false
// Token generated successfully // Token generated successfully
// message.success(t('msg.success.tokenGenerated')) // message.success(t('msg.success.tokenGenerated'))
selectedTokenData.value = {} await loadTokens(currentPage.value, currentLimit.value, true)
await loadTokens()
updateAllTokens('add', token as IApiTokenInfo) updateAllTokens('add', token as IApiTokenInfo)
} catch (e: any) { } catch (e: any) {
@ -216,7 +229,7 @@ const handleCancel = () => {
<div class="max-w-202 mx-auto px-4 h-full" data-testid="nc-token-list"> <div class="max-w-202 mx-auto px-4 h-full" data-testid="nc-token-list">
<div class="py-2 flex gap-4 items-baseline justify-between"> <div class="py-2 flex gap-4 items-baseline justify-between">
<h6 class="text-2xl text-left font-bold" data-rec="true">{{ $t('title.apiTokens') }}</h6> <h6 class="text-2xl text-left font-bold" data-rec="true">{{ $t('title.apiTokens') }}</h6>
<NcTooltip :disabled="!(isEeUI && tokens.length)"> <NcTooltip v-if="tokens.length" :disabled="!(isEeUI && tokens.length)">
<template #title>{{ $t('labels.tokenLimit') }}</template> <template #title>{{ $t('labels.tokenLimit') }}</template>
<NcButton <NcButton
:disabled="showNewTokenModal || (isEeUI && tokens.length)" :disabled="showNewTokenModal || (isEeUI && tokens.length)"
@ -237,7 +250,7 @@ const handleCancel = () => {
</NcTooltip> </NcTooltip>
</div> </div>
<span data-rec="true">{{ $t('msg.apiTokenCreate') }}</span> <span data-rec="true">{{ $t('msg.apiTokenCreate') }}</span>
<div class="mt-5 h-[calc(100%-13rem)]"> <div v-if="!isLoadingAllTokens && (tokens.length || showNewTokenModal)" class="mt-5 h-[calc(100%-13rem)]">
<div class="h-full w-full !overflow-hidden rounded-md"> <div class="h-full w-full !overflow-hidden rounded-md">
<div class="flex w-full pl-5 bg-gray-50 border-1 rounded-t-md"> <div class="flex w-full pl-5 bg-gray-50 border-1 rounded-t-md">
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9" data-rec="true">{{ $t('title.tokenName') }}</span> <span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9" data-rec="true">{{ $t('title.tokenName') }}</span>
@ -268,6 +281,7 @@ const handleCancel = () => {
class="!rounded-lg !py-1" class="!rounded-lg !py-1"
placeholder="Token Name" placeholder="Token Name"
data-testid="nc-token-input" data-testid="nc-token-input"
:disabled="isLoading"
@press-enter="generateToken" @press-enter="generateToken"
/> />
<span v-if="!isValidTokenName" class="text-red-500 text-xs font-light mt-1.5 ml-1" data-rec="true" <span v-if="!isValidTokenName" class="text-red-500 text-xs font-light mt-1.5 ml-1" data-rec="true"
@ -278,13 +292,7 @@ const handleCancel = () => {
<NcButton v-if="!isLoading" type="secondary" size="small" @click="handleCancel"> <NcButton v-if="!isLoading" type="secondary" size="small" @click="handleCancel">
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</NcButton> </NcButton>
<NcButton <NcButton type="primary" size="sm" :loading="isLoading" data-testid="nc-token-save-btn" @click="generateToken">
type="primary"
size="sm"
:is-loading="isLoading"
data-testid="nc-token-save-btn"
@click="generateToken"
>
{{ $t('general.save') }} {{ $t('general.save') }}
</NcButton> </NcButton>
</div> </div>
@ -329,15 +337,15 @@ const handleCancel = () => {
@click="hideOrShowToken(el.token as string)" @click="hideOrShowToken(el.token as string)"
/> />
</NcTooltip> </NcTooltip>
<NcTooltip placement="top" class="h-4"> <NcTooltip placement="top">
<template #title>{{ $t('general.copy') }}</template> <template #title>{{ $t('general.copy') }}</template>
<component <component
:is="iconMap.copy" :is="iconMap.copy"
class="hover::cursor-pointer w-4 h-4 text-gray-600 mt-0.25" class="hover::cursor-pointer w-4 h-4 text-gray-600"
@click="copyToken(el.token)" @click="copyToken(el.token)"
/> />
</NcTooltip> </NcTooltip>
<NcTooltip placement="top" class="mb-0.5"> <NcTooltip placement="top">
<template #title>{{ $t('general.delete') }}</template> <template #title>{{ $t('general.delete') }}</template>
<component <component
:is="iconMap.delete" :is="iconMap.delete"
@ -351,6 +359,25 @@ const handleCancel = () => {
</div> </div>
</div> </div>
</div> </div>
<div
v-else-if="!isLoadingAllTokens && !tokens.length && !showNewTokenModal"
class="max-w-[40rem] border px-3 py-6 flex flex-col items-center justify-center gap-6 text-center"
>
<img src="~assets/img/placeholder/api-tokens.png" class="!w-[22rem] flex-none" />
<div class="text-2xl text-gray-800 font-bold">{{ $t('placeholder.noTokenCreated') }}</div>
<div class="text-sm text-gray-700">
{{ $t('placeholder.noTokenCreatedLabel') }}
</div>
<NcButton class="!rounded-lg !py-3 !h-10" data-testid="nc-token-create" type="primary" @click="showNewTokenModal = true">
<span class="hidden md:block" data-rec="true">
{{ $t('title.createNewToken') }}
</span>
<span class="flex items-center justify-center md:hidden" data-rec="true">
<component :is="iconMap.plus" />
</span>
</NcButton>
</div>
<div v-if="pagination.total > 10" class="flex items-center justify-center mt-5"> <div v-if="pagination.total > 10" class="flex items-center justify-center mt-5">
<a-pagination <a-pagination

20
packages/nc-gui/components/account/UserList.vue

@ -273,9 +273,9 @@ const openDeleteModal = (user: UserType) => {
class="w-4 h-4 text-primary" class="w-4 h-4 text-primary"
/> />
</div> </div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true"> <div class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgCreator') }} {{ $t('msg.info.roles.orgCreator') }}
</span> </div>
</a-select-option> </a-select-option>
<a-select-option <a-select-option
@ -292,9 +292,9 @@ const openDeleteModal = (user: UserType) => {
class="w-4 h-4 text-primary" class="w-4 h-4 text-primary"
/> />
</div> </div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true"> <div class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgViewer') }} {{ $t('msg.info.roles.orgViewer') }}
</span> </div>
</a-select-option> </a-select-option>
</NcSelect> </NcSelect>
<div v-else class="font-weight-bold" data-rec="true"> <div v-else class="font-weight-bold" data-rec="true">
@ -345,6 +345,18 @@ const openDeleteModal = (user: UserType) => {
</div> </div>
</span> </span>
</div> </div>
<div
v-if="sortedUsers.length === 1"
class="user pt-12 pb-4 px-2 flex flex-col items-center gap-6 text-center border-b-1 border-l-1 border-r-1"
>
<div class="text-2xl text-gray-800 font-bold">
{{ $t('placeholder.inviteYourTeam') }}
</div>
<div class="text-sm text-gray-700">
{{ $t('placeholder.inviteYourTeamLabel') }}
</div>
<img src="~assets/img/placeholder/invite-team.png" class="!w-[30rem] flex-none" />
</div>
</section> </section>
</div> </div>
<div v-if="pagination.total > 10" class="flex items-center justify-center mt-4"> <div v-if="pagination.total > 10" class="flex items-center justify-center mt-4">

12
packages/nc-gui/components/project/AllTables.vue

@ -121,6 +121,7 @@ const onCreateBaseClick = () => {
</div> </div>
</component> </component>
</div> </div>
<template v-if="activeTables.length">
<div class="flex flex-row w-full text-gray-400 border-b-1 border-gray-50 py-3 px-2.5"> <div class="flex flex-row w-full text-gray-400 border-b-1 border-gray-50 py-3 px-2.5">
<div class="w-2/5">{{ $t('objects.table') }}</div> <div class="w-2/5">{{ $t('objects.table') }}</div>
<div class="w-1/5">{{ $t('general.source') }}</div> <div class="w-1/5">{{ $t('general.source') }}</div>
@ -159,6 +160,17 @@ const onCreateBaseClick = () => {
</div> </div>
</div> </div>
</div> </div>
</template>
<div v-else class="py-3 flex items-center gap-6 <lg:flex-col">
<img src="~assets/img/placeholder/table.png" class="!w-[23rem] flex-none" />
<div class="text-center lg:text-left">
<div class="text-2xl text-gray-800 font-bold">{{ $t('placeholder.createTable') }}</div>
<div class="text-sm text-gray-700 pt-6">
{{ $t('placeholder.createTableLabel') }}
</div>
</div>
</div>
<ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :source="defaultBase" /> <ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :source="defaultBase" />
<LazyDashboardSettingsDataSourcesCreateBase v-model:open="isNewBaseModalOpen" /> <LazyDashboardSettingsDataSourcesCreateBase v-model:open="isNewBaseModalOpen" />
</div> </div>

2
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -1254,7 +1254,7 @@ watch(
@add="onFieldAdd" @add="onFieldAdd"
/> />
<div v-else class="w-[25rem] flex flex-col justify-center p-4 items-center"> <div v-else class="w-[25rem] flex flex-col justify-center p-4 items-center">
<img src="~assets/img/fieldPlaceholder.svg" class="!w-[18rem]" /> <img src="~assets/img/placeholder/multi-field-editor.png" class="!w-[18rem]" />
<div class="text-2xl text-gray-600 font-bold text-center pt-6">{{ $t('labels.multiField.selectField') }}</div> <div class="text-2xl text-gray-600 font-bold text-center pt-6">{{ $t('labels.multiField.selectField') }}</div>
<div class="text-center text-sm px-2 text-gray-500 pt-6"> <div class="text-center text-sm px-2 text-gray-500 pt-6">
{{ $t('labels.multiField.selectFieldLabel') }} {{ $t('labels.multiField.selectFieldLabel') }}

11
packages/nc-gui/components/smartsheet/details/Webhooks.vue

@ -197,13 +197,10 @@ watch(
</NcButton> </NcButton>
</div> </div>
<div v-if="!selectedHookId && !isDraftMode" class="flex flex-col h-full w-full items-center"> <div v-if="!selectedHookId && !isDraftMode" class="flex flex-col h-full w-full items-center">
<div v-if="hooks.length === 0" class="flex flex-col px-1.5 py-2.5 ml-1 h-full justify-center items-center gap-y-6"> <div v-if="hooks.length === 0" class="flex flex-col px-1.5 py-2.5 ml-1 h-full items-center gap-y-6 text-center">
<GeneralIcon icon="webhook" class="flex text-5xl h-10" style="-webkit-text-stroke: 0.5px" /> <img src="~assets/img/placeholder/webhooks.png" class="!w-[24rem] flex-none" />
<div class="flex text-gray-600 font-medium text-lg">{{ $t('msg.createWebhookMsg1') }}</div> <div class="text-gray-700 font-bold text-2xl">{{ $t('msg.createWebhookMsg1') }}</div>
<div class="flex flex-col items-center"> <div class="text-gray-700 max-w-[24rem]">{{ $t('msg.createWebhookMsg2') }}</div>
<div class="flex">{{ $t('msg.createWebhookMsg2') }}</div>
<div class="flex">{{ $t('msg.createWebhookMsg3') }}</div>
</div>
<NcButton v-e="['c:actions:webhook']" class="flex max-w-40" type="primary" @click="createWebhook()"> <NcButton v-e="['c:actions:webhook']" class="flex max-w-40" type="primary" @click="createWebhook()">
<div class="flex flex-row items-center justify-between w-full"> <div class="flex flex-row items-center justify-between w-full">
<span class="ml-1">{{ $t('activity.newWebhook') }}</span> <span class="ml-1">{{ $t('activity.newWebhook') }}</span>

16
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -14,7 +14,6 @@ import {
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
useVModel, useVModel,
} from '#imports' } from '#imports'
import InboxIcon from '~icons/nc-icons/inbox'
interface Prop { interface Prop {
modelValue?: boolean modelValue?: boolean
@ -300,19 +299,20 @@ onUnmounted(() => {
</template> </template>
</div> </div>
</div> </div>
<div v-else class="pt-1 flex flex-col gap-3 my-auto items-center justify-center text-gray-500"> <div v-else class="pt-1 flex flex-col gap-4 my-auto items-center justify-center text-gray-500 text-center">
<InboxIcon class="w-16 h-16 mx-auto" /> <img src="~assets/img/placeholder/link-records.png" class="!w-[18.5rem] flex-none" />
<p> <div class="text-2xl text-gray-700 font-bold">{{ $t('msg.noLinkedRecords') }}</div>
{{ $t('msg.noRecordsAreLinkedFromTable') }} <div class="text-gray-700">
{{ relatedTableMeta?.title }} {{ $t('msg.clickLinkRecordsToAddLinkFromTable', { tableName: relatedTableMeta?.title }) }}
</p> </div>
<NcButton <NcButton
v-if="!readOnly && childrenListCount < 1" v-if="!readOnly && childrenListCount < 1"
v-e="['c:links:link']" v-e="['c:links:link']"
data-testid="nc-child-list-button-link-to" data-testid="nc-child-list-button-link-to"
@click="emit('attachRecord')" @click="emit('attachRecord')"
> >
<div class="flex items-center gap-1"><MdiPlus /> {{ $t('title.linkMoreRecords') }}</div> <div class="flex items-center gap-1"><MdiPlus /> {{ $t('title.linkRecords') }}</div>
</NcButton> </NcButton>
</div> </div>
</div> </div>

9
packages/nc-gui/components/workspace/CollaboratorsList.vue

@ -152,6 +152,15 @@ onMounted(async () => {
</NcDropdown> </NcDropdown>
</div> </div>
</div> </div>
<div v-if="sortedCollaborators.length === 1" class="pt-12 pb-4 px-2 flex flex-col items-center gap-6 text-center">
<div class="text-2xl text-gray-800 font-bold">
{{ $t('placeholder.inviteYourTeam') }}
</div>
<div class="text-sm text-gray-700">
{{ $t('placeholder.inviteYourTeamLabel') }}
</div>
<img src="~assets/img/placeholder/invite-team.png" class="!w-[30rem] flex-none" />
</div>
</div> </div>
</div> </div>
</div> </div>

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

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find record by scanning a QR or Barcode", "findRowByScanningCode": "Find record by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated":"No API Tokens created",
"noTokenCreatedLabel" :"Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

Loading…
Cancel
Save