Browse Source

feat: setup page

pull/9314/head
Pranav C 3 months ago
parent
commit
1c934004f5
  1. 7
      packages/nc-gui/components/account/Breadcrumb.vue
  2. 62
      packages/nc-gui/components/account/Setup.vue
  3. 115
      packages/nc-gui/components/account/setup/Email.vue
  4. 114
      packages/nc-gui/components/account/setup/Storage.vue
  5. 46
      packages/nc-gui/composables/useAccountSetupStore.ts
  6. 3
      packages/nc-gui/lang/en.json
  7. 15
      packages/nc-gui/pages/account/index.vue
  8. 1
      packages/nc-gui/pages/account/index/[page].vue
  9. 21
      packages/nc-gui/pages/account/index/setup/[[nestedPage]].vue

7
packages/nc-gui/components/account/Breadcrumb.vue

@ -44,6 +44,13 @@ const breadcrumb = computed<BreadcrumbType[]>(() => {
})
break
}
case 'setup': {
payload.push({
title: t('labels.setup'),
active: true,
})
break
}
}
switch (route.params.nestedPage) {

62
packages/nc-gui/components/account/Setup.vue

@ -0,0 +1,62 @@
<script setup lang="ts">
const { t } = useI18n()
const configs = ref([
{
title: t('labels.setupLabel', { label: t('labels.email') }),
key: 'email',
description: 'Configure an email account to send system notifications to your organisation’s users.',
docsLink: '',
path: '/account/setup/email'
},
{
title: t('labels.setupLabel', { label: t('labels.storage') }),
key: 'storage',
description: 'Configure a storage service to store your organisation’s data.',
docsLink: '',
path: '/account/setup/storage'
},
{
title: t('labels.switchToProd'),
key: 'switchToProd',
description: 'Configure a production-ready app database to port from the existing built-in application database.',
docsLink: ''
},
])
</script>
<template>
<div class="flex flex-col" data-test-id="nc-setup">
<NcPageHeader>
<template #icon>
<div class="flex justify-center items-center h-5 w-5">
<GeneralIcon icon="ncSliders" class="flex-none text-[20px]" />
</div>
</template>
<template #title>
<span data-rec="true">
{{ $t('labels.setup') }}
</span>
</template>
</NcPageHeader>
<div
class="nc-content-max-w flex-1 max-h-[calc(100vh_-_100px)] overflow-y-auto nc-scrollbar-thin flex flex-col items-center gap-6 p-6"
>
<div class="flex flex-col gap-6 w-150">
<div v-for="config of configs" class="flex flex-col border-1 rounded-2xl border-gray-200 p-6 gap-y-2">
<div class="flex font-bold text-base" data-rec="true">{{ config.title }}</div>
<div class="text-gray-600 text-tiny">{{config.description}}</div>
<div class="flex justify-between">
<NcButton size="small !text-tiny" type="text">Go to docs</NcButton>
<NcButton size="small" @click="navigateTo(config.path)">Configure</NcButton>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss"></style>

115
packages/nc-gui/components/account/setup/Email.vue

@ -0,0 +1,115 @@
<script setup lang="ts">
const { emailApps } = useAccountSetupStoreOrThrow()
</script>
<template>
<div class="w-full">
<div class="p-4 flex flex-wrap w-full gap-5 mx-auto my-2">
<div v-for="(app) in emailApps" :key="app.title" class="w-296px max-w-296px flex gap-6 border-1 border-gray-100 py-3 px-6 rounded items-center">
<img
v-if="app.title !== 'SMTP'"
class="max-w-32px max-h-32px"
alt="logo"
:style="{
backgroundColor: app.title === 'SES' ? '#242f3e' : '',
}"
:src="app.logo"
/>
<GeneralIcon v-else icon="mail" />
<span class="font-weight-bold">{{ app.title }}</span>
</div>
<!--
<a-card
v-for="(app, i) in emailApps"
:key="i"
class="sm:w-100 md:w-130"
:class="`relative flex overflow-x-hidden app-item-card !shadow-sm rounded-md w-full nc-app-store-card-${app.title}`"
>
<div class="install-btn flex flex-row justify-end space-x-1">
<a-button v-if="app.parsedInput" size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-edit">
<IcRoundEdit class="pr-0.5" :height="12" />
{{ $t('general.edit') }}
</div>
</a-button>
<a-button v-if="app.parsedInput" size="small" outlined @click="showResetPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-reset">
<component :is="iconMap.closeCircle" />
<div class="flex ml-0.5">{{ $t('general.reset') }}</div>
</div>
</a-button>
<a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install">
<component :is="iconMap.plus" />
{{ $t('general.install') }}
</div>
</a-button>
</div>
<div class="flex flex-row space-x-2 items-center justify-start w-full">
<div class="flex w-[68px]">
<img
v-if="app.title !== 'SMTP'"
class="avatar"
alt="logo"
:style="{
backgroundColor: app.title === 'SES' ? '#242f3e' : '',
}"
:src="app.logo"
/>
<div v-else />
</div>
<div class="flex flex-col flex-1 w-3/5 pl-3">
<a-typography-title :level="5">{{ app.title }}</a-typography-title>
{{ app.description }}
</div>
</div>
</a-card>-->
</div>
</div>
</template>
<style scoped lang="scss">
.app-item-card {
position: relative;
transition: 0.4s background-color;
.install-btn {
position: absolute;
opacity: 1;
right: -100%;
top: 10px;
transition: 0.4s opacity, 0.4s right;
}
&:hover .install-btn {
right: 10px;
opacity: 1;
}
}
.app-item-card {
transition: 0.4s background-color, 0.4s transform;
&:hover {
background: rgba(123, 126, 136, 0.1) !important;
}
}
.caption {
font-size: 0.7rem;
}
.avatar {
width: 5rem;
height: 5rem;
padding: 0.25rem;
object-fit: contain;
}
</style>

114
packages/nc-gui/components/account/setup/Storage.vue

@ -0,0 +1,114 @@
<script setup lang="ts">
const { storageApps } = useAccountSetupStoreOrThrow()
</script>
<template>
<div class="w-full">
<div class="p-4 flex flex-wrap w-full gap-5 mx-auto my-2">
<div v-for="(app) in storageApps" :key="app.title" class="w-296px max-w-296px flex gap-6 border-1 border-gray-100 py-3 px-6 rounded items-center">
<img
v-if="app.title !== 'SMTP'"
class="max-w-32px max-h-32px"
alt="logo"
:style="{
backgroundColor: app.title === 'SES' ? '#242f3e' : '',
}"
:src="app.logo"
/>
<GeneralIcon v-else icon="mail" />
<span class="font-weight-bold">{{ app.title }}</span>
</div>
<!--
<a-card
v-for="(app, i) in storageApps"
:key="i"
class="sm:w-100 md:w-130"
:class="`relative flex overflow-x-hidden app-item-card !shadow-sm rounded-md w-full nc-app-store-card-${app.title}`"
>
<div class="install-btn flex flex-row justify-end space-x-1">
<a-button v-if="app.parsedInput" size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-edit">
<IcRoundEdit class="pr-0.5" :height="12" />
{{ $t('general.edit') }}
</div>
</a-button>
<a-button v-if="app.parsedInput" size="small" outlined @click="showResetPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-reset">
<component :is="iconMap.closeCircle" />
<div class="flex ml-0.5">{{ $t('general.reset') }}</div>
</div>
</a-button>
<a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install">
<component :is="iconMap.plus" />
{{ $t('general.install') }}
</div>
</a-button>
</div>
<div class="flex flex-row space-x-2 items-center justify-start w-full">
<div class="flex w-[68px]">
<img
v-if="app.title !== 'SMTP'"
class="avatar"
alt="logo"
:style="{
backgroundColor: app.title === 'SES' ? '#242f3e' : '',
}"
:src="app.logo"
/>
<div v-else />
</div>
<div class="flex flex-col flex-1 w-3/5 pl-3">
<a-typography-title :level="5">{{ app.title }}</a-typography-title>
{{ app.description }}
</div>
</div>
</a-card>-->
</div>
</div>
</template>
<style scoped lang="scss">
.app-item-card {
position: relative;
transition: 0.4s background-color;
.install-btn {
position: absolute;
opacity: 1;
right: -100%;
top: 10px;
transition: 0.4s opacity, 0.4s right;
}
&:hover .install-btn {
right: 10px;
opacity: 1;
}
}
.app-item-card {
transition: 0.4s background-color, 0.4s transform;
&:hover {
background: rgba(123, 126, 136, 0.1) !important;
}
}
.caption {
font-size: 0.7rem;
}
.avatar {
width: 5rem;
height: 5rem;
padding: 0.25rem;
object-fit: contain;
}
</style>

46
packages/nc-gui/composables/useAccountSetupStore.ts

@ -0,0 +1,46 @@
import rfdc from 'rfdc'
import type { ColumnReqType, ColumnType, PluginType, TableType } from 'nocodb-sdk'
import { UITypes, isLinksOrLTAR } from 'nocodb-sdk'
import type { Ref } from 'vue'
import type { RuleObject } from 'ant-design-vue/es/form'
import { generateUniqueColumnName } from '~/helpers/parsers/parserHelpers'
const [useProvideAccountSetupStore, useAccountSetupStore] = createInjectionState(() => {
const apps = ref<(PluginType & { parsedInput?: Record<string, any>; tags?: string[] })[]>([])
const { $api, $e } = useNuxtApp()
const emailApps = computed(() => apps.value.filter((app) => app.category === 'Email'))
const storageApps = computed(() => apps.value.filter((app) => app.category === ('Storage')))
const loadSetupApps = async () => {
try {
const plugins = (await $api.plugin.list()).list ?? []
apps.value = plugins.map((p) => ({
...p,
tags: p.tags ? p.tags.split(',') : [],
parsedInput: p.input && JSON.parse(p.input as string),
})) as any[]
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
return {
apps,
emailApps,
storageApps,
loadSetupApps,
}
})
export { useProvideAccountSetupStore }
export function useAccountSetupStoreOrThrow() {
const columnCreateStore = useAccountSetupStore()
if (columnCreateStore == null) throw new Error('Please call `useProvideAccountSetupStore` on the appropriate parent component')
return columnCreateStore
}

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

@ -616,6 +616,9 @@
"categories": "Categories"
},
"labels": {
"setup": "Setup",
"setupLabel": "Setup {label}",
"switchToProd": "Switch to a production-ready app database",
"fieldID": "Field ID",
"addDescription": "Add description",
"editDescription": "Edit description",

15
packages/nc-gui/pages/account/index.vue

@ -60,6 +60,21 @@ const logout = async () => {
<div class="text-sm text-gray-500 font-semibold ml-4 py-1.5 mt-2">{{ $t('labels.account') }}</div>
<NcMenuItem
key="profile"
class="item"
:class="{
active: $route.path?.startsWith( '/account/setup'),
}"
@click="navigateTo('/account/setup')"
>
<div class="flex items-center space-x-2">
<GeneralIcon icon="ncSliders" class="!h-3.5 !w-3.5" />
<div class="select-none">{{ $t('labels.setup') }}</div>
</div>
</NcMenuItem>
<NcMenuItem
key="profile"
class="item"

1
packages/nc-gui/pages/account/index/[page].vue

@ -5,6 +5,7 @@ const { appInfo } = useGlobal()
<template>
<div>
<AccountToken v-if="$route.params.page === 'tokens'" />
<AccountSetup v-else-if="$route.params.page === 'setup'" />
<WorkspaceAuditLogs v-else-if="$route.params.page === 'audit'" />
<AccountProfile v-else-if="$route.params.page === 'profile'" />
<AccountAppStore v-else-if="$route.params.page === 'apps' && !appInfo.isCloud" />

21
packages/nc-gui/pages/account/index/setup/[[nestedPage]].vue

@ -0,0 +1,21 @@
<script setup lang="ts">
const { loadSetupApps } = useProvideAccountSetupStore()
onMounted(async () => {
await loadSetupApps()
})
</script>
<template>
<div class="h-full">
<template v-if="$route.params.nestedPage === 'storage'">
<LazyAccountSetupStorage />
</template>
<template v-else-if="$route.params.nestedPage === 'email'">
<LazyAccountSetupEmail />
</template>
<template v-else>
<LazyAccountSetup />
</template>
</div>
</template>
Loading…
Cancel
Save