Skip to content

Commit 80ff8ed

Browse files
author
Lasim
committed
feat(frontend): enhance user preferences handling for walkthrough
1 parent 68623a2 commit 80ff8ed

File tree

20 files changed

+1239
-124
lines changed

20 files changed

+1239
-124
lines changed

services/backend/api-spec.json

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14479,11 +14479,52 @@
1447914479
"template_env": {
1448014480
"anyOf": [
1448114481
{
14482-
"type": "object",
14483-
"propertyNames": {
14484-
"type": "string"
14485-
},
14486-
"additionalProperties": {}
14482+
"type": "array",
14483+
"items": {}
14484+
},
14485+
{
14486+
"type": "null"
14487+
}
14488+
]
14489+
},
14490+
"team_args_schema": {
14491+
"anyOf": [
14492+
{
14493+
"type": "array",
14494+
"items": {}
14495+
},
14496+
{
14497+
"type": "null"
14498+
}
14499+
]
14500+
},
14501+
"team_env_schema": {
14502+
"anyOf": [
14503+
{
14504+
"type": "array",
14505+
"items": {}
14506+
},
14507+
{
14508+
"type": "null"
14509+
}
14510+
]
14511+
},
14512+
"user_args_schema": {
14513+
"anyOf": [
14514+
{
14515+
"type": "array",
14516+
"items": {}
14517+
},
14518+
{
14519+
"type": "null"
14520+
}
14521+
]
14522+
},
14523+
"user_env_schema": {
14524+
"anyOf": [
14525+
{
14526+
"type": "array",
14527+
"items": {}
1448714528
},
1448814529
{
1448914530
"type": "null"
@@ -14584,6 +14625,10 @@
1458414625
"transport_type",
1458514626
"template_args",
1458614627
"template_env",
14628+
"team_args_schema",
14629+
"team_env_schema",
14630+
"user_args_schema",
14631+
"user_env_schema",
1458714632
"dependencies",
1458814633
"category_id",
1458914634
"tags",

services/backend/api-spec.yaml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10043,10 +10043,28 @@ paths:
1004310043
- type: "null"
1004410044
template_env:
1004510045
anyOf:
10046-
- type: object
10047-
propertyNames:
10048-
type: string
10049-
additionalProperties: {}
10046+
- type: array
10047+
items: {}
10048+
- type: "null"
10049+
team_args_schema:
10050+
anyOf:
10051+
- type: array
10052+
items: {}
10053+
- type: "null"
10054+
team_env_schema:
10055+
anyOf:
10056+
- type: array
10057+
items: {}
10058+
- type: "null"
10059+
user_args_schema:
10060+
anyOf:
10061+
- type: array
10062+
items: {}
10063+
- type: "null"
10064+
user_env_schema:
10065+
anyOf:
10066+
- type: array
10067+
items: {}
1005010068
- type: "null"
1005110069
dependencies:
1005210070
anyOf:
@@ -10109,6 +10127,10 @@ paths:
1010910127
- transport_type
1011010128
- template_args
1011110129
- template_env
10130+
- team_args_schema
10131+
- team_env_schema
10132+
- user_args_schema
10133+
- user_env_schema
1011210134
- dependencies
1011310135
- category_id
1011410136
- tags

services/backend/src/global-settings/global.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ export const globalSettings: GlobalSettingsModule = {
8080
description: 'Show banner during MCP installation to inform users that adding new servers to the DeployStack catalog requires submitting a pull request to the awesome-mcp-server repository',
8181
encrypted: false,
8282
required: false
83+
},
84+
{
85+
key: 'global.show_user_walkthrough',
86+
defaultValue: false,
87+
type: 'boolean',
88+
description: 'Show user walkthrough flow when users log in. When enabled, users who have not completed or cancelled the walkthrough will see the walkthrough process in the frontend.',
89+
encrypted: false,
90+
required: false
8391
}
8492
]
8593
};

services/backend/src/routes/mcp/servers/get.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ const getServerResponseSchema = z.object({
3838
organization: z.string().nullable(),
3939
license: z.string().nullable(),
4040
transport_type: z.enum(['stdio', 'http', 'sse']),
41-
// Three-tier configuration schema (template values only for general users)
4241
template_args: z.array(z.any()).nullable(),
43-
template_env: z.record(z.string(), z.any()).nullable(),
44-
// team_args_schema, user_args_schema etc. are admin-only fields
45-
// and are not exposed on this general-purpose endpoint.
42+
template_env: z.array(z.any()).nullable(),
43+
team_args_schema: z.array(z.any()).nullable(),
44+
team_env_schema: z.array(z.any()).nullable(),
45+
user_args_schema: z.array(z.any()).nullable(),
46+
user_env_schema: z.array(z.any()).nullable(),
4647
dependencies: z.record(z.string(), z.any()).nullable(),
4748
category_id: z.string().nullable(),
4849
tags: z.array(z.string()).nullable(),

services/frontend/src/components/admin/mcp-catalog/ReviewStep.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { onMounted, onUnmounted, ref } from 'vue'
33
import { Badge } from '@/components/ui/badge'
44
import { useI18n } from 'vue-i18n'
55
import { useEventBus } from '@/composables/useEventBus'
6+
import CategoryDisplay from '@/components/mcp-server/CategoryDisplay.vue'
67
import type { ReviewFormData, McpServerFormData } from '@/views/admin/mcp-server-catalog/types'
78
89
interface Props {
@@ -126,7 +127,7 @@ const formatJson = (jsonString: string) => {
126127
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
127128
<dt class="text-sm/6 font-medium text-gray-900">{{ t('mcpCatalog.form.review.fields.category') }}</dt>
128129
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0">
129-
{{ getBasicData().category_id || t('mcpCatalog.form.review.values.notSpecified') }}
130+
<CategoryDisplay :category-id="getBasicData().category_id" />
130131
</dd>
131132
</div>
132133

services/frontend/src/components/mcp-catalog/McpCatalogContributionBanner.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ onMounted(() => {
4242
<!-- Only render when not loading and visible -->
4343
<div v-if="!isLoading && isVisible" class="bg-white">
4444
<div class="mx-auto max-w-7xl py-12 sm:px-6 lg:px-8">
45-
<div class="relative isolate overflow-hidden bg-gradient-to-r from-emerald-600 to-emerald-950 px-6 pt-8 pb-8 sm:rounded-3xl sm:px-8 lg:flex lg:gap-x-20">
45+
<div class="relative isolate overflow-hidden bg-linear-to-r from-emerald-600 to-emerald-950 px-6 pt-8 pb-8 sm:rounded-3xl sm:px-8 lg:flex lg:gap-x-20">
4646
<div class="mx-auto max-w-full text-left lg:mx-0 lg:flex-auto lg:flex lg:items-center lg:justify-between">
4747
<div class="flex-1 lg:max-w-[70%]">
4848
<p class="text-lg/8 text-pretty text-gray-300">

services/frontend/src/components/mcp-server/FeaturedMcpServers.vue

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { ref, onMounted, computed } from 'vue'
33
import { useI18n } from 'vue-i18n'
4-
import { useRouter } from 'vue-router'
4+
import { useRouter, useRoute } from 'vue-router'
55
import { Button } from '@/components/ui/button'
66
import { Loader2, Github, Code } from 'lucide-vue-next'
77
import { McpCatalogService } from '@/services/mcpCatalogService'
@@ -29,6 +29,7 @@ const emit = defineEmits<{
2929
3030
const { t } = useI18n()
3131
const router = useRouter()
32+
const route = useRoute()
3233
3334
// State
3435
const featuredServers = ref<McpServer[]>([])
@@ -47,6 +48,10 @@ const gridCols = computed(() => {
4748
return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'
4849
})
4950
51+
const shouldShowBrowseAllButton = computed(() => {
52+
return route.path !== '/mcp-server/add'
53+
})
54+
5055
// Methods
5156
const fetchFeaturedServers = async () => {
5257
try {
@@ -82,6 +87,10 @@ const handleInstall = (server: McpServer) => {
8287
})
8388
}
8489
90+
const handleServerClick = (server: McpServer) => {
91+
router.push(`/mcp-server/view/${server.id}`)
92+
}
93+
8594
const getServerLanguageBadge = (server: McpServer) => {
8695
return server.language || 'Unknown'
8796
}
@@ -141,7 +150,13 @@ onMounted(() => {
141150
<div class="rounded-lg bg-gray-50 shadow-xs outline-1 outline-gray-900/5">
142151
<dl class="flex flex-wrap">
143152
<div class="flex-auto pt-6 pl-6">
144-
<dt class="text-sm/6 font-semibold text-gray-900">{{ server.name }}</dt>
153+
<dt
154+
class="text-sm/6 font-semibold text-gray-900 cursor-pointer hover:text-teal-700 transition-colors"
155+
@click="handleServerClick(server)"
156+
:title="`View ${server.name} details`"
157+
>
158+
{{ server.name }}
159+
</dt>
145160
</div>
146161
<div class="mt-6 flex w-full flex-none gap-x-4 items-center border-t border-gray-900/5 px-6 pt-6">
147162
<dt class="flex-none">
@@ -189,7 +204,7 @@ onMounted(() => {
189204
</div>
190205

191206
<!-- Show More Link -->
192-
<div v-if="featuredServers.length > 0" class="text-center">
207+
<div v-if="featuredServers.length > 0 && shouldShowBrowseAllButton" class="text-center">
193208
<Button
194209
variant="ghost"
195210
size="sm"

services/frontend/src/components/mcp-server/McpInstallationsCard.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import FeaturedMcpServers from './FeaturedMcpServers.vue'
1111
interface Props {
1212
installations: McpInstallation[]
1313
hasInstallations: boolean
14+
showWalkthrough?: boolean
1415
}
1516
1617
defineProps<Props>()
@@ -78,6 +79,7 @@ const handleRemoveInstallation = (installationId: string) => {
7879
<McpInstallationsList
7980
v-else
8081
:installations="installations"
82+
:show-walkthrough="showWalkthrough"
8183
@view-installation="handleViewInstallation"
8284
@manage-installation="handleManageInstallation"
8385
@remove-installation="handleRemoveInstallation"

services/frontend/src/components/mcp-server/McpInstallationsList.vue

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { computed, ref } from 'vue'
2+
import { computed, ref, onMounted, onUnmounted } from 'vue'
33
import { useI18n } from 'vue-i18n'
44
import { useRouter } from 'vue-router'
55
import {
@@ -23,9 +23,11 @@ import type { McpInstallation } from '@/types/mcp-installations'
2323
import { McpInstallationService } from '@/services/mcpInstallationService'
2424
import { TeamService } from '@/services/teamService'
2525
import CategoryDisplay from '@/components/mcp-server/CategoryDisplay.vue'
26+
import { useEventBus } from '@/composables/useEventBus'
2627
2728
interface Props {
2829
installations: McpInstallation[]
30+
showWalkthrough?: boolean
2931
}
3032
3133
const props = defineProps<Props>()
@@ -38,13 +40,22 @@ const emit = defineEmits<{
3840
3941
const { t } = useI18n()
4042
const router = useRouter()
43+
const eventBus = useEventBus()
4144
4245
// Modal state
4346
const showDeleteModal = ref(false)
4447
const isDeleting = ref(false)
4548
const deleteError = ref<string | null>(null)
4649
const installationToDelete = ref<McpInstallation | null>(null)
4750
51+
// Walkthrough state
52+
const showWalkthroughOverlay = ref(false)
53+
const showWalkthroughBorder = ref(false)
54+
const showWalkthroughHighZIndex = ref(false)
55+
56+
// Watch for walkthrough prop changes
57+
// Note: Walkthrough popover is now handled by parent index.vue
58+
4859
// Find which team owns the installation
4960
async function findInstallationTeam(installationId: string): Promise<{ teamId: string; installation: McpInstallation } | null> {
5061
try {
@@ -145,20 +156,80 @@ const cancelRemoval = () => {
145156
showDeleteModal.value = false
146157
installationToDelete.value = null
147158
}
159+
160+
// Event handlers for walkthrough overlay
161+
const handleWalkthroughOverlayShow = () => {
162+
console.log('Showing walkthrough overlay for step 1')
163+
showWalkthroughOverlay.value = true
164+
showWalkthroughBorder.value = true
165+
showWalkthroughHighZIndex.value = true
166+
}
167+
168+
const handleWalkthroughOverlayHide = () => {
169+
console.log('Hiding walkthrough overlay')
170+
showWalkthroughOverlay.value = false
171+
showWalkthroughBorder.value = false
172+
showWalkthroughHighZIndex.value = false
173+
}
174+
175+
// Event handlers for step-specific z-index control
176+
const handleWalkthroughStep1Active = () => {
177+
console.log('Step 1 active: MCP liste should be visible (high z-index)')
178+
// Step 1: MCP liste should be visible (high z-index)
179+
showWalkthroughHighZIndex.value = true
180+
showWalkthroughBorder.value = true
181+
}
182+
183+
const handleWalkthroughStep2Active = () => {
184+
console.log('Step 2 active: MCP liste should be hidden under overlay (no high z-index)')
185+
// Step 2: MCP liste should be hidden under overlay (no high z-index)
186+
showWalkthroughHighZIndex.value = false
187+
showWalkthroughBorder.value = false
188+
}
189+
190+
onMounted(() => {
191+
console.log('McpInstallationsList mounted - registering walkthrough event listeners')
192+
// Listen for walkthrough overlay events
193+
eventBus.on('walkthrough-overlay-show', handleWalkthroughOverlayShow)
194+
eventBus.on('walkthrough-overlay-hide', handleWalkthroughOverlayHide)
195+
196+
// Listen for step-specific events
197+
eventBus.on('walkthrough-step1-active', handleWalkthroughStep1Active)
198+
eventBus.on('walkthrough-step2-active', handleWalkthroughStep2Active)
199+
})
200+
201+
onUnmounted(() => {
202+
// Clean up event listeners
203+
eventBus.off('walkthrough-overlay-show', handleWalkthroughOverlayShow)
204+
eventBus.off('walkthrough-overlay-hide', handleWalkthroughOverlayHide)
205+
eventBus.off('walkthrough-step1-active', handleWalkthroughStep1Active)
206+
eventBus.off('walkthrough-step2-active', handleWalkthroughStep2Active)
207+
})
148208
</script>
149209

150210
<template>
211+
<!-- Walkthrough Background Overlay -->
212+
<div
213+
v-if="showWalkthroughOverlay && sortedInstallations.length > 0"
214+
class="fixed inset-0 bg-black/40 backdrop-blur-[2px] z-[9998]"
215+
/>
216+
151217
<div class="min-h-screen bg-gray-50">
152218
<div class="mx-auto max-w-4xl space-y-8 py-16">
153219
<!-- Installations List -->
154-
<ul
155-
v-if="sortedInstallations.length > 0"
156-
role="list"
157-
class="divide-y divide-gray-100 overflow-hidden bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg"
158-
>
220+
<div v-if="sortedInstallations.length > 0" class="relative">
221+
<ul
222+
role="list"
223+
:class="[
224+
'divide-y divide-gray-100 overflow-hidden bg-white shadow-sm ring-1 ring-gray-900/5 rounded-lg relative',
225+
showWalkthroughHighZIndex ? 'z-[9999]' : '',
226+
showWalkthroughBorder ? 'ring-4 ring-teal-500 ring-opacity-50' : ''
227+
]"
228+
>
159229
<li
160-
v-for="installation in sortedInstallations"
230+
v-for="(installation, index) in sortedInstallations"
161231
:key="installation.id"
232+
:id="index === sortedInstallations.length - 1 ? 'last-server-item' : undefined"
162233
class="relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6"
163234
>
164235
<div class="flex min-w-0 gap-x-4">
@@ -231,9 +302,13 @@ const cancelRemoval = () => {
231302
</div>
232303

233304
<ChevronRight class="size-5 flex-none text-gray-400" aria-hidden="true" />
234-
</div>
235-
</li>
236-
</ul>
305+
</div>
306+
</li>
307+
</ul>
308+
309+
<!-- Walkthrough Popover positioned relative to last server -->
310+
<!-- REMOVED: Duplicate popover - handled by parent index.vue -->
311+
</div>
237312

238313
<!-- Empty State -->
239314
<div

0 commit comments

Comments
 (0)