@igstack/app-catalog-frontend-core 0.10.0 → 0.11.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/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 +56 -10
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/launcher/LauncherDetailPanel.d.ts +12 -0
- package/dist/esm/modules/appCatalog/ui/launcher/LauncherDetailPanel.js +48 -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 +355 -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/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/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 +60 -11
- 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/SubResourcesSection.tsx +22 -1
- package/src/modules/appCatalog/ui/context/AppCatalogFiltersContext.tsx +11 -5
- package/src/modules/appCatalog/ui/grid/AppCatalogGrid.tsx +68 -10
- package/src/modules/appCatalog/ui/launcher/LauncherDetailPanel.tsx +55 -0
- package/src/modules/appCatalog/ui/launcher/LauncherHome.tsx +497 -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/ui/components/header/Header.tsx +3 -1
|
@@ -29,6 +29,9 @@ import {
|
|
|
29
29
|
TableRow,
|
|
30
30
|
} from '~/ui/table'
|
|
31
31
|
import { AccessRequestSection } from '../components/AccessRequestSection'
|
|
32
|
+
import { AccessPrerequisiteChain } from '../components/AccessPrerequisiteChain'
|
|
33
|
+
import { useAppCatalogFilters } from '../context/AppCatalogFiltersContext'
|
|
34
|
+
import { PersonBadge } from '../components/PersonBadge'
|
|
32
35
|
import { useUser } from '~/modules/auth'
|
|
33
36
|
import { InlineEditableField } from '../components/InlineEditableField'
|
|
34
37
|
import { MarkdownText } from '../components/MarkdownText'
|
|
@@ -173,6 +176,10 @@ function AppScreenshot({ app }: { app: Resource }) {
|
|
|
173
176
|
|
|
174
177
|
function TiersAndSubResourcesPanel({ app }: { app: Resource }) {
|
|
175
178
|
const { resources } = useAppCatalogContext()
|
|
179
|
+
// #38 item C: the active catalog search term. When the user reached this app
|
|
180
|
+
// by searching something that matched a child, seed the sub-resource filter
|
|
181
|
+
// with it so the matched child is revealed.
|
|
182
|
+
const { state: filterState } = useAppCatalogFilters()
|
|
176
183
|
const appSubResources = React.useMemo(
|
|
177
184
|
() => getChildResources(resources, app.slug),
|
|
178
185
|
[resources, app.slug],
|
|
@@ -187,14 +194,17 @@ function TiersAndSubResourcesPanel({ app }: { app: Resource }) {
|
|
|
187
194
|
)}
|
|
188
195
|
{appSubResources.length > 0 && (
|
|
189
196
|
<div className="mt-6">
|
|
190
|
-
<SubResourcesSection
|
|
197
|
+
<SubResourcesSection
|
|
198
|
+
subResources={appSubResources}
|
|
199
|
+
initialSearch={filterState.searchValue}
|
|
200
|
+
/>
|
|
191
201
|
</div>
|
|
192
202
|
)}
|
|
193
203
|
</>
|
|
194
204
|
)
|
|
195
205
|
}
|
|
196
206
|
|
|
197
|
-
function AppDetails({
|
|
207
|
+
export function AppDetails({
|
|
198
208
|
app,
|
|
199
209
|
onAppClick,
|
|
200
210
|
onClosePanel,
|
|
@@ -269,7 +279,7 @@ function AppDetails({
|
|
|
269
279
|
<AppIcon app={app} className="size-16" />
|
|
270
280
|
<div className="-mx-3 flex-1 min-w-0">
|
|
271
281
|
<div className="flex items-center gap-2 px-3">
|
|
272
|
-
<div className="text-2xl font-semibold min-w-0">
|
|
282
|
+
<div className="font-serif text-2xl font-semibold min-w-0">
|
|
273
283
|
{app.abbreviation
|
|
274
284
|
? `${app.displayName} (${app.abbreviation})`
|
|
275
285
|
: app.displayName}
|
|
@@ -333,10 +343,11 @@ function AppDetails({
|
|
|
333
343
|
target="_blank"
|
|
334
344
|
rel="noopener noreferrer"
|
|
335
345
|
onClick={() => recordClick(app.slug)}
|
|
336
|
-
className="inline-flex items-center gap-1 rounded-md py-1 text-
|
|
346
|
+
className="inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1 text-xs text-muted-foreground hover:border-primary hover:text-primary transition-all"
|
|
347
|
+
title="Open app in new tab (secondary — see access info below)"
|
|
337
348
|
>
|
|
338
349
|
{app.appUrl.replace(/https?:\/\//g, '')}
|
|
339
|
-
<ExternalLink className="size-3
|
|
350
|
+
<ExternalLink className="size-3 shrink-0 opacity-60" />
|
|
340
351
|
</a>
|
|
341
352
|
) : (
|
|
342
353
|
<span className="text-muted-foreground">—</span>
|
|
@@ -387,6 +398,13 @@ function AppDetails({
|
|
|
387
398
|
)
|
|
388
399
|
})()}
|
|
389
400
|
|
|
401
|
+
{/* Two-step access (#38 D): for a nested resource, show the parent-first
|
|
402
|
+
prerequisite chain before this resource's own access instructions. */}
|
|
403
|
+
<AccessPrerequisiteChain resource={app} onOpenParent={onAppClick} />
|
|
404
|
+
|
|
405
|
+
{/* Access Request Section — hero of the detail, shown before description */}
|
|
406
|
+
<AccessRequestSection app={app} approvalMethods={approvalMethods} />
|
|
407
|
+
|
|
390
408
|
{/* Description */}
|
|
391
409
|
<div className="mt-6">
|
|
392
410
|
<h3 className="mb-2 text-sm font-medium">Description</h3>
|
|
@@ -429,8 +447,17 @@ function AppDetails({
|
|
|
429
447
|
</div>
|
|
430
448
|
)}
|
|
431
449
|
|
|
432
|
-
{/*
|
|
433
|
-
|
|
450
|
+
{/* Owner — who is responsible for this resource. Distinct from the
|
|
451
|
+
access approver (who decides access requests); see domain model. */}
|
|
452
|
+
{app.ownerPersonSlug && (
|
|
453
|
+
<div className="mt-6">
|
|
454
|
+
<h3 className="mb-1 text-sm font-medium">Owner</h3>
|
|
455
|
+
<p className="mb-2 text-xs text-muted-foreground">
|
|
456
|
+
Who is responsible for this resource
|
|
457
|
+
</p>
|
|
458
|
+
<PersonBadge slug={app.ownerPersonSlug} />
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
434
461
|
|
|
435
462
|
{/* Tier Variants and Sub-Resources */}
|
|
436
463
|
<TiersAndSubResourcesPanel app={app} />
|
|
@@ -742,6 +769,9 @@ export function AppCatalogGrid({
|
|
|
742
769
|
onAppClick,
|
|
743
770
|
})
|
|
744
771
|
|
|
772
|
+
// Launch history for the secondary "open in new tab" action on each row.
|
|
773
|
+
const { recordClick: recordLaunch } = useAppClickHistory()
|
|
774
|
+
|
|
745
775
|
// Build a map of parentSlug -> matched child resource displayName for search annotation
|
|
746
776
|
const { resources: allResources2 } = useAppCatalogContext()
|
|
747
777
|
const matchedSubResourceMap = React.useMemo(() => {
|
|
@@ -847,8 +877,36 @@ export function AppCatalogGrid({
|
|
|
847
877
|
</div>
|
|
848
878
|
),
|
|
849
879
|
},
|
|
880
|
+
{
|
|
881
|
+
// Secondary "launch" action (#31): the primary row click opens the
|
|
882
|
+
// detail panel (how to get access); this quiet ↗ button is the fast
|
|
883
|
+
// "I just want the URL" jump. stopPropagation so it doesn't also open
|
|
884
|
+
// the panel. Only shown when the resource has a URL.
|
|
885
|
+
id: 'launch',
|
|
886
|
+
header: '',
|
|
887
|
+
cell: ({ row }) =>
|
|
888
|
+
row.original.appUrl ? (
|
|
889
|
+
<a
|
|
890
|
+
href={row.original.appUrl}
|
|
891
|
+
target="_blank"
|
|
892
|
+
rel="noopener noreferrer"
|
|
893
|
+
aria-label={`Open ${row.original.displayName} in a new tab`}
|
|
894
|
+
title={`Open ${row.original.displayName}`}
|
|
895
|
+
onClick={(e) => {
|
|
896
|
+
e.stopPropagation()
|
|
897
|
+
recordLaunch(row.original.slug)
|
|
898
|
+
}}
|
|
899
|
+
className="inline-flex size-8 items-center justify-center rounded-full border text-muted-foreground opacity-0 transition-opacity hover:border-primary hover:text-primary group-hover:opacity-100 focus-visible:opacity-100"
|
|
900
|
+
>
|
|
901
|
+
<ExternalLink className="size-4" />
|
|
902
|
+
</a>
|
|
903
|
+
) : null,
|
|
904
|
+
meta: {
|
|
905
|
+
className: 'w-[56px] text-right',
|
|
906
|
+
},
|
|
907
|
+
},
|
|
850
908
|
],
|
|
851
|
-
[searchQuery, matchedSubResourceMap],
|
|
909
|
+
[searchQuery, matchedSubResourceMap, recordLaunch],
|
|
852
910
|
)
|
|
853
911
|
|
|
854
912
|
// Create a single table instance with all apps
|
|
@@ -937,7 +995,7 @@ export function AppCatalogGrid({
|
|
|
937
995
|
className="px-4 py-6 sticky top-[49px] bg-muted/90 backdrop-blur z-10"
|
|
938
996
|
>
|
|
939
997
|
<div className="flex items-center justify-center">
|
|
940
|
-
<span className="font-
|
|
998
|
+
<span className="font-serif font-semibold text-lg tracking-widest uppercase leading-loose text-muted-foreground">
|
|
941
999
|
{group.groupName}
|
|
942
1000
|
</span>
|
|
943
1001
|
</div>
|
|
@@ -963,7 +1021,7 @@ export function AppCatalogGrid({
|
|
|
963
1021
|
}}
|
|
964
1022
|
onClick={() => handleAppClick(row.original)}
|
|
965
1023
|
className={cn(
|
|
966
|
-
'border-b cursor-pointer transition-colors',
|
|
1024
|
+
'group border-b cursor-pointer transition-colors',
|
|
967
1025
|
selectedApp?.id === row.original.id
|
|
968
1026
|
? 'bg-blue-100 dark:bg-blue-950 hover:bg-blue-200 dark:hover:bg-blue-900'
|
|
969
1027
|
: 'hover:bg-muted/30',
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Resource } from '@igstack/app-catalog-backend-core'
|
|
2
|
+
import { useEffect } from 'react'
|
|
3
|
+
import { AppDetails } from '../grid/AppCatalogGrid'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Slide-over detail panel for the launcher (#38, item B). Renders the rich
|
|
7
|
+
* AppDetails (access-hero, sub-resources, tiers — increments 3/4) as a right
|
|
8
|
+
* slide-over over the launcher backdrop, matching the option-a prototype,
|
|
9
|
+
* instead of dropping the user into the old grid + resizable split-pane.
|
|
10
|
+
*/
|
|
11
|
+
export function LauncherDetailPanel({
|
|
12
|
+
app,
|
|
13
|
+
onClose,
|
|
14
|
+
onAppClick,
|
|
15
|
+
}: {
|
|
16
|
+
app: Resource
|
|
17
|
+
onClose: () => void
|
|
18
|
+
onAppClick?: (app: Resource) => void
|
|
19
|
+
}) {
|
|
20
|
+
// Close on Escape.
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const onKey = (e: KeyboardEvent) => {
|
|
23
|
+
if (e.key === 'Escape') onClose()
|
|
24
|
+
}
|
|
25
|
+
document.addEventListener('keydown', onKey)
|
|
26
|
+
return () => document.removeEventListener('keydown', onKey)
|
|
27
|
+
}, [onClose])
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
{/* scrim */}
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
aria-label="Close details panel"
|
|
35
|
+
onClick={onClose}
|
|
36
|
+
className="fixed inset-0 z-40 bg-black/30 backdrop-blur-[2px] animate-in fade-in"
|
|
37
|
+
/>
|
|
38
|
+
{/* panel */}
|
|
39
|
+
<aside
|
|
40
|
+
role="dialog"
|
|
41
|
+
aria-modal="true"
|
|
42
|
+
aria-label={`${app.displayName} details`}
|
|
43
|
+
className="fixed right-0 top-0 z-50 h-full w-[min(560px,100%)] bg-background shadow-2xl border-l border-border flex flex-col animate-in slide-in-from-right duration-200"
|
|
44
|
+
>
|
|
45
|
+
<div className="overflow-y-auto flex-1 px-6 py-5">
|
|
46
|
+
<AppDetails
|
|
47
|
+
app={app}
|
|
48
|
+
onAppClick={onAppClick}
|
|
49
|
+
onClosePanel={onClose}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</aside>
|
|
53
|
+
</>
|
|
54
|
+
)
|
|
55
|
+
}
|