From f3324044f1eea97b46424a97318923b1775b9a15 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Thu, 18 Sep 2025 12:12:17 +0300 Subject: [PATCH 01/26] feat: add settings page and validation for user menu settings --- adminforth/modules/codeInjector.ts | 14 +++- adminforth/modules/configValidator.ts | 12 +++ adminforth/spa/src/router/index.ts | 9 +++ adminforth/spa/src/views/SettingsView.vue | 99 +++++++++++++++++++++++ adminforth/types/Back.ts | 10 +++ 5 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 adminforth/spa/src/views/SettingsView.vue diff --git a/adminforth/modules/codeInjector.ts b/adminforth/modules/codeInjector.ts index f6912be25..b8f454388 100644 --- a/adminforth/modules/codeInjector.ts +++ b/adminforth/modules/codeInjector.ts @@ -323,10 +323,22 @@ class CodeInjector implements ICodeInjector { } },`}) }} + // const registerSettingPages = ( settingPage ) => { + // if (!settingPage) { + // return; + // } + // console.log('🪲⚙️ registerSettingPages', settingPage); + // routes += `{ + // path: '/settings', + // name: 'Settings', + // component: () => import('@/views/SettingsView.vue'), + // meta: { title: 'Settings'} + // },\n` + // } registerCustomPages(this.adminforth.config); collectAssetsFromMenu(this.adminforth.config.menu); - + // registerSettingPages(this.adminforth.config.auth.userMenuSettingsPages); const spaDir = this.getSpaDir(); if (process.env.HEAVY_DEBUG) { diff --git a/adminforth/modules/configValidator.ts b/adminforth/modules/configValidator.ts index dbed4513d..88799280f 100644 --- a/adminforth/modules/configValidator.ts +++ b/adminforth/modules/configValidator.ts @@ -999,6 +999,18 @@ export default class ConfigValidator implements IConfigValidator { const similar = suggestIfTypo(newConfig.resources.map((res) => res.resourceId ), newConfig.auth.usersResourceId); throw new Error(`Resource with id "${newConfig.auth.usersResourceId}" not found. ${similar ? `Did you mean "${similar}"?` : ''}`); } + if (newConfig.auth.userMenuSettingsPages) { + for (const page of newConfig.auth.userMenuSettingsPages) { + if (!page.component.startsWith('@@')) { + errors.push(`Menu item component must start with @@ : ${JSON.stringify(page)}`); + } + + const path = page.component.replace('@@', newConfig.customization.customComponentsDir); + if (!fs.existsSync(path)) { + errors.push(`Menu item component "${page.component.replace('@@', '')}" does not exist in "${newConfig.customization.customComponentsDir}"`); + } + } + } // normalize beforeLoginConfirmation hooks const blc = this.inputConfig.auth.beforeLoginConfirmation; diff --git a/adminforth/spa/src/router/index.ts b/adminforth/spa/src/router/index.ts index 726e076ea..a92a878e9 100644 --- a/adminforth/spa/src/router/index.ts +++ b/adminforth/spa/src/router/index.ts @@ -62,6 +62,15 @@ const router = createRouter({ }, ] }, + { + path: '/settings', + name: 'settings', + component: () => import('@/views/SettingsView.vue'), + meta: { + title: 'Settings', + customLayout: true + }, + }, /* IMPORTANT:ADMINFORTH ROUTES */ { path: "/:pathMatch(.*)*", component: PageNotFound }, ] diff --git a/adminforth/spa/src/views/SettingsView.vue b/adminforth/spa/src/views/SettingsView.vue new file mode 100644 index 000000000..2485f6898 --- /dev/null +++ b/adminforth/spa/src/views/SettingsView.vue @@ -0,0 +1,99 @@ + + + diff --git a/adminforth/types/Back.ts b/adminforth/types/Back.ts index 36de272e2..f569c641c 100644 --- a/adminforth/types/Back.ts +++ b/adminforth/types/Back.ts @@ -1034,6 +1034,16 @@ export interface AdminForthInputConfig { * If you are using Cloudflare, set this to 'CF-Connecting-IP'. Case-insensitive. */ clientIpHeader?: string, + + /** + * Add custom page to the settings page + */ + userMenuSettingsPages: { + icon?: string, + pageLabel: string, + slug?: string, + component: string + }[], }, /** From 35f62719ffe1556c0239126203a503832f5beb1b Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Thu, 18 Sep 2025 17:00:36 +0300 Subject: [PATCH 02/26] feat: implement user menu settings pages in configuration and UI --- adminforth/modules/codeInjector.ts | 26 +++---- adminforth/modules/restApi.ts | 1 + adminforth/spa/src/afcl/VerticalTabs.vue | 3 - adminforth/spa/src/views/SettingsView.vue | 93 ++++++++++++++++++++--- adminforth/types/Common.ts | 8 +- 5 files changed, 103 insertions(+), 28 deletions(-) diff --git a/adminforth/modules/codeInjector.ts b/adminforth/modules/codeInjector.ts index b8f454388..e41c5a863 100644 --- a/adminforth/modules/codeInjector.ts +++ b/adminforth/modules/codeInjector.ts @@ -323,22 +323,16 @@ class CodeInjector implements ICodeInjector { } },`}) }} - // const registerSettingPages = ( settingPage ) => { - // if (!settingPage) { - // return; - // } - // console.log('🪲⚙️ registerSettingPages', settingPage); - // routes += `{ - // path: '/settings', - // name: 'Settings', - // component: () => import('@/views/SettingsView.vue'), - // meta: { title: 'Settings'} - // },\n` - // } + const registerSettingPages = ( settingPage ) => { + if (!settingPage) { + return; + } + console.log('🪲⚙️ registerSettingPages', settingPage); + } registerCustomPages(this.adminforth.config); collectAssetsFromMenu(this.adminforth.config.menu); - // registerSettingPages(this.adminforth.config.auth.userMenuSettingsPages); + registerSettingPages(this.adminforth.config.auth.userMenuSettingsPages); const spaDir = this.getSpaDir(); if (process.env.HEAVY_DEBUG) { @@ -484,6 +478,12 @@ class CodeInjector implements ICodeInjector { }); } + if (this.adminforth.config.auth.userMenuSettingsPages) { + for (const settingPage of this.adminforth.config.auth.userMenuSettingsPages) { + checkInjections([{ file: settingPage.component, meta: settingPage.pageLabel }]); + } + } + customResourceComponents.forEach((filePath) => { const componentName = getComponentNameFromPath(filePath); diff --git a/adminforth/modules/restApi.ts b/adminforth/modules/restApi.ts index b72743fcc..a4dd4e20c 100644 --- a/adminforth/modules/restApi.ts +++ b/adminforth/modules/restApi.ts @@ -375,6 +375,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { announcementBadge, globalInjections: this.adminforth.config.customization.globalInjections, userFullnameField: this.adminforth.config.auth.userFullNameField, + settingPages: this.adminforth.config.auth.userMenuSettingsPages, } // translate menu labels diff --git a/adminforth/spa/src/afcl/VerticalTabs.vue b/adminforth/spa/src/afcl/VerticalTabs.vue index b4652af8b..aac9b8576 100644 --- a/adminforth/spa/src/afcl/VerticalTabs.vue +++ b/adminforth/spa/src/afcl/VerticalTabs.vue @@ -43,7 +43,4 @@ activeTab.value = tabs.value[0]; } }); - - - \ No newline at end of file diff --git a/adminforth/spa/src/views/SettingsView.vue b/adminforth/spa/src/views/SettingsView.vue index 2485f6898..9e04927bf 100644 --- a/adminforth/spa/src/views/SettingsView.vue +++ b/adminforth/spa/src/views/SettingsView.vue @@ -1,8 +1,8 @@ diff --git a/adminforth/types/Common.ts b/adminforth/types/Common.ts index 71ad75d1a..cb4ed7d21 100644 --- a/adminforth/types/Common.ts +++ b/adminforth/types/Common.ts @@ -1093,7 +1093,13 @@ export interface AdminForthConfigForFrontend { customHeadItems?: { tagName: string; attributes: Record; - }[], + }[], + settingPages?:{ + icon?: string, + pageLabel: string, + slug?: string, + component?: string, + }[], } export interface GetBaseConfigResponse { From 0cb2ed2d2e6895a23fab79ce85f67d6a48688ce0 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Fri, 19 Sep 2025 10:28:48 +0300 Subject: [PATCH 03/26] feat: add icons support for options in SettingView --- adminforth/modules/codeInjector.ts | 6 +++++- adminforth/spa/src/views/SettingsView.vue | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/adminforth/modules/codeInjector.ts b/adminforth/modules/codeInjector.ts index e41c5a863..798d98803 100644 --- a/adminforth/modules/codeInjector.ts +++ b/adminforth/modules/codeInjector.ts @@ -327,7 +327,11 @@ class CodeInjector implements ICodeInjector { if (!settingPage) { return; } - console.log('🪲⚙️ registerSettingPages', settingPage); + for (const page of settingPage) { + if (page.icon) { + icons.push(page.icon); + } + } } registerCustomPages(this.adminforth.config); diff --git a/adminforth/spa/src/views/SettingsView.vue b/adminforth/spa/src/views/SettingsView.vue index 9e04927bf..108ce4ffb 100644 --- a/adminforth/spa/src/views/SettingsView.vue +++ b/adminforth/spa/src/views/SettingsView.vue @@ -76,7 +76,8 @@ @@ -97,7 +98,7 @@ import { computed, ref, onMounted, watch } from 'vue'; import { useRouter } from 'vue-router'; import { useCoreStore } from '@/stores/core'; import { useUserStore } from '@/stores/user'; -import { getCustomComponent } from '@/utils'; +import { getCustomComponent, getIcon } from '@/utils'; import { Dropdown } from 'flowbite'; import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite'; import adminforth from '@/adminforth'; From 885e76c57a84bd475369b798b1511eeca59115b5 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Fri, 19 Sep 2025 12:03:04 +0300 Subject: [PATCH 04/26] feat: enhance settings routing --- adminforth/spa/src/afcl/VerticalTabs.vue | 13 ++++- adminforth/spa/src/router/index.ts | 2 +- adminforth/spa/src/views/SettingsView.vue | 58 +++++++++++++++++++++-- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/adminforth/spa/src/afcl/VerticalTabs.vue b/adminforth/spa/src/afcl/VerticalTabs.vue index aac9b8576..c0fc666e7 100644 --- a/adminforth/spa/src/afcl/VerticalTabs.vue +++ b/adminforth/spa/src/afcl/VerticalTabs.vue @@ -5,7 +5,7 @@ @@ -31,6 +31,11 @@ const emites = defineEmits([ 'update:activeTab', ]); + + defineExpose({ + setActiveTab + }); + onMounted(() => { const slots = useSlots(); tabs.value = Object.keys(slots).reduce((tbs: string[], tb: string) => { @@ -43,4 +48,10 @@ activeTab.value = tabs.value[0]; } }); + + function setActiveTab(tab: string) { + if (tabs.value.includes(tab)) { + activeTab.value = tab; + } + } \ No newline at end of file diff --git a/adminforth/spa/src/router/index.ts b/adminforth/spa/src/router/index.ts index a92a878e9..262be5ad7 100644 --- a/adminforth/spa/src/router/index.ts +++ b/adminforth/spa/src/router/index.ts @@ -63,7 +63,7 @@ const router = createRouter({ ] }, { - path: '/settings', + path: '/settings/:page?', name: 'settings', component: () => import('@/views/SettingsView.vue'), meta: { diff --git a/adminforth/spa/src/views/SettingsView.vue b/adminforth/spa/src/views/SettingsView.vue index 108ce4ffb..0f0d5fb5d 100644 --- a/adminforth/spa/src/views/SettingsView.vue +++ b/adminforth/spa/src/views/SettingsView.vue @@ -74,9 +74,9 @@

No setting pages configured or still loading...

- +