Browse Source

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

Nc feat/empty state
pull/7646/head
Raju Udava 10 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. 70
      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,
})
const isLoadingAllTokens = ref(true)
const setDefaultTokenName = () => {
selectedTokenData.value.description = extractNextDefaultName(
[...allTokens.value.map((el) => el?.description || '')],
@ -94,7 +96,7 @@ const updateAllTokens = (type: 'delete' | 'add', token: IApiTokenInfo) => {
setDefaultTokenName()
}
const loadTokens = async (page = currentPage.value, limit = currentLimit.value) => {
const loadTokens = async (page = currentPage.value, limit = currentLimit.value, hideShowNewToken = false) => {
currentPage.value = page
try {
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,
},
} as RequestParams)
if (!response) return
if (!response) {
isLoadingAllTokens.value = false
return
}
pagination.total = response.pageInfo.totalRows ?? 0
pagination.pageSize = 10
tokens.value = response.list as IApiTokenInfo[]
if (hideShowNewToken) {
showNewTokenModal.value = false
selectedTokenData.value = {}
}
if (!allTokens.value.length) {
await loadAllTokens(pagination.total)
}
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
} finally {
if (isLoadingAllTokens.value) {
isLoadingAllTokens.value = false
}
}
}
@ -159,11 +173,10 @@ const generateToken = async () => {
if (!isValidTokenName.value) return
try {
const token = await api.orgTokens.create(selectedTokenData.value)
showNewTokenModal.value = false
// Token generated successfully
// message.success(t('msg.success.tokenGenerated'))
selectedTokenData.value = {}
await loadTokens()
await loadTokens(currentPage.value, currentLimit.value, true)
updateAllTokens('add', token as IApiTokenInfo)
} 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="py-2 flex gap-4 items-baseline justify-between">
<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>
<NcButton
:disabled="showNewTokenModal || (isEeUI && tokens.length)"
@ -237,7 +250,7 @@ const handleCancel = () => {
</NcTooltip>
</div>
<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="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>
@ -268,6 +281,7 @@ const handleCancel = () => {
class="!rounded-lg !py-1"
placeholder="Token Name"
data-testid="nc-token-input"
:disabled="isLoading"
@press-enter="generateToken"
/>
<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">
{{ $t('general.cancel') }}
</NcButton>
<NcButton
type="primary"
size="sm"
:is-loading="isLoading"
data-testid="nc-token-save-btn"
@click="generateToken"
>
<NcButton type="primary" size="sm" :loading="isLoading" data-testid="nc-token-save-btn" @click="generateToken">
{{ $t('general.save') }}
</NcButton>
</div>
@ -329,15 +337,15 @@ const handleCancel = () => {
@click="hideOrShowToken(el.token as string)"
/>
</NcTooltip>
<NcTooltip placement="top" class="h-4">
<NcTooltip placement="top">
<template #title>{{ $t('general.copy') }}</template>
<component
: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)"
/>
</NcTooltip>
<NcTooltip placement="top" class="mb-0.5">
<NcTooltip placement="top">
<template #title>{{ $t('general.delete') }}</template>
<component
:is="iconMap.delete"
@ -351,6 +359,25 @@ const handleCancel = () => {
</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">
<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"
/>
</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') }}
</span>
</div>
</a-select-option>
<a-select-option
@ -292,9 +292,9 @@ const openDeleteModal = (user: UserType) => {
class="w-4 h-4 text-primary"
/>
</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') }}
</span>
</div>
</a-select-option>
</NcSelect>
<div v-else class="font-weight-bold" data-rec="true">
@ -345,6 +345,18 @@ const openDeleteModal = (user: UserType) => {
</div>
</span>
</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>
</div>
<div v-if="pagination.total > 10" class="flex items-center justify-center mt-4">

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

@ -121,44 +121,56 @@ const onCreateBaseClick = () => {
</div>
</component>
</div>
<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-1/5">{{ $t('general.source') }}</div>
<div class="w-1/5">{{ $t('labels.createdOn') }}</div>
</div>
<div
class="nc-base-view-all-table-list nc-scrollbar-md"
:style="{
height: 'calc(100vh - var(--topbar-height) - 18rem)',
}"
>
<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="w-2/5">{{ $t('objects.table') }}</div>
<div class="w-1/5">{{ $t('general.source') }}</div>
<div class="w-1/5">{{ $t('labels.createdOn') }}</div>
</div>
<div
v-for="table in [...activeTables].sort(
class="nc-base-view-all-table-list nc-scrollbar-md"
:style="{
height: 'calc(100vh - var(--topbar-height) - 18rem)',
}"
>
<div
v-for="table in [...activeTables].sort(
(a, b) => a.source_id!.localeCompare(b.source_id!) * 20
)"
:key="table.id"
class="py-4 flex flex-row w-full cursor-pointer hover:bg-gray-100 border-b-1 border-gray-100 px-2.25"
data-testid="proj-view-list__item"
@click="openTable(table)"
>
<div class="flex flex-row w-2/5 items-center gap-x-2" data-testid="proj-view-list__item-title">
<div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="table" class="text-gray-500" />
:key="table.id"
class="py-4 flex flex-row w-full cursor-pointer hover:bg-gray-100 border-b-1 border-gray-100 px-2.25"
data-testid="proj-view-list__item"
@click="openTable(table)"
>
<div class="flex flex-row w-2/5 items-center gap-x-2" data-testid="proj-view-list__item-title">
<div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="table" class="text-gray-500" />
</div>
{{ table?.title }}
</div>
{{ table?.title }}
</div>
<div class="w-1/5 text-gray-600" data-testid="proj-view-list__item-type">
<div v-if="table.source_id === defaultBase?.id" class="ml-0.75">-</div>
<div v-else class="capitalize flex flex-row items-center gap-x-0.5">
<GeneralBaseLogo class="w-4 mr-1" />
{{ sources.get(table.source_id!)?.alias }}
<div class="w-1/5 text-gray-600" data-testid="proj-view-list__item-type">
<div v-if="table.source_id === defaultBase?.id" class="ml-0.75">-</div>
<div v-else class="capitalize flex flex-row items-center gap-x-0.5">
<GeneralBaseLogo class="w-4 mr-1" />
{{ sources.get(table.source_id!)?.alias }}
</div>
</div>
<div class="w-1/5 text-gray-400 ml-0.25" data-testid="proj-view-list__item-created-at">
{{ dayjs(table?.created_at).fromNow() }}
</div>
</div>
<div class="w-1/5 text-gray-400 ml-0.25" data-testid="proj-view-list__item-created-at">
{{ dayjs(table?.created_at).fromNow() }}
</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" />
<LazyDashboardSettingsDataSourcesCreateBase v-model:open="isNewBaseModalOpen" />
</div>

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

@ -1254,7 +1254,7 @@ watch(
@add="onFieldAdd"
/>
<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-center text-sm px-2 text-gray-500 pt-6">
{{ $t('labels.multiField.selectFieldLabel') }}

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

@ -197,13 +197,10 @@ watch(
</NcButton>
</div>
<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">
<GeneralIcon icon="webhook" class="flex text-5xl h-10" style="-webkit-text-stroke: 0.5px" />
<div class="flex text-gray-600 font-medium text-lg">{{ $t('msg.createWebhookMsg1') }}</div>
<div class="flex flex-col items-center">
<div class="flex">{{ $t('msg.createWebhookMsg2') }}</div>
<div class="flex">{{ $t('msg.createWebhookMsg3') }}</div>
</div>
<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">
<img src="~assets/img/placeholder/webhooks.png" class="!w-[24rem] flex-none" />
<div class="text-gray-700 font-bold text-2xl">{{ $t('msg.createWebhookMsg1') }}</div>
<div class="text-gray-700 max-w-[24rem]">{{ $t('msg.createWebhookMsg2') }}</div>
<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">
<span class="ml-1">{{ $t('activity.newWebhook') }}</span>

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

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

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

@ -152,6 +152,15 @@ onMounted(async () => {
</NcDropdown>
</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>

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

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation",
"linkMore": "Link More",
"linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File",
"renameTable": "Rename Table",
"renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find record by scanning a QR or Barcode",
"tokenManagement": "Token Management",
"addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password",
"tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration",
"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": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000",
"decimal8": "1.00000000",
"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": {
"clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "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",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.",

Loading…
Cancel
Save