@igstack/app-catalog-frontend-core 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/__tests__/integration/harness/given.d.ts +1 -0
- package/dist/esm/__tests__/integration/skewWarning.integration.test.d.ts +0 -0
- package/dist/esm/__tests__/integration/tools/AppDetailTools.d.ts +5 -0
- package/dist/esm/__tests__/integration/tools/CatalogTools.d.ts +15 -1
- package/dist/esm/__tests__/modules/appCatalog/utils/resolveHelpers.test.d.ts +1 -0
- package/dist/esm/modules/appCatalog/hooks/useSessionSyncedState.d.ts +40 -0
- package/dist/esm/modules/appCatalog/hooks/useSessionSyncedState.js +40 -0
- package/dist/esm/modules/appCatalog/hooks/useSessionSyncedState.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/components/AccessPrerequisiteChain.d.ts +17 -0
- package/dist/esm/modules/appCatalog/ui/components/AccessPrerequisiteChain.js +61 -0
- package/dist/esm/modules/appCatalog/ui/components/AccessPrerequisiteChain.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.js +9 -2
- package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/OnboardingCard.js +1 -1
- package/dist/esm/modules/appCatalog/ui/components/OnboardingCard.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/PersonBadge.d.ts +6 -0
- package/dist/esm/modules/appCatalog/ui/components/PersonBadge.js +95 -31
- package/dist/esm/modules/appCatalog/ui/components/PersonBadge.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.d.ts +9 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.js +12 -3
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/context/AppCatalogFiltersContext.js +3 -2
- package/dist/esm/modules/appCatalog/ui/context/AppCatalogFiltersContext.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.d.ts +5 -0
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.js +73 -18
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/launcher/AttributionFooter.d.ts +9 -0
- package/dist/esm/modules/appCatalog/ui/launcher/AttributionFooter.js +31 -0
- package/dist/esm/modules/appCatalog/ui/launcher/AttributionFooter.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/launcher/LauncherDetailPanel.d.ts +16 -0
- package/dist/esm/modules/appCatalog/ui/launcher/LauncherDetailPanel.js +40 -0
- package/dist/esm/modules/appCatalog/ui/launcher/LauncherDetailPanel.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/launcher/LauncherHome.d.ts +19 -0
- package/dist/esm/modules/appCatalog/ui/launcher/LauncherHome.js +389 -0
- package/dist/esm/modules/appCatalog/ui/launcher/LauncherHome.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/launcher/ResourceIcon.d.ts +13 -0
- package/dist/esm/modules/appCatalog/ui/launcher/ResourceIcon.js +45 -0
- package/dist/esm/modules/appCatalog/ui/launcher/ResourceIcon.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/launcher/monogram.d.ts +4 -0
- package/dist/esm/modules/appCatalog/ui/launcher/monogram.js +41 -0
- package/dist/esm/modules/appCatalog/ui/launcher/monogram.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js +40 -6
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js.map +1 -1
- package/dist/esm/modules/appCatalog/utils/markdownToPlainText.d.ts +1 -1
- package/dist/esm/modules/appCatalog/utils/markdownToPlainText.js.map +1 -1
- package/dist/esm/modules/appCatalog/utils/resolveHelpers.d.ts +10 -0
- package/dist/esm/modules/appCatalog/utils/resolveHelpers.js +17 -0
- package/dist/esm/modules/appCatalog/utils/resolveHelpers.js.map +1 -1
- package/dist/esm/routeTree.gen.d.ts +1 -1
- package/dist/esm/routes/_layout/app.$slug.d.ts +1 -1
- package/dist/esm/routes/_layout/app._slug.js +4 -3
- package/dist/esm/routes/_layout/app._slug.js.map +1 -1
- package/dist/esm/routes/_layout/catalog.apps.index.js +2 -1
- package/dist/esm/routes/_layout/catalog.apps.index.js.map +1 -1
- package/dist/esm/routes/_layout/index.js +2 -1
- package/dist/esm/routes/_layout/index.js.map +1 -1
- package/dist/esm/routes/_layout/service-desks.js +2 -1
- package/dist/esm/routes/_layout/service-desks.js.map +1 -1
- package/dist/esm/types/uiSettings.d.ts +15 -0
- package/dist/esm/ui/components/header/Header.js +1 -1
- package/dist/esm/ui/components/header/Header.js.map +1 -1
- package/dist/index.css +73 -66
- package/package.json +4 -4
- package/src/__tests__/integration/appCatalog.integration.test.ts +106 -0
- package/src/__tests__/integration/appDeepLink.integration.test.ts +50 -24
- package/src/__tests__/integration/appFreshness.integration.test.ts +3 -3
- package/src/__tests__/integration/crossRefLinkListView.integration.test.ts +8 -8
- package/src/__tests__/integration/harness/given.tsx +9 -1
- package/src/__tests__/integration/replacementLink.integration.test.ts +6 -2
- package/src/__tests__/integration/searchDeprecatedFallback.integration.test.ts +21 -16
- package/src/__tests__/integration/setup/testSetup.ts +1 -0
- package/src/__tests__/integration/skewWarning.integration.test.ts +56 -0
- package/src/__tests__/integration/tools/AppDetailTools.ts +13 -0
- package/src/__tests__/integration/tools/CatalogTools.ts +62 -11
- package/src/__tests__/integration/tools/GalleryTools.ts +7 -1
- package/src/__tests__/modules/appCatalog/utils/markdownToPlainText.test.ts +5 -5
- package/src/__tests__/modules/appCatalog/utils/resolveHelpers.test.ts +79 -0
- package/src/index.css +73 -66
- package/src/modules/appCatalog/hooks/useSessionSyncedState.ts +83 -0
- package/src/modules/appCatalog/ui/components/AccessPrerequisiteChain.tsx +89 -0
- package/src/modules/appCatalog/ui/components/AccessRequestSection.tsx +31 -2
- package/src/modules/appCatalog/ui/components/OnboardingCard.tsx +1 -1
- package/src/modules/appCatalog/ui/components/PersonBadge.tsx +103 -30
- package/src/modules/appCatalog/ui/components/SubResourcesSection.tsx +22 -1
- package/src/modules/appCatalog/ui/context/AppCatalogFiltersContext.tsx +11 -5
- package/src/modules/appCatalog/ui/grid/AppCatalogGrid.tsx +90 -23
- package/src/modules/appCatalog/ui/launcher/AttributionFooter.tsx +43 -0
- package/src/modules/appCatalog/ui/launcher/LauncherDetailPanel.tsx +52 -0
- package/src/modules/appCatalog/ui/launcher/LauncherHome.tsx +547 -0
- package/src/modules/appCatalog/ui/launcher/ResourceIcon.tsx +54 -0
- package/src/modules/appCatalog/ui/launcher/monogram.ts +41 -0
- package/src/modules/appCatalog/ui/pages/AppCatalogPage.tsx +75 -13
- package/src/modules/appCatalog/utils/markdownToPlainText.ts +1 -1
- package/src/modules/appCatalog/utils/resolveHelpers.ts +31 -0
- package/src/routes/_layout/app.$slug.tsx +7 -4
- package/src/routes/_layout/catalog.apps.index.tsx +4 -1
- package/src/routes/_layout/index.tsx +5 -1
- package/src/routes/_layout/service-desks.tsx +4 -1
- package/src/types/uiSettings.ts +15 -0
- package/src/ui/components/header/Header.tsx +3 -1
|
@@ -19,6 +19,8 @@ import { OnboardingCard } from '../components/OnboardingCard'
|
|
|
19
19
|
import { useAppCatalogFilters } from '../context/AppCatalogFiltersContext'
|
|
20
20
|
import { FilterBar } from '../filters/FilterBar'
|
|
21
21
|
import { AppCatalogGrid } from '../grid/AppCatalogGrid'
|
|
22
|
+
import { LauncherHome } from '../launcher/LauncherHome'
|
|
23
|
+
import { LauncherDetailPanel } from '../launcher/LauncherDetailPanel'
|
|
22
24
|
|
|
23
25
|
export function AppCatalogPage({
|
|
24
26
|
selectedSlug,
|
|
@@ -153,32 +155,37 @@ export function AppCatalogPage({
|
|
|
153
155
|
searchValue: deferredSearchValue,
|
|
154
156
|
})
|
|
155
157
|
|
|
156
|
-
// Auto-open details when only 1 result
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
158
|
+
// Auto-open details when only 1 result — ONLY in the legacy grid path
|
|
159
|
+
// (recent/tag filters active). In the launcher (#38) typing shows the
|
|
160
|
+
// search-morph results list; auto-navigating to /app/<slug> on a single
|
|
161
|
+
// match would yank the user out of the launcher into the old grid+panel
|
|
162
|
+
// (reported bug: searching "biom" jumped straight to an app-detail page).
|
|
163
|
+
// The morph already renders the single match as a keyboard-selectable row —
|
|
164
|
+
// the user presses ↵ or clicks to open it.
|
|
165
|
+
const legacyGridActive =
|
|
166
|
+
filterState.recentMode || Object.keys(filterState.tagFilters).length > 0
|
|
162
167
|
useEffect(() => {
|
|
163
|
-
if (
|
|
168
|
+
if (
|
|
169
|
+
legacyGridActive &&
|
|
170
|
+
filteredApps.length === 1 &&
|
|
171
|
+
filteredApps[0] &&
|
|
172
|
+
!selectedAppSlug
|
|
173
|
+
) {
|
|
164
174
|
void navigate({
|
|
165
175
|
to: '/app/$slug',
|
|
166
176
|
params: { slug: filteredApps[0].slug },
|
|
167
|
-
search: (prev) =>
|
|
168
|
-
...prev,
|
|
169
|
-
q: searchValue === '' ? undefined : searchValue,
|
|
170
|
-
}),
|
|
177
|
+
search: (prev) => prev,
|
|
171
178
|
replace: true,
|
|
172
179
|
})
|
|
173
180
|
}
|
|
174
|
-
}, [filteredApps, selectedAppSlug, navigate
|
|
181
|
+
}, [legacyGridActive, filteredApps, selectedAppSlug, navigate])
|
|
175
182
|
|
|
176
183
|
// #22: alias → canonical redirect. When an app is renamed its old slug is
|
|
177
184
|
// kept in `aliases[]`. If the URL slug matches no canonical slug but does
|
|
178
185
|
// match some app's alias, redirect (replace) to that app's canonical slug so
|
|
179
186
|
// old bookmarks resolve instead of showing a blank catalog. Guard on a real
|
|
180
187
|
// canonical miss so we never fight the normal detail-open path. Note: this is
|
|
181
|
-
// a client-side SPA redirect (replace), not an HTTP 301 — see #22.
|
|
188
|
+
// a client-side SPA redirect (replace), not an HTTP 301 — see ig-umbrella#22.
|
|
182
189
|
useEffect(() => {
|
|
183
190
|
if (!selectedAppSlug || resources.length === 0) return
|
|
184
191
|
const canonical = resources.some((r) => r.slug === selectedAppSlug)
|
|
@@ -202,6 +209,27 @@ export function AppCatalogPage({
|
|
|
202
209
|
})
|
|
203
210
|
}
|
|
204
211
|
|
|
212
|
+
const handleLaunch = (app: Resource) => {
|
|
213
|
+
if (app.appUrl) window.open(app.appUrl, '_blank', 'noopener,noreferrer')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Adaptive-home launcher (#38): shown whenever no structural filter
|
|
217
|
+
// (recentMode / tagFilters) is active. A selected app no longer forces the
|
|
218
|
+
// old grid — instead the launcher stays as the backdrop and the app detail
|
|
219
|
+
// renders in a slide-over panel (#38 item B). The legacy grid+split-pane is
|
|
220
|
+
// reserved for the recent/tag filter views.
|
|
221
|
+
const showLauncherHome =
|
|
222
|
+
!filterState.recentMode && Object.keys(filterState.tagFilters).length === 0
|
|
223
|
+
|
|
224
|
+
// The app whose detail slide-over is open over the launcher backdrop.
|
|
225
|
+
const launcherSelectedApp = useMemo(
|
|
226
|
+
() =>
|
|
227
|
+
selectedAppSlug
|
|
228
|
+
? (resources.find((r) => r.slug === selectedAppSlug) ?? null)
|
|
229
|
+
: null,
|
|
230
|
+
[selectedAppSlug, resources],
|
|
231
|
+
)
|
|
232
|
+
|
|
205
233
|
const handleClearFilters = () => {
|
|
206
234
|
setSearchValue('')
|
|
207
235
|
actions.clearAllFilters()
|
|
@@ -224,6 +252,40 @@ export function AppCatalogPage({
|
|
|
224
252
|
// Use first tag definition for grouping
|
|
225
253
|
const groupingDefinition = tagsDefinitions[0]
|
|
226
254
|
|
|
255
|
+
// Adaptive-home launcher view (#38): the discovery spine or the search-morph
|
|
256
|
+
// results list (handled inside LauncherHome). Owns vertical scroll —
|
|
257
|
+
// MainLayout is h-screen/overflow-hidden, so the launcher must scroll
|
|
258
|
+
// internally to reveal the full Browse-all list as the user scrolls.
|
|
259
|
+
if (showLauncherHome) {
|
|
260
|
+
return (
|
|
261
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
262
|
+
<LauncherHome
|
|
263
|
+
apps={rootResources.filter(
|
|
264
|
+
(a) => filterState.showDeprecated || !a.deprecated,
|
|
265
|
+
)}
|
|
266
|
+
// All resources incl. children — so search matches sub-resources
|
|
267
|
+
// (e.g. an AWS account) and surfaces their parent, preserving the
|
|
268
|
+
// existing cross-sub-resource search behavior.
|
|
269
|
+
allResources={resources}
|
|
270
|
+
searchValue={searchValue}
|
|
271
|
+
onSearchChange={setSearchValue}
|
|
272
|
+
onAppClick={handleAppClick}
|
|
273
|
+
onLaunch={handleLaunch}
|
|
274
|
+
totalCount={totalAppsCount}
|
|
275
|
+
/>
|
|
276
|
+
{/* #38 item B: app detail as a slide-over over the launcher backdrop,
|
|
277
|
+
instead of dropping into the old grid + split-pane. */}
|
|
278
|
+
{launcherSelectedApp && (
|
|
279
|
+
<LauncherDetailPanel
|
|
280
|
+
app={launcherSelectedApp}
|
|
281
|
+
onClose={() => void navigate({ to: '/' })}
|
|
282
|
+
onAppClick={handleAppClick}
|
|
283
|
+
/>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
227
289
|
return (
|
|
228
290
|
<div className="flex flex-col flex-1 min-h-0">
|
|
229
291
|
<div className="shrink-0">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Reduce a short markdown string to its visible plain text.
|
|
3
3
|
*
|
|
4
4
|
* Descriptions may contain markdown — notably cross-reference links like
|
|
5
|
-
* `[
|
|
5
|
+
* `[Signatera Portal](/app/signatera-portal)` (#25). The detail panel renders
|
|
6
6
|
* those as interactive links via {@link MarkdownText}, but in compact / clamped
|
|
7
7
|
* surfaces (grid list preview, table row, filter combobox, sub-resources) an
|
|
8
8
|
* interactive link would break `line-clamp` and can't be reliably highlighted
|
|
@@ -25,5 +25,36 @@ export function getRootResources(resources: Resource[]): Resource[] {
|
|
|
25
25
|
return resources.filter((r) => !r.parentSlug)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** The immediate parent of a resource (undefined for a root or if missing). */
|
|
29
|
+
export function getParentResource(
|
|
30
|
+
resources: Resource[],
|
|
31
|
+
resource: Pick<Resource, 'parentSlug'>,
|
|
32
|
+
): Resource | undefined {
|
|
33
|
+
if (!resource.parentSlug) return undefined
|
|
34
|
+
return resources.find((r) => r.slug === resource.parentSlug)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The prerequisite chain for a resource: its ancestors that carry an access
|
|
39
|
+
* policy, ordered ROOT → nearest parent. Used to surface the two-step access
|
|
40
|
+
* pattern ("get <parent> access first, then this resource"), which the domain
|
|
41
|
+
* model computes from the tree but users can't see otherwise (#38 item D).
|
|
42
|
+
* Guards against cycles.
|
|
43
|
+
*/
|
|
44
|
+
export function getAccessPrerequisiteChain(
|
|
45
|
+
resources: Resource[],
|
|
46
|
+
resource: Pick<Resource, 'parentSlug'>,
|
|
47
|
+
): Resource[] {
|
|
48
|
+
const chain: Resource[] = []
|
|
49
|
+
const seen = new Set<string>()
|
|
50
|
+
let current = getParentResource(resources, resource)
|
|
51
|
+
while (current && !seen.has(current.slug)) {
|
|
52
|
+
seen.add(current.slug)
|
|
53
|
+
if (current.accessRequest) chain.unshift(current)
|
|
54
|
+
current = getParentResource(resources, current)
|
|
55
|
+
}
|
|
56
|
+
return chain
|
|
57
|
+
}
|
|
58
|
+
|
|
28
59
|
/** @deprecated Use getChildResources instead */
|
|
29
60
|
export const getSubResourcesForApp = getChildResources
|
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
1
|
+
import { createFileRoute, stripSearchParams } from '@tanstack/react-router'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { appCatalogRouteLoader } from '~/modules/appCatalog/routeLoader'
|
|
4
4
|
import { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'
|
|
5
5
|
import { AppCatalogPage } from '~/modules/appCatalog/ui/pages/AppCatalogPage'
|
|
6
6
|
|
|
7
7
|
// Preserve the URL-synced filter params (see AppCatalogFiltersContext) on this
|
|
8
|
-
// route so they aren't stripped on navigation
|
|
9
|
-
//
|
|
8
|
+
// route so they aren't stripped on navigation. `q` (the search text) is the
|
|
9
|
+
// exception: as of #27 it lives in sessionStorage, not the URL. It stays
|
|
10
|
+
// declared here so an incoming legacy `?q=` still validates, but the
|
|
11
|
+
// `stripSearchParams` middleware removes it so shared app links stay clean.
|
|
10
12
|
const searchSchema = z.object({
|
|
11
|
-
q: z.string().optional(),
|
|
12
13
|
filterTag: z.string().optional(),
|
|
13
14
|
recent: z.string().optional(),
|
|
14
15
|
filters: z.string().optional(),
|
|
15
16
|
deprecated: z.string().optional(),
|
|
17
|
+
q: z.string().optional(),
|
|
16
18
|
})
|
|
17
19
|
|
|
18
20
|
export const Route = createFileRoute('/_layout/app/$slug')({
|
|
19
21
|
component: RouteComponent,
|
|
20
22
|
validateSearch: searchSchema,
|
|
23
|
+
search: { middlewares: [stripSearchParams(['q'])] },
|
|
21
24
|
async loader() {
|
|
22
25
|
const appCatalogLoader = await appCatalogRouteLoader()
|
|
23
26
|
return { appCatalogLoader }
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
1
|
+
import { createFileRoute, stripSearchParams } from '@tanstack/react-router'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { catalogRouteLoader } from '~/modules/appCatalog/catalogRouteLoader'
|
|
4
4
|
import { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'
|
|
5
5
|
import { AppCatalogPage } from '~/modules/appCatalog/ui/pages/AppCatalogPage'
|
|
6
6
|
|
|
7
|
+
// #27: `q` kept declared for legacy links but stripped from the URL (search
|
|
8
|
+
// text lives in sessionStorage now).
|
|
7
9
|
const searchSchema = z.object({
|
|
8
10
|
app: z.string().optional(),
|
|
9
11
|
q: z.string().optional(),
|
|
@@ -12,6 +14,7 @@ const searchSchema = z.object({
|
|
|
12
14
|
export const Route = createFileRoute('/_layout/catalog/apps/')({
|
|
13
15
|
component: RouteComponent,
|
|
14
16
|
validateSearch: searchSchema,
|
|
17
|
+
search: { middlewares: [stripSearchParams(['q'])] },
|
|
15
18
|
async loader(ctx) {
|
|
16
19
|
await catalogRouteLoader(ctx)
|
|
17
20
|
},
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
1
|
+
import { createFileRoute, stripSearchParams } from '@tanstack/react-router'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { appCatalogRouteLoader } from '~/modules/appCatalog/routeLoader'
|
|
4
4
|
import { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'
|
|
5
5
|
import { AppCatalogPage } from '~/modules/appCatalog/ui/pages/AppCatalogPage'
|
|
6
6
|
|
|
7
|
+
// #27: `q` (search text) no longer belongs in the URL — it lives in
|
|
8
|
+
// sessionStorage now. Keep it declared so incoming `?q=` validates, but the
|
|
9
|
+
// `stripSearchParams` middleware removes it so shared/bookmarked links stay clean.
|
|
7
10
|
const searchSchema = z.object({
|
|
8
11
|
filterTag: z.string().optional(),
|
|
9
12
|
q: z.string().optional(),
|
|
@@ -12,6 +15,7 @@ const searchSchema = z.object({
|
|
|
12
15
|
export const Route = createFileRoute('/_layout/')({
|
|
13
16
|
component: RouteComponent,
|
|
14
17
|
validateSearch: searchSchema,
|
|
18
|
+
search: { middlewares: [stripSearchParams(['q'])] },
|
|
15
19
|
async loader() {
|
|
16
20
|
const appCatalogLoader = await appCatalogRouteLoader()
|
|
17
21
|
return {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
1
|
+
import { createFileRoute, stripSearchParams } from '@tanstack/react-router'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { appCatalogRouteLoader } from '~/modules/appCatalog/routeLoader'
|
|
4
4
|
import { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'
|
|
5
5
|
import { ServiceDesksPage } from '~/modules/appCatalog/ui/pages/ServiceDesksPage'
|
|
6
6
|
|
|
7
|
+
// #27: `q` kept declared for legacy links but stripped from the URL (search
|
|
8
|
+
// text lives in sessionStorage now).
|
|
7
9
|
const searchSchema = z.object({
|
|
8
10
|
q: z.string().optional(),
|
|
9
11
|
})
|
|
@@ -11,6 +13,7 @@ const searchSchema = z.object({
|
|
|
11
13
|
export const Route = createFileRoute('/_layout/service-desks')({
|
|
12
14
|
component: RouteComponent,
|
|
13
15
|
validateSearch: searchSchema,
|
|
16
|
+
search: { middlewares: [stripSearchParams(['q'])] },
|
|
14
17
|
async loader() {
|
|
15
18
|
const appCatalogLoader = await appCatalogRouteLoader()
|
|
16
19
|
return { appCatalogLoader }
|
package/src/types/uiSettings.ts
CHANGED
|
@@ -13,4 +13,19 @@ export interface UiSettings {
|
|
|
13
13
|
frontendBuildId?: string
|
|
14
14
|
/** PWA auto-update configuration (idle timeout, check interval, debug) */
|
|
15
15
|
pwaAutoUpdate?: PwaAutoUpdateOptions
|
|
16
|
+
/**
|
|
17
|
+
* Optional attribution shown as a subtle footer line on the home view.
|
|
18
|
+
* Kept generic here (no hard-coded names/links) so the OSS core stays
|
|
19
|
+
* vendor-neutral; the consuming app supplies its own author + repo links.
|
|
20
|
+
*/
|
|
21
|
+
attribution?: {
|
|
22
|
+
/** e.g. "Made by <name>" — rendered verbatim. */
|
|
23
|
+
madeBy?: string
|
|
24
|
+
/** Labeled links; `kind` lets the UI hint open-source vs proprietary. */
|
|
25
|
+
links?: {
|
|
26
|
+
label: string
|
|
27
|
+
url: string
|
|
28
|
+
kind?: 'oss' | 'proprietary'
|
|
29
|
+
}[]
|
|
30
|
+
}
|
|
16
31
|
}
|
|
@@ -142,7 +142,9 @@ export function Header({ middle }: HeaderProps) {
|
|
|
142
142
|
<div className="flex items-center gap-2">
|
|
143
143
|
<AppCatalogLogo className="h-16 w-16" />
|
|
144
144
|
<div className="flex flex-col">
|
|
145
|
-
<span className="text-lg font-
|
|
145
|
+
<span className="font-serif text-lg font-semibold">
|
|
146
|
+
App Catalog
|
|
147
|
+
</span>
|
|
146
148
|
{appCatalogContextMaybe?.versions && (
|
|
147
149
|
<VersionDisplay versions={appCatalogContextMaybe.versions} />
|
|
148
150
|
)}
|