@igstack/app-catalog-frontend-core 0.4.0 → 0.6.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 +5 -1
- package/dist/esm/modules/appCatalog/context/AppCatalogContext.js +1 -1
- package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.js +1 -13
- package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/MarkdownText.d.ts +18 -0
- package/dist/esm/modules/appCatalog/ui/components/MarkdownText.js +26 -0
- package/dist/esm/modules/appCatalog/ui/components/MarkdownText.js.map +1 -0
- package/dist/esm/modules/appCatalog/ui/components/PersonBadge.js +1 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.js +1 -1
- package/dist/esm/modules/appCatalog/ui/components/TierVariantsSection.js +1 -1
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.d.ts +3 -1
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.js +5 -2
- package/dist/esm/modules/appCatalog/ui/grid/AppCatalogGrid.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.d.ts +3 -1
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js +17 -12
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js.map +1 -1
- package/dist/esm/routeTree.gen.d.ts +18 -4
- package/dist/esm/routeTree.gen.js +11 -4
- package/dist/esm/routeTree.gen.js.map +1 -1
- package/dist/esm/routes/_layout/app.$slug.d.ts +3 -0
- package/dist/esm/routes/_layout/app._slug.js +21 -0
- package/dist/esm/routes/_layout/app._slug.js.map +1 -0
- package/dist/esm/routes/_layout/index.d.ts +0 -1
- package/dist/esm/routes/_layout/index.js +0 -1
- package/dist/esm/routes/_layout/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/integration/appDeepLink.integration.test.ts +55 -0
- package/src/__tests__/integration/harness/given.tsx +7 -3
- package/src/modules/appCatalog/ui/components/AccessRequestSection.tsx +1 -18
- package/src/modules/appCatalog/ui/components/MarkdownText.tsx +42 -0
- package/src/modules/appCatalog/ui/grid/AppCatalogGrid.tsx +10 -3
- package/src/modules/appCatalog/ui/pages/AppCatalogPage.tsx +18 -15
- package/src/routeTree.gen.ts +21 -2
- package/src/routes/_layout/app.$slug.tsx +23 -0
- package/src/routes/_layout/index.tsx +0 -1
- package/dist/esm/__tests__/modules/gallery/EscapeDismissal.integration.test.d.ts +0 -0
- package/dist/esm/modules/appCatalog/ui/components/AppDetailModal.d.ts +0 -7
- package/src/__tests__/modules/appCatalog/ScreenshotPreview.test.tsx +0 -63
- package/src/__tests__/modules/gallery/EscapeDismissal.integration.test.tsx +0 -118
- package/src/modules/appCatalog/ui/components/AppDetailModal.tsx +0 -365
- /package/dist/esm/__tests__/{modules/appCatalog/ScreenshotPreview.test.d.ts → integration/appDeepLink.integration.test.d.ts} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
2
3
|
import { X } from "lucide-react";
|
|
3
4
|
import { useDeferredValue, useState, useEffect, useMemo } from "react";
|
|
4
5
|
import { Button } from "../../../../ui/button.js";
|
|
@@ -6,20 +7,19 @@ import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyCont
|
|
|
6
7
|
import { useAppCatalogContext } from "../../context/AppCatalogContext.js";
|
|
7
8
|
import { useAppClickHistory } from "../../hooks/useAppClickHistory.js";
|
|
8
9
|
import { useAppCounts } from "../../hooks/useAppCounts.js";
|
|
9
|
-
import { useUrlSyncedState } from "../../hooks/useUrlSyncedState.js";
|
|
10
10
|
import { searchResources } from "../../utils/searchApps.js";
|
|
11
11
|
import { OnboardingCard } from "../components/OnboardingCard.js";
|
|
12
12
|
import { useAppCatalogFilters } from "../context/AppCatalogFiltersContext.js";
|
|
13
13
|
import { FilterBar } from "../filters/FilterBar.js";
|
|
14
14
|
import { AppCatalogGrid } from "../grid/AppCatalogGrid.js";
|
|
15
|
-
function AppCatalogPage(
|
|
15
|
+
function AppCatalogPage({
|
|
16
|
+
selectedSlug
|
|
17
|
+
} = {}) {
|
|
16
18
|
const { resources, isLoadingApps, tagsDefinitions } = useAppCatalogContext();
|
|
17
19
|
const { state: filterState, actions } = useAppCatalogFilters();
|
|
18
20
|
const { getTopApps } = useAppClickHistory();
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
defaultValue: void 0
|
|
22
|
-
});
|
|
21
|
+
const navigate = useNavigate();
|
|
22
|
+
const selectedAppSlug = selectedSlug;
|
|
23
23
|
const searchValue = filterState.searchValue;
|
|
24
24
|
const setSearchValue = actions.setSearchValue;
|
|
25
25
|
const deferredSearchValue = useDeferredValue(searchValue);
|
|
@@ -72,17 +72,21 @@ function AppCatalogPage() {
|
|
|
72
72
|
searchValue: deferredSearchValue
|
|
73
73
|
});
|
|
74
74
|
useEffect(() => {
|
|
75
|
-
if (filteredApps.length === 1 && filteredApps[0]) {
|
|
76
|
-
|
|
75
|
+
if (filteredApps.length === 1 && filteredApps[0] && !selectedAppSlug) {
|
|
76
|
+
void navigate({
|
|
77
|
+
to: "/app/$slug",
|
|
78
|
+
params: { slug: filteredApps[0].slug },
|
|
79
|
+
replace: true
|
|
80
|
+
});
|
|
77
81
|
}
|
|
78
|
-
}, [filteredApps,
|
|
82
|
+
}, [filteredApps, selectedAppSlug, navigate]);
|
|
79
83
|
const handleAppClick = (app) => {
|
|
80
|
-
|
|
84
|
+
void navigate({ to: "/app/$slug", params: { slug: app.slug } });
|
|
81
85
|
};
|
|
82
86
|
const handleClearFilters = () => {
|
|
83
87
|
setSearchValue("");
|
|
84
88
|
actions.clearAllFilters();
|
|
85
|
-
|
|
89
|
+
void navigate({ to: "/" });
|
|
86
90
|
};
|
|
87
91
|
const totalAppsCount = useMemo(() => {
|
|
88
92
|
let count = rootResources.length;
|
|
@@ -121,7 +125,7 @@ function AppCatalogPage() {
|
|
|
121
125
|
variant: "outline",
|
|
122
126
|
onClick: () => {
|
|
123
127
|
setSearchValue("");
|
|
124
|
-
|
|
128
|
+
void navigate({ to: "/" });
|
|
125
129
|
},
|
|
126
130
|
className: "gap-2",
|
|
127
131
|
children: [
|
|
@@ -137,6 +141,7 @@ function AppCatalogPage() {
|
|
|
137
141
|
selectedAppSlug,
|
|
138
142
|
groupingDefinition,
|
|
139
143
|
onAppClick: handleAppClick,
|
|
144
|
+
onClosePanel: () => void navigate({ to: "/" }),
|
|
140
145
|
hasSearch: !!deferredSearchValue,
|
|
141
146
|
searchQuery: deferredSearchValue,
|
|
142
147
|
totalAppsCount,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppCatalogPage.js","sources":["../../../../../../src/modules/appCatalog/ui/pages/AppCatalogPage.tsx"],"sourcesContent":["import type { Resource } from '@igstack/app-catalog-backend-core'\nimport { X } from 'lucide-react'\nimport { useDeferredValue, useEffect, useMemo, useState } from 'react'\nimport { Button } from '~/ui/button'\nimport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n EmptyTitle,\n} from '~/ui/empty'\nimport { useAppCatalogContext } from '../../context/AppCatalogContext'\nimport { useAppClickHistory } from '../../hooks/useAppClickHistory'\nimport { useAppCounts } from '../../hooks/useAppCounts'\nimport { useUrlSyncedState } from '../../hooks/useUrlSyncedState'\nimport { searchResources } from '../../utils/searchApps'\nimport { OnboardingCard } from '../components/OnboardingCard'\nimport { useAppCatalogFilters } from '../context/AppCatalogFiltersContext'\nimport { FilterBar } from '../filters/FilterBar'\nimport { AppCatalogGrid } from '../grid/AppCatalogGrid'\n\nexport function AppCatalogPage() {\n const { resources, isLoadingApps, tagsDefinitions } = useAppCatalogContext()\n const { state: filterState, actions } = useAppCatalogFilters()\n const { getTopApps } = useAppClickHistory()\n\n // URL-synced state\n const [selectedAppSlug, setSelectedAppSlug] = useUrlSyncedState<\n string | undefined\n >({\n key: 'app',\n defaultValue: undefined,\n })\n\n // Search value from context (URL-synced in AppCatalogFiltersContext)\n const searchValue = filterState.searchValue\n const setSearchValue = actions.setSearchValue\n\n // Defer the search value used for filtering to avoid blocking the input\n const deferredSearchValue = useDeferredValue(searchValue)\n\n // State for top apps (loaded async)\n const [topAppSlugs, setTopAppSlugs] = useState<string[]>([])\n\n // Load top apps on mount to calculate recent count\n useEffect(() => {\n void getTopApps(10).then(setTopAppSlugs)\n }, [getTopApps])\n\n // Get root resources for filtering (children handled internally by searchResources)\n const rootResources = useMemo(\n () => resources.filter((r) => !r.parentSlug),\n [resources],\n )\n\n const filteredApps = useMemo(() => {\n let result = rootResources\n\n // Step 1: Filter deprecated apps (if not showing them)\n if (!filterState.showDeprecated) {\n result = result.filter((app) => !app.deprecated)\n }\n\n // Step 2: Apply recent mode or tag filters\n if (filterState.recentMode) {\n // Filter to top 10 most clicked apps\n result = result.filter((app) => topAppSlugs.includes(app.slug))\n } else if (Object.keys(filterState.tagFilters).length > 0) {\n // Apply tag filters (AND condition)\n result = result.filter((app) => {\n return Object.entries(filterState.tagFilters).every(\n ([prefix, value]) => {\n const fullTag = `${prefix}:${value}`\n return app.tags?.some(\n (tag) => tag.toLowerCase() === fullTag.toLowerCase(),\n )\n },\n )\n })\n }\n\n // Step 3: Apply search (using deferred value)\n // Pass all resources so children contribute to parent scoring\n const childResources = resources.filter((r) => r.parentSlug)\n result = searchResources(\n [...result, ...childResources],\n deferredSearchValue,\n )\n\n return result\n }, [\n rootResources,\n resources,\n deferredSearchValue,\n filterState.recentMode,\n filterState.tagFilters,\n filterState.showDeprecated,\n topAppSlugs,\n ])\n\n // Calculate counts for FilterBar\n const { allCount, recentCount, deprecatedCount } = useAppCounts({\n apps: rootResources,\n topAppSlugs,\n searchValue: deferredSearchValue,\n })\n\n // Auto-open details when only 1 result\n useEffect(() => {\n if (filteredApps.length === 1 && filteredApps[0]) {\n setSelectedAppSlug(filteredApps[0].slug)\n }\n }, [filteredApps, setSelectedAppSlug])\n\n const handleAppClick = (app: Resource) => {\n setSelectedAppSlug(app.slug)\n }\n\n const handleClearFilters = () => {\n setSearchValue('')\n actions.clearAllFilters()\n setSelectedAppSlug(undefined)\n }\n\n // Calculate total apps count (respecting showDeprecated setting)\n const totalAppsCount = useMemo(() => {\n let count = rootResources.length\n if (!filterState.showDeprecated) {\n count = rootResources.filter((app) => !app.deprecated).length\n }\n return count\n }, [rootResources, filterState.showDeprecated])\n\n if (isLoadingApps) {\n return <div className=\"py-6 text-muted-foreground\">Loading…</div>\n }\n\n // Use first tag definition for grouping\n const groupingDefinition = tagsDefinitions[0]\n\n return (\n <div className=\"flex flex-col flex-1 min-h-0\">\n <div className=\"shrink-0\">\n <OnboardingCard />\n </div>\n\n <div className=\"shrink-0\">\n <FilterBar\n totalCount={allCount}\n recentCount={recentCount}\n deprecatedCount={deprecatedCount}\n apps={rootResources}\n />\n </div>\n\n <div className=\"flex-1 min-h-0\">\n {filteredApps.length === 0 ? (\n <Empty>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <X className=\"h-6 w-6\" />\n </EmptyMedia>\n <EmptyTitle>\n No apps found{searchValue && ` for \"${searchValue}\"`}\n </EmptyTitle>\n <EmptyDescription>\n Try adjusting your search or filters\n </EmptyDescription>\n </EmptyHeader>\n <EmptyContent>\n {searchValue && (\n <Button\n variant=\"outline\"\n onClick={() => {\n setSearchValue('')\n setSelectedAppSlug(undefined)\n }}\n className=\"gap-2\"\n >\n <X className=\"h-4 w-4\" />\n Clear search\n </Button>\n )}\n </EmptyContent>\n </Empty>\n ) : (\n <AppCatalogGrid\n apps={filteredApps}\n selectedAppSlug={selectedAppSlug}\n groupingDefinition={groupingDefinition}\n onAppClick={handleAppClick}\n hasSearch={!!deferredSearchValue}\n searchQuery={deferredSearchValue}\n totalAppsCount={totalAppsCount}\n onClearFilters={handleClearFilters}\n />\n )}\n </div>\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppCatalogPage.js","sources":["../../../../../../src/modules/appCatalog/ui/pages/AppCatalogPage.tsx"],"sourcesContent":["import type { Resource } from '@igstack/app-catalog-backend-core'\nimport { useNavigate } from '@tanstack/react-router'\nimport { X } from 'lucide-react'\nimport { useDeferredValue, useEffect, useMemo, useState } from 'react'\nimport { Button } from '~/ui/button'\nimport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n EmptyTitle,\n} from '~/ui/empty'\nimport { useAppCatalogContext } from '../../context/AppCatalogContext'\nimport { useAppClickHistory } from '../../hooks/useAppClickHistory'\nimport { useAppCounts } from '../../hooks/useAppCounts'\nimport { searchResources } from '../../utils/searchApps'\nimport { OnboardingCard } from '../components/OnboardingCard'\nimport { useAppCatalogFilters } from '../context/AppCatalogFiltersContext'\nimport { FilterBar } from '../filters/FilterBar'\nimport { AppCatalogGrid } from '../grid/AppCatalogGrid'\n\nexport function AppCatalogPage({\n selectedSlug,\n}: { selectedSlug?: string } = {}) {\n const { resources, isLoadingApps, tagsDefinitions } = useAppCatalogContext()\n const { state: filterState, actions } = useAppCatalogFilters()\n const { getTopApps } = useAppClickHistory()\n const navigate = useNavigate()\n\n // Selected app comes from the route path (/app/<slug>); undefined = nothing open\n const selectedAppSlug = selectedSlug\n\n // Search value from context (URL-synced in AppCatalogFiltersContext)\n const searchValue = filterState.searchValue\n const setSearchValue = actions.setSearchValue\n\n // Defer the search value used for filtering to avoid blocking the input\n const deferredSearchValue = useDeferredValue(searchValue)\n\n // State for top apps (loaded async)\n const [topAppSlugs, setTopAppSlugs] = useState<string[]>([])\n\n // Load top apps on mount to calculate recent count\n useEffect(() => {\n void getTopApps(10).then(setTopAppSlugs)\n }, [getTopApps])\n\n // Get root resources for filtering (children handled internally by searchResources)\n const rootResources = useMemo(\n () => resources.filter((r) => !r.parentSlug),\n [resources],\n )\n\n const filteredApps = useMemo(() => {\n let result = rootResources\n\n // Step 1: Filter deprecated apps (if not showing them)\n if (!filterState.showDeprecated) {\n result = result.filter((app) => !app.deprecated)\n }\n\n // Step 2: Apply recent mode or tag filters\n if (filterState.recentMode) {\n // Filter to top 10 most clicked apps\n result = result.filter((app) => topAppSlugs.includes(app.slug))\n } else if (Object.keys(filterState.tagFilters).length > 0) {\n // Apply tag filters (AND condition)\n result = result.filter((app) => {\n return Object.entries(filterState.tagFilters).every(\n ([prefix, value]) => {\n const fullTag = `${prefix}:${value}`\n return app.tags?.some(\n (tag) => tag.toLowerCase() === fullTag.toLowerCase(),\n )\n },\n )\n })\n }\n\n // Step 3: Apply search (using deferred value)\n // Pass all resources so children contribute to parent scoring\n const childResources = resources.filter((r) => r.parentSlug)\n result = searchResources(\n [...result, ...childResources],\n deferredSearchValue,\n )\n\n return result\n }, [\n rootResources,\n resources,\n deferredSearchValue,\n filterState.recentMode,\n filterState.tagFilters,\n filterState.showDeprecated,\n topAppSlugs,\n ])\n\n // Calculate counts for FilterBar\n const { allCount, recentCount, deprecatedCount } = useAppCounts({\n apps: rootResources,\n topAppSlugs,\n searchValue: deferredSearchValue,\n })\n\n // Auto-open details when only 1 result\n useEffect(() => {\n if (filteredApps.length === 1 && filteredApps[0] && !selectedAppSlug) {\n void navigate({\n to: '/app/$slug',\n params: { slug: filteredApps[0].slug },\n replace: true,\n })\n }\n }, [filteredApps, selectedAppSlug, navigate])\n\n const handleAppClick = (app: Resource) => {\n void navigate({ to: '/app/$slug', params: { slug: app.slug } })\n }\n\n const handleClearFilters = () => {\n setSearchValue('')\n actions.clearAllFilters()\n void navigate({ to: '/' })\n }\n\n // Calculate total apps count (respecting showDeprecated setting)\n const totalAppsCount = useMemo(() => {\n let count = rootResources.length\n if (!filterState.showDeprecated) {\n count = rootResources.filter((app) => !app.deprecated).length\n }\n return count\n }, [rootResources, filterState.showDeprecated])\n\n if (isLoadingApps) {\n return <div className=\"py-6 text-muted-foreground\">Loading…</div>\n }\n\n // Use first tag definition for grouping\n const groupingDefinition = tagsDefinitions[0]\n\n return (\n <div className=\"flex flex-col flex-1 min-h-0\">\n <div className=\"shrink-0\">\n <OnboardingCard />\n </div>\n\n <div className=\"shrink-0\">\n <FilterBar\n totalCount={allCount}\n recentCount={recentCount}\n deprecatedCount={deprecatedCount}\n apps={rootResources}\n />\n </div>\n\n <div className=\"flex-1 min-h-0\">\n {filteredApps.length === 0 ? (\n <Empty>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <X className=\"h-6 w-6\" />\n </EmptyMedia>\n <EmptyTitle>\n No apps found{searchValue && ` for \"${searchValue}\"`}\n </EmptyTitle>\n <EmptyDescription>\n Try adjusting your search or filters\n </EmptyDescription>\n </EmptyHeader>\n <EmptyContent>\n {searchValue && (\n <Button\n variant=\"outline\"\n onClick={() => {\n setSearchValue('')\n void navigate({ to: '/' })\n }}\n className=\"gap-2\"\n >\n <X className=\"h-4 w-4\" />\n Clear search\n </Button>\n )}\n </EmptyContent>\n </Empty>\n ) : (\n <AppCatalogGrid\n apps={filteredApps}\n selectedAppSlug={selectedAppSlug}\n groupingDefinition={groupingDefinition}\n onAppClick={handleAppClick}\n onClosePanel={() => void navigate({ to: '/' })}\n hasSearch={!!deferredSearchValue}\n searchQuery={deferredSearchValue}\n totalAppsCount={totalAppsCount}\n onClearFilters={handleClearFilters}\n />\n )}\n </div>\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -3,10 +3,10 @@ import { Route as LayoutRouteImport } from './routes/_layout.js';
|
|
|
3
3
|
import { Route as LayoutIndexRouteImport } from './routes/_layout/index.js';
|
|
4
4
|
import { Route as AuthCallbackRouteImport } from './routes/auth.callback';
|
|
5
5
|
import { Route as LayoutLoginRouteImport } from './routes/_layout/login.js';
|
|
6
|
+
import { Route as LayoutAppSlugRouteImport } from './routes/_layout/app.$slug';
|
|
6
7
|
import { Route as LayoutCatalogAppsIndexRouteImport } from './routes/_layout/catalog.apps.index';
|
|
7
8
|
declare const LayoutRoute: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('./types/types.js').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "", "/", "/_layout", "/_layout", undefined, import('@tanstack/router-core').ResolveParams<"">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
8
9
|
declare const LayoutIndexRoute: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('./types/types.js').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "", "/", "/_layout", "/_layout", undefined, import('@tanstack/router-core').ResolveParams<"">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", "/", "/_layout/", "/_layout/", import('zod').ZodObject<{
|
|
9
|
-
app: import('zod').ZodOptional<import('zod').ZodString>;
|
|
10
10
|
filterTag: import('zod').ZodOptional<import('zod').ZodString>;
|
|
11
11
|
}, import('zod/v4/core').$strip>, import('@tanstack/router-core').ResolveParams<"/">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, () => Promise<{
|
|
12
12
|
appCatalogLoader: import('./modules/appCatalog/routeLoader.js').AppCatalogLoaderReturn;
|
|
@@ -16,6 +16,9 @@ declare const AuthCallbackRoute: import('@tanstack/router-core').Route<import('@
|
|
|
16
16
|
code: string | undefined;
|
|
17
17
|
}, import('@tanstack/router-core').ResolveParams<"/auth/callback">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
18
18
|
declare const LayoutLoginRoute: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('./types/types.js').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "", "/", "/_layout", "/_layout", undefined, import('@tanstack/router-core').ResolveParams<"">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/login", "/login", "/_layout/login", "/_layout/login", undefined, import('@tanstack/router-core').ResolveParams<"/login">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
19
|
+
declare const LayoutAppSlugRoute: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('./types/types.js').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "", "/", "/_layout", "/_layout", undefined, import('@tanstack/router-core').ResolveParams<"">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/app/$slug", "/app/$slug", "/_layout/app/$slug", "/_layout/app/$slug", undefined, import('@tanstack/router-core').ResolveParams<"/app/$slug">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, () => Promise<{
|
|
20
|
+
appCatalogLoader: import('./modules/appCatalog/routeLoader.js').AppCatalogLoaderReturn;
|
|
21
|
+
}>, unknown, unknown, unknown, unknown, undefined>;
|
|
19
22
|
declare const LayoutCatalogAppsIndexRoute: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('./types/types.js').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "", "/", "/_layout", "/_layout", undefined, import('@tanstack/router-core').ResolveParams<"">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/catalog/apps", "/catalog/apps/", "/_layout/catalog/apps/", "/_layout/catalog/apps/", import('zod').ZodObject<{
|
|
20
23
|
app: import('zod').ZodOptional<import('zod').ZodString>;
|
|
21
24
|
q: import('zod').ZodOptional<import('zod').ZodString>;
|
|
@@ -24,12 +27,14 @@ export interface FileRoutesByFullPath {
|
|
|
24
27
|
'/': typeof LayoutIndexRoute;
|
|
25
28
|
'/login': typeof LayoutLoginRoute;
|
|
26
29
|
'/auth/callback': typeof AuthCallbackRoute;
|
|
30
|
+
'/app/$slug': typeof LayoutAppSlugRoute;
|
|
27
31
|
'/catalog/apps/': typeof LayoutCatalogAppsIndexRoute;
|
|
28
32
|
}
|
|
29
33
|
export interface FileRoutesByTo {
|
|
30
34
|
'/login': typeof LayoutLoginRoute;
|
|
31
35
|
'/auth/callback': typeof AuthCallbackRoute;
|
|
32
36
|
'/': typeof LayoutIndexRoute;
|
|
37
|
+
'/app/$slug': typeof LayoutAppSlugRoute;
|
|
33
38
|
'/catalog/apps': typeof LayoutCatalogAppsIndexRoute;
|
|
34
39
|
}
|
|
35
40
|
export interface FileRoutesById {
|
|
@@ -38,14 +43,15 @@ export interface FileRoutesById {
|
|
|
38
43
|
'/_layout/login': typeof LayoutLoginRoute;
|
|
39
44
|
'/auth/callback': typeof AuthCallbackRoute;
|
|
40
45
|
'/_layout/': typeof LayoutIndexRoute;
|
|
46
|
+
'/_layout/app/$slug': typeof LayoutAppSlugRoute;
|
|
41
47
|
'/_layout/catalog/apps/': typeof LayoutCatalogAppsIndexRoute;
|
|
42
48
|
}
|
|
43
49
|
export interface FileRouteTypes {
|
|
44
50
|
fileRoutesByFullPath: FileRoutesByFullPath;
|
|
45
|
-
fullPaths: '/' | '/login' | '/auth/callback' | '/catalog/apps/';
|
|
51
|
+
fullPaths: '/' | '/login' | '/auth/callback' | '/app/$slug' | '/catalog/apps/';
|
|
46
52
|
fileRoutesByTo: FileRoutesByTo;
|
|
47
|
-
to: '/login' | '/auth/callback' | '/' | '/catalog/apps';
|
|
48
|
-
id: '__root__' | '/_layout' | '/_layout/login' | '/auth/callback' | '/_layout/' | '/_layout/catalog/apps/';
|
|
53
|
+
to: '/login' | '/auth/callback' | '/' | '/app/$slug' | '/catalog/apps';
|
|
54
|
+
id: '__root__' | '/_layout' | '/_layout/login' | '/auth/callback' | '/_layout/' | '/_layout/app/$slug' | '/_layout/catalog/apps/';
|
|
49
55
|
fileRoutesById: FileRoutesById;
|
|
50
56
|
}
|
|
51
57
|
export interface RootRouteChildren {
|
|
@@ -82,6 +88,13 @@ declare module '@tanstack/react-router' {
|
|
|
82
88
|
preLoaderRoute: typeof LayoutLoginRouteImport;
|
|
83
89
|
parentRoute: typeof LayoutRoute;
|
|
84
90
|
};
|
|
91
|
+
'/_layout/app/$slug': {
|
|
92
|
+
id: '/_layout/app/$slug';
|
|
93
|
+
path: '/app/$slug';
|
|
94
|
+
fullPath: '/app/$slug';
|
|
95
|
+
preLoaderRoute: typeof LayoutAppSlugRouteImport;
|
|
96
|
+
parentRoute: typeof LayoutRoute;
|
|
97
|
+
};
|
|
85
98
|
'/_layout/catalog/apps/': {
|
|
86
99
|
id: '/_layout/catalog/apps/';
|
|
87
100
|
path: '/catalog/apps';
|
|
@@ -94,6 +107,7 @@ declare module '@tanstack/react-router' {
|
|
|
94
107
|
interface LayoutRouteChildren {
|
|
95
108
|
LayoutLoginRoute: typeof LayoutLoginRoute;
|
|
96
109
|
LayoutIndexRoute: typeof LayoutIndexRoute;
|
|
110
|
+
LayoutAppSlugRoute: typeof LayoutAppSlugRoute;
|
|
97
111
|
LayoutCatalogAppsIndexRoute: typeof LayoutCatalogAppsIndexRoute;
|
|
98
112
|
}
|
|
99
113
|
declare const LayoutRouteChildren: LayoutRouteChildren;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Route } from "./routes/__root.js";
|
|
2
2
|
import { Route as Route$2 } from "./routes/_layout.js";
|
|
3
|
-
import { Route as Route$
|
|
3
|
+
import { Route as Route$5 } from "./routes/_layout/index.js";
|
|
4
4
|
import { Route as Route$1 } from "./routes/auth.callback.js";
|
|
5
|
-
import { Route as Route$
|
|
5
|
+
import { Route as Route$6 } from "./routes/_layout/login.js";
|
|
6
|
+
import { Route as Route$4 } from "./routes/_layout/app._slug.js";
|
|
6
7
|
import { Route as Route$3 } from "./routes/_layout/catalog.apps.index.js";
|
|
7
8
|
const LayoutRoute = Route$2.update({
|
|
8
9
|
id: "/_layout",
|
|
9
10
|
getParentRoute: () => Route
|
|
10
11
|
});
|
|
11
|
-
const LayoutIndexRoute = Route$
|
|
12
|
+
const LayoutIndexRoute = Route$5.update({
|
|
12
13
|
id: "/",
|
|
13
14
|
path: "/",
|
|
14
15
|
getParentRoute: () => LayoutRoute
|
|
@@ -18,11 +19,16 @@ const AuthCallbackRoute = Route$1.update({
|
|
|
18
19
|
path: "/auth/callback",
|
|
19
20
|
getParentRoute: () => Route
|
|
20
21
|
});
|
|
21
|
-
const LayoutLoginRoute = Route$
|
|
22
|
+
const LayoutLoginRoute = Route$6.update({
|
|
22
23
|
id: "/login",
|
|
23
24
|
path: "/login",
|
|
24
25
|
getParentRoute: () => LayoutRoute
|
|
25
26
|
});
|
|
27
|
+
const LayoutAppSlugRoute = Route$4.update({
|
|
28
|
+
id: "/app/$slug",
|
|
29
|
+
path: "/app/$slug",
|
|
30
|
+
getParentRoute: () => LayoutRoute
|
|
31
|
+
});
|
|
26
32
|
const LayoutCatalogAppsIndexRoute = Route$3.update({
|
|
27
33
|
id: "/catalog/apps/",
|
|
28
34
|
path: "/catalog/apps/",
|
|
@@ -31,6 +37,7 @@ const LayoutCatalogAppsIndexRoute = Route$3.update({
|
|
|
31
37
|
const LayoutRouteChildren = {
|
|
32
38
|
LayoutLoginRoute,
|
|
33
39
|
LayoutIndexRoute,
|
|
40
|
+
LayoutAppSlugRoute,
|
|
34
41
|
LayoutCatalogAppsIndexRoute
|
|
35
42
|
};
|
|
36
43
|
const LayoutRouteWithChildren = LayoutRoute._addFileChildren(LayoutRouteChildren);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routeTree.gen.js","sources":["../../src/routeTree.gen.ts"],"sourcesContent":["/* eslint-disable */\n\n// @ts-nocheck\n\n// noinspection JSUnusedGlobalSymbols\n\n// This file was automatically generated by TanStack Router.\n// You should NOT make any changes in this file as it will be overwritten.\n// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.\n\nimport { Route as rootRouteImport } from './routes/__root'\nimport { Route as LayoutRouteImport } from './routes/_layout'\nimport { Route as LayoutIndexRouteImport } from './routes/_layout/index'\nimport { Route as AuthCallbackRouteImport } from './routes/auth.callback'\nimport { Route as LayoutLoginRouteImport } from './routes/_layout/login'\nimport { Route as LayoutCatalogAppsIndexRouteImport } from './routes/_layout/catalog.apps.index'\n\nconst LayoutRoute = LayoutRouteImport.update({\n id: '/_layout',\n getParentRoute: () => rootRouteImport,\n} as any)\nconst LayoutIndexRoute = LayoutIndexRouteImport.update({\n id: '/',\n path: '/',\n getParentRoute: () => LayoutRoute,\n} as any)\nconst AuthCallbackRoute = AuthCallbackRouteImport.update({\n id: '/auth/callback',\n path: '/auth/callback',\n getParentRoute: () => rootRouteImport,\n} as any)\nconst LayoutLoginRoute = LayoutLoginRouteImport.update({\n id: '/login',\n path: '/login',\n getParentRoute: () => LayoutRoute,\n} as any)\nconst LayoutCatalogAppsIndexRoute = LayoutCatalogAppsIndexRouteImport.update({\n id: '/catalog/apps/',\n path: '/catalog/apps/',\n getParentRoute: () => LayoutRoute,\n} as any)\n\nexport interface FileRoutesByFullPath {\n '/': typeof LayoutIndexRoute\n '/login': typeof LayoutLoginRoute\n '/auth/callback': typeof AuthCallbackRoute\n '/catalog/apps/': typeof LayoutCatalogAppsIndexRoute\n}\nexport interface FileRoutesByTo {\n '/login': typeof LayoutLoginRoute\n '/auth/callback': typeof AuthCallbackRoute\n '/': typeof LayoutIndexRoute\n '/catalog/apps': typeof LayoutCatalogAppsIndexRoute\n}\nexport interface FileRoutesById {\n __root__: typeof rootRouteImport\n '/_layout': typeof LayoutRouteWithChildren\n '/_layout/login': typeof LayoutLoginRoute\n '/auth/callback': typeof AuthCallbackRoute\n '/_layout/': typeof LayoutIndexRoute\n '/_layout/catalog/apps/': typeof LayoutCatalogAppsIndexRoute\n}\nexport interface FileRouteTypes {\n fileRoutesByFullPath: FileRoutesByFullPath\n fullPaths: '/' | '/login' | '/auth/callback' | '/catalog/apps/'\n fileRoutesByTo: FileRoutesByTo\n to: '/login' | '/auth/callback' | '/' | '/catalog/apps'\n id:\n | '__root__'\n | '/_layout'\n | '/_layout/login'\n | '/auth/callback'\n | '/_layout/'\n | '/_layout/catalog/apps/'\n fileRoutesById: FileRoutesById\n}\nexport interface RootRouteChildren {\n LayoutRoute: typeof LayoutRouteWithChildren\n AuthCallbackRoute: typeof AuthCallbackRoute\n}\n\ndeclare module '@tanstack/react-router' {\n interface FileRoutesByPath {\n '/_layout': {\n id: '/_layout'\n path: ''\n fullPath: '/'\n preLoaderRoute: typeof LayoutRouteImport\n parentRoute: typeof rootRouteImport\n }\n '/_layout/': {\n id: '/_layout/'\n path: '/'\n fullPath: '/'\n preLoaderRoute: typeof LayoutIndexRouteImport\n parentRoute: typeof LayoutRoute\n }\n '/auth/callback': {\n id: '/auth/callback'\n path: '/auth/callback'\n fullPath: '/auth/callback'\n preLoaderRoute: typeof AuthCallbackRouteImport\n parentRoute: typeof rootRouteImport\n }\n '/_layout/login': {\n id: '/_layout/login'\n path: '/login'\n fullPath: '/login'\n preLoaderRoute: typeof LayoutLoginRouteImport\n parentRoute: typeof LayoutRoute\n }\n '/_layout/catalog/apps/': {\n id: '/_layout/catalog/apps/'\n path: '/catalog/apps'\n fullPath: '/catalog/apps/'\n preLoaderRoute: typeof LayoutCatalogAppsIndexRouteImport\n parentRoute: typeof LayoutRoute\n }\n }\n}\n\ninterface LayoutRouteChildren {\n LayoutLoginRoute: typeof LayoutLoginRoute\n LayoutIndexRoute: typeof LayoutIndexRoute\n LayoutCatalogAppsIndexRoute: typeof LayoutCatalogAppsIndexRoute\n}\n\nconst LayoutRouteChildren: LayoutRouteChildren = {\n LayoutLoginRoute: LayoutLoginRoute,\n LayoutIndexRoute: LayoutIndexRoute,\n LayoutCatalogAppsIndexRoute: LayoutCatalogAppsIndexRoute,\n}\n\nconst LayoutRouteWithChildren =\n LayoutRoute._addFileChildren(LayoutRouteChildren)\n\nconst rootRouteChildren: RootRouteChildren = {\n LayoutRoute: LayoutRouteWithChildren,\n AuthCallbackRoute: AuthCallbackRoute,\n}\nexport const routeTree = rootRouteImport\n ._addFileChildren(rootRouteChildren)\n ._addFileTypes<FileRouteTypes>()\n"],"names":["LayoutRouteImport","rootRouteImport","LayoutIndexRouteImport","AuthCallbackRouteImport","LayoutLoginRouteImport","LayoutCatalogAppsIndexRouteImport"],"mappings":"
|
|
1
|
+
{"version":3,"file":"routeTree.gen.js","sources":["../../src/routeTree.gen.ts"],"sourcesContent":["/* eslint-disable */\n\n// @ts-nocheck\n\n// noinspection JSUnusedGlobalSymbols\n\n// This file was automatically generated by TanStack Router.\n// You should NOT make any changes in this file as it will be overwritten.\n// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.\n\nimport { Route as rootRouteImport } from './routes/__root'\nimport { Route as LayoutRouteImport } from './routes/_layout'\nimport { Route as LayoutIndexRouteImport } from './routes/_layout/index'\nimport { Route as AuthCallbackRouteImport } from './routes/auth.callback'\nimport { Route as LayoutLoginRouteImport } from './routes/_layout/login'\nimport { Route as LayoutAppSlugRouteImport } from './routes/_layout/app.$slug'\nimport { Route as LayoutCatalogAppsIndexRouteImport } from './routes/_layout/catalog.apps.index'\n\nconst LayoutRoute = LayoutRouteImport.update({\n id: '/_layout',\n getParentRoute: () => rootRouteImport,\n} as any)\nconst LayoutIndexRoute = LayoutIndexRouteImport.update({\n id: '/',\n path: '/',\n getParentRoute: () => LayoutRoute,\n} as any)\nconst AuthCallbackRoute = AuthCallbackRouteImport.update({\n id: '/auth/callback',\n path: '/auth/callback',\n getParentRoute: () => rootRouteImport,\n} as any)\nconst LayoutLoginRoute = LayoutLoginRouteImport.update({\n id: '/login',\n path: '/login',\n getParentRoute: () => LayoutRoute,\n} as any)\nconst LayoutAppSlugRoute = LayoutAppSlugRouteImport.update({\n id: '/app/$slug',\n path: '/app/$slug',\n getParentRoute: () => LayoutRoute,\n} as any)\nconst LayoutCatalogAppsIndexRoute = LayoutCatalogAppsIndexRouteImport.update({\n id: '/catalog/apps/',\n path: '/catalog/apps/',\n getParentRoute: () => LayoutRoute,\n} as any)\n\nexport interface FileRoutesByFullPath {\n '/': typeof LayoutIndexRoute\n '/login': typeof LayoutLoginRoute\n '/auth/callback': typeof AuthCallbackRoute\n '/app/$slug': typeof LayoutAppSlugRoute\n '/catalog/apps/': typeof LayoutCatalogAppsIndexRoute\n}\nexport interface FileRoutesByTo {\n '/login': typeof LayoutLoginRoute\n '/auth/callback': typeof AuthCallbackRoute\n '/': typeof LayoutIndexRoute\n '/app/$slug': typeof LayoutAppSlugRoute\n '/catalog/apps': typeof LayoutCatalogAppsIndexRoute\n}\nexport interface FileRoutesById {\n __root__: typeof rootRouteImport\n '/_layout': typeof LayoutRouteWithChildren\n '/_layout/login': typeof LayoutLoginRoute\n '/auth/callback': typeof AuthCallbackRoute\n '/_layout/': typeof LayoutIndexRoute\n '/_layout/app/$slug': typeof LayoutAppSlugRoute\n '/_layout/catalog/apps/': typeof LayoutCatalogAppsIndexRoute\n}\nexport interface FileRouteTypes {\n fileRoutesByFullPath: FileRoutesByFullPath\n fullPaths: '/' | '/login' | '/auth/callback' | '/app/$slug' | '/catalog/apps/'\n fileRoutesByTo: FileRoutesByTo\n to: '/login' | '/auth/callback' | '/' | '/app/$slug' | '/catalog/apps'\n id:\n | '__root__'\n | '/_layout'\n | '/_layout/login'\n | '/auth/callback'\n | '/_layout/'\n | '/_layout/app/$slug'\n | '/_layout/catalog/apps/'\n fileRoutesById: FileRoutesById\n}\nexport interface RootRouteChildren {\n LayoutRoute: typeof LayoutRouteWithChildren\n AuthCallbackRoute: typeof AuthCallbackRoute\n}\n\ndeclare module '@tanstack/react-router' {\n interface FileRoutesByPath {\n '/_layout': {\n id: '/_layout'\n path: ''\n fullPath: '/'\n preLoaderRoute: typeof LayoutRouteImport\n parentRoute: typeof rootRouteImport\n }\n '/_layout/': {\n id: '/_layout/'\n path: '/'\n fullPath: '/'\n preLoaderRoute: typeof LayoutIndexRouteImport\n parentRoute: typeof LayoutRoute\n }\n '/auth/callback': {\n id: '/auth/callback'\n path: '/auth/callback'\n fullPath: '/auth/callback'\n preLoaderRoute: typeof AuthCallbackRouteImport\n parentRoute: typeof rootRouteImport\n }\n '/_layout/login': {\n id: '/_layout/login'\n path: '/login'\n fullPath: '/login'\n preLoaderRoute: typeof LayoutLoginRouteImport\n parentRoute: typeof LayoutRoute\n }\n '/_layout/app/$slug': {\n id: '/_layout/app/$slug'\n path: '/app/$slug'\n fullPath: '/app/$slug'\n preLoaderRoute: typeof LayoutAppSlugRouteImport\n parentRoute: typeof LayoutRoute\n }\n '/_layout/catalog/apps/': {\n id: '/_layout/catalog/apps/'\n path: '/catalog/apps'\n fullPath: '/catalog/apps/'\n preLoaderRoute: typeof LayoutCatalogAppsIndexRouteImport\n parentRoute: typeof LayoutRoute\n }\n }\n}\n\ninterface LayoutRouteChildren {\n LayoutLoginRoute: typeof LayoutLoginRoute\n LayoutIndexRoute: typeof LayoutIndexRoute\n LayoutAppSlugRoute: typeof LayoutAppSlugRoute\n LayoutCatalogAppsIndexRoute: typeof LayoutCatalogAppsIndexRoute\n}\n\nconst LayoutRouteChildren: LayoutRouteChildren = {\n LayoutLoginRoute: LayoutLoginRoute,\n LayoutIndexRoute: LayoutIndexRoute,\n LayoutAppSlugRoute: LayoutAppSlugRoute,\n LayoutCatalogAppsIndexRoute: LayoutCatalogAppsIndexRoute,\n}\n\nconst LayoutRouteWithChildren =\n LayoutRoute._addFileChildren(LayoutRouteChildren)\n\nconst rootRouteChildren: RootRouteChildren = {\n LayoutRoute: LayoutRouteWithChildren,\n AuthCallbackRoute: AuthCallbackRoute,\n}\nexport const routeTree = rootRouteImport\n ._addFileChildren(rootRouteChildren)\n ._addFileTypes<FileRouteTypes>()\n"],"names":["LayoutRouteImport","rootRouteImport","LayoutIndexRouteImport","AuthCallbackRouteImport","LayoutLoginRouteImport","LayoutAppSlugRouteImport","LayoutCatalogAppsIndexRouteImport"],"mappings":";;;;;;;AAkBA,MAAM,cAAcA,QAAkB,OAAO;AAAA,EAC3C,IAAI;AAAA,EACJ,gBAAgB,MAAMC;AACxB,CAAQ;AACR,MAAM,mBAAmBC,QAAuB,OAAO;AAAA,EACrD,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,gBAAgB,MAAM;AACxB,CAAQ;AACR,MAAM,oBAAoBC,QAAwB,OAAO;AAAA,EACvD,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,gBAAgB,MAAMF;AACxB,CAAQ;AACR,MAAM,mBAAmBG,QAAuB,OAAO;AAAA,EACrD,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,gBAAgB,MAAM;AACxB,CAAQ;AACR,MAAM,qBAAqBC,QAAyB,OAAO;AAAA,EACzD,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,gBAAgB,MAAM;AACxB,CAAQ;AACR,MAAM,8BAA8BC,QAAkC,OAAO;AAAA,EAC3E,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,gBAAgB,MAAM;AACxB,CAAQ;AAmGR,MAAM,sBAA2C;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,0BACJ,YAAY,iBAAiB,mBAAmB;AAElD,MAAM,oBAAuC;AAAA,EAC3C,aAAa;AAAA,EACb;AACF;AACO,MAAM,YAAYL,MACtB,iBAAiB,iBAAiB,EAClC,cAAA;"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const Route: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('../../types/types.js').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "", "/", "/_layout", "/_layout", undefined, import('@tanstack/router-core').ResolveParams<"">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/app/$slug", "/app/$slug", "/_layout/app/$slug", "/_layout/app/$slug", undefined, import('@tanstack/router-core').ResolveParams<"/app/$slug">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, () => Promise<{
|
|
2
|
+
appCatalogLoader: import('~/modules/appCatalog/routeLoader').AppCatalogLoaderReturn;
|
|
3
|
+
}>, unknown, unknown, unknown, unknown, undefined>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
3
|
+
import { appCatalogRouteLoader } from "../../modules/appCatalog/routeLoader.js";
|
|
4
|
+
import { AppCatalogLayout } from "../../modules/appCatalog/ui/layout/AppCatalogLayout.js";
|
|
5
|
+
import { AppCatalogPage } from "../../modules/appCatalog/ui/pages/AppCatalogPage.js";
|
|
6
|
+
const Route = createFileRoute("/_layout/app/$slug")({
|
|
7
|
+
component: RouteComponent,
|
|
8
|
+
async loader() {
|
|
9
|
+
const appCatalogLoader = await appCatalogRouteLoader();
|
|
10
|
+
return { appCatalogLoader };
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
function RouteComponent() {
|
|
14
|
+
const { queryClient, trpcClient } = Route.useRouteContext();
|
|
15
|
+
const { slug } = Route.useParams();
|
|
16
|
+
return /* @__PURE__ */ jsx(AppCatalogLayout, { queryClient, trpcClient, children: /* @__PURE__ */ jsx(AppCatalogPage, { selectedSlug: slug }) });
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
Route
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=app._slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app._slug.js","sources":["../../../../src/routes/_layout/app.$slug.tsx"],"sourcesContent":["import { createFileRoute } from '@tanstack/react-router'\nimport { appCatalogRouteLoader } from '~/modules/appCatalog/routeLoader'\nimport { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'\nimport { AppCatalogPage } from '~/modules/appCatalog/ui/pages/AppCatalogPage'\n\nexport const Route = createFileRoute('/_layout/app/$slug')({\n component: RouteComponent,\n async loader() {\n const appCatalogLoader = await appCatalogRouteLoader()\n return { appCatalogLoader }\n },\n})\n\nfunction RouteComponent() {\n const { queryClient, trpcClient } = Route.useRouteContext()\n const { slug } = Route.useParams()\n\n return (\n <AppCatalogLayout queryClient={queryClient} trpcClient={trpcClient}>\n <AppCatalogPage selectedSlug={slug} />\n </AppCatalogLayout>\n )\n}\n"],"names":[],"mappings":";;;;;AAKO,MAAM,QAAQ,gBAAgB,oBAAoB,EAAE;AAAA,EACzD,WAAW;AAAA,EACX,MAAM,SAAS;AACb,UAAM,mBAAmB,MAAM,sBAAA;AAC/B,WAAO,EAAE,iBAAA;AAAA,EACX;AACF,CAAC;AAED,SAAS,iBAAiB;AACxB,QAAM,EAAE,aAAa,eAAe,MAAM,gBAAA;AAC1C,QAAM,EAAE,KAAA,IAAS,MAAM,UAAA;AAEvB,SACE,oBAAC,oBAAiB,aAA0B,YAC1C,8BAAC,gBAAA,EAAe,cAAc,MAAM,EAAA,CACtC;AAEJ;"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const Route: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('../../types/types.js').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "", "/", "/_layout", "/_layout", undefined, import('@tanstack/router-core').ResolveParams<"">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/", "/", "/_layout/", "/_layout/", z.ZodObject<{
|
|
3
|
-
app: z.ZodOptional<z.ZodString>;
|
|
4
3
|
filterTag: z.ZodOptional<z.ZodString>;
|
|
5
4
|
}, z.core.$strip>, import('@tanstack/router-core').ResolveParams<"/">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, () => Promise<{
|
|
6
5
|
appCatalogLoader: import('~/modules/appCatalog/routeLoader').AppCatalogLoaderReturn;
|
|
@@ -5,7 +5,6 @@ import { AppCatalogLayout } from "../../modules/appCatalog/ui/layout/AppCatalogL
|
|
|
5
5
|
import { AppCatalogPage } from "../../modules/appCatalog/ui/pages/AppCatalogPage.js";
|
|
6
6
|
import { object, string } from "../../node_modules/.pnpm/zod@4.3.5/node_modules/zod/v4/classic/schemas.js";
|
|
7
7
|
const searchSchema = object({
|
|
8
|
-
app: string().optional(),
|
|
9
8
|
filterTag: string().optional()
|
|
10
9
|
});
|
|
11
10
|
const Route = createFileRoute("/_layout/")({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/routes/_layout/index.tsx"],"sourcesContent":["import { createFileRoute } from '@tanstack/react-router'\nimport { z } from 'zod'\nimport { appCatalogRouteLoader } from '~/modules/appCatalog/routeLoader'\nimport { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'\nimport { AppCatalogPage } from '~/modules/appCatalog/ui/pages/AppCatalogPage'\n\nconst searchSchema = z.object({\n
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/routes/_layout/index.tsx"],"sourcesContent":["import { createFileRoute } from '@tanstack/react-router'\nimport { z } from 'zod'\nimport { appCatalogRouteLoader } from '~/modules/appCatalog/routeLoader'\nimport { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'\nimport { AppCatalogPage } from '~/modules/appCatalog/ui/pages/AppCatalogPage'\n\nconst searchSchema = z.object({\n filterTag: z.string().optional(),\n})\n\nexport const Route = createFileRoute('/_layout/')({\n component: RouteComponent,\n validateSearch: searchSchema,\n async loader() {\n const appCatalogLoader = await appCatalogRouteLoader()\n return {\n appCatalogLoader,\n }\n },\n})\n\nfunction RouteComponent() {\n const { queryClient, trpcClient } = Route.useRouteContext()\n\n return (\n <AppCatalogLayout queryClient={queryClient} trpcClient={trpcClient}>\n <AppCatalogPage />\n </AppCatalogLayout>\n )\n}\n"],"names":["z.object","z.string"],"mappings":";;;;;;AAMA,MAAM,eAAeA,OAAS;AAAA,EAC5B,WAAWC,OAAE,EAAS,SAAA;AACxB,CAAC;AAEM,MAAM,QAAQ,gBAAgB,WAAW,EAAE;AAAA,EAChD,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,MAAM,SAAS;AACb,UAAM,mBAAmB,MAAM,sBAAA;AAC/B,WAAO;AAAA,MACL;AAAA,IAAA;AAAA,EAEJ;AACF,CAAC;AAED,SAAS,iBAAiB;AACxB,QAAM,EAAE,aAAa,eAAe,MAAM,gBAAA;AAE1C,6BACG,kBAAA,EAAiB,aAA0B,YAC1C,UAAA,oBAAC,kBAAe,GAClB;AAEJ;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igstack/app-catalog-frontend-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Frontend core library for App Catalog",
|
|
5
5
|
"homepage": "https://github.com/lislon/app-catalog",
|
|
6
6
|
"repository": {
|
|
@@ -134,8 +134,8 @@
|
|
|
134
134
|
"vite-plugin-static-copy": "^3.1.4",
|
|
135
135
|
"vite-plugin-svgr": "^4.2.0",
|
|
136
136
|
"vitest": "^4.1.2",
|
|
137
|
-
"@igstack/app-catalog-backend-core": "0.
|
|
138
|
-
"@igstack/app-catalog-shared-core": "0.
|
|
137
|
+
"@igstack/app-catalog-backend-core": "0.6.0",
|
|
138
|
+
"@igstack/app-catalog-shared-core": "0.6.0"
|
|
139
139
|
},
|
|
140
140
|
"peerDependencies": {
|
|
141
141
|
"react": "19.1.2",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { fireEvent, waitFor } from '@testing-library/react'
|
|
3
|
+
import '@testing-library/jest-dom/vitest'
|
|
4
|
+
|
|
5
|
+
import { given } from './harness/given'
|
|
6
|
+
import { magazine } from './mock-backend/magazines'
|
|
7
|
+
|
|
8
|
+
describe('App deep-link routing', () => {
|
|
9
|
+
it('opens the app detail when navigating directly to /app/<slug>', async () => {
|
|
10
|
+
const { ui } = await given(magazine.full(), { initialRoute: '/app/jira' })
|
|
11
|
+
await waitFor(() => {
|
|
12
|
+
expect(ui.catalog.isDetailPanelOpen()).toBe(true)
|
|
13
|
+
})
|
|
14
|
+
expect(ui.catalog.getTableData().length).toBeGreaterThan(0)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('navigates to /app/<slug> when selecting an app, and pushes history', async () => {
|
|
18
|
+
const { ui, router } = await given(magazine.full(), { initialRoute: '/' })
|
|
19
|
+
await ui.catalog.openApp('Jira')
|
|
20
|
+
await waitFor(() => {
|
|
21
|
+
expect(router.state.location.pathname).toBe('/app/jira')
|
|
22
|
+
})
|
|
23
|
+
// push (not replace): catalog '/' is still in history, Back returns to it
|
|
24
|
+
expect(router.history.canGoBack()).toBe(true)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('renders the catalog with nothing open for an unknown slug', async () => {
|
|
28
|
+
const { ui } = await given(magazine.full(), {
|
|
29
|
+
initialRoute: '/app/does-not-exist',
|
|
30
|
+
})
|
|
31
|
+
await waitFor(() => {
|
|
32
|
+
expect(ui.catalog.getTableData().length).toBeGreaterThan(0)
|
|
33
|
+
})
|
|
34
|
+
expect(ui.catalog.isDetailPanelOpen()).toBe(false)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('returns to / when the detail panel is closed', async () => {
|
|
38
|
+
const { ui, router } = await given(magazine.full(), { initialRoute: '/' })
|
|
39
|
+
await ui.catalog.openApp('Jira')
|
|
40
|
+
await waitFor(() => expect(ui.catalog.isDetailPanelOpen()).toBe(true))
|
|
41
|
+
fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' })
|
|
42
|
+
await waitFor(() => {
|
|
43
|
+
expect(ui.catalog.isDetailPanelOpen()).toBe(false)
|
|
44
|
+
})
|
|
45
|
+
expect(router.state.location.pathname).toBe('/')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('ignores legacy ?app= query param (no detail opens)', async () => {
|
|
49
|
+
const { ui } = await given(magazine.full(), { initialRoute: '/?app=jira' })
|
|
50
|
+
await waitFor(() => {
|
|
51
|
+
expect(ui.catalog.getTableData().length).toBeGreaterThan(0)
|
|
52
|
+
})
|
|
53
|
+
expect(ui.catalog.isDetailPanelOpen()).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -32,6 +32,7 @@ import { GalleryTools } from '../tools/GalleryTools'
|
|
|
32
32
|
export interface GivenResult {
|
|
33
33
|
ui: UiTools
|
|
34
34
|
backend: MockBackendVerifier
|
|
35
|
+
router: ReturnType<typeof createAcRouter>
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export interface UiTools {
|
|
@@ -57,7 +58,10 @@ export async function cleanupTestResources(): Promise<void> {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
export async function given(
|
|
61
|
+
export async function given(
|
|
62
|
+
magazine: Magazine,
|
|
63
|
+
opts: { initialRoute?: string } = {},
|
|
64
|
+
): Promise<GivenResult> {
|
|
61
65
|
// Clean up any previous resources
|
|
62
66
|
await cleanupTestResources()
|
|
63
67
|
resetConfigurerCounter()
|
|
@@ -126,7 +130,7 @@ export async function given(magazine: Magazine): Promise<GivenResult> {
|
|
|
126
130
|
|
|
127
131
|
const router = createAcRouter({
|
|
128
132
|
history: createMemoryHistory({
|
|
129
|
-
initialEntries: ['/catalog/apps'],
|
|
133
|
+
initialEntries: [opts.initialRoute ?? '/catalog/apps'],
|
|
130
134
|
}),
|
|
131
135
|
context: {
|
|
132
136
|
queryClient,
|
|
@@ -187,5 +191,5 @@ export async function given(magazine: Magazine): Promise<GivenResult> {
|
|
|
187
191
|
globalError: () => getGlobalError(),
|
|
188
192
|
}
|
|
189
193
|
|
|
190
|
-
return { ui, backend }
|
|
194
|
+
return { ui, backend, router }
|
|
191
195
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
TableHeader,
|
|
21
21
|
TableRow,
|
|
22
22
|
} from '~/ui/table'
|
|
23
|
+
import { MarkdownLink } from './MarkdownText'
|
|
23
24
|
import { PersonBadge } from './PersonBadge'
|
|
24
25
|
|
|
25
26
|
// Constants
|
|
@@ -30,24 +31,6 @@ interface AccessRequestSectionProps {
|
|
|
30
31
|
approvalMethods: AppApprovalMethod[]
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
// Component for rendering markdown links with security attributes
|
|
34
|
-
const MarkdownLink = ({
|
|
35
|
-
href,
|
|
36
|
-
children,
|
|
37
|
-
}: {
|
|
38
|
-
href?: string
|
|
39
|
-
children?: React.ReactNode
|
|
40
|
-
}) => (
|
|
41
|
-
<a
|
|
42
|
-
href={href}
|
|
43
|
-
target="_blank"
|
|
44
|
-
rel="noopener noreferrer"
|
|
45
|
-
className="text-primary hover:underline"
|
|
46
|
-
>
|
|
47
|
-
{children}
|
|
48
|
-
</a>
|
|
49
|
-
)
|
|
50
|
-
|
|
51
34
|
// Helper function for approval method icons
|
|
52
35
|
function getApprovalMethodIcon(
|
|
53
36
|
type: 'service' | 'personTeam' | 'custom' | 'noAccessRequired' | 'unknown',
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
import ReactMarkdown from 'react-markdown'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Renders a markdown link with security attributes (opens in a new tab,
|
|
6
|
+
* no referrer/opener leakage). Shared so all catalog text fields render
|
|
7
|
+
* links identically.
|
|
8
|
+
*/
|
|
9
|
+
export const MarkdownLink = ({
|
|
10
|
+
href,
|
|
11
|
+
children,
|
|
12
|
+
}: {
|
|
13
|
+
href?: string
|
|
14
|
+
children?: React.ReactNode
|
|
15
|
+
}) => (
|
|
16
|
+
<a
|
|
17
|
+
href={href}
|
|
18
|
+
target="_blank"
|
|
19
|
+
rel="noopener noreferrer"
|
|
20
|
+
className="text-primary hover:underline"
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</a>
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Renders user-facing catalog text (descriptions, comments, prompts) as
|
|
28
|
+
* markdown so links become clickable. Links use {@link MarkdownLink}.
|
|
29
|
+
*/
|
|
30
|
+
export function MarkdownText({
|
|
31
|
+
children,
|
|
32
|
+
className,
|
|
33
|
+
}: {
|
|
34
|
+
children: string
|
|
35
|
+
className?: string
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<span className={className}>
|
|
39
|
+
<ReactMarkdown components={{ a: MarkdownLink }}>{children}</ReactMarkdown>
|
|
40
|
+
</span>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { AccessRequestSection } from '../components/AccessRequestSection'
|
|
32
32
|
import { useUser } from '~/modules/auth'
|
|
33
33
|
import { InlineEditableField } from '../components/InlineEditableField'
|
|
34
|
+
import { MarkdownText } from '../components/MarkdownText'
|
|
34
35
|
import { ScreenshotGallery } from '../components/ScreenshotGallery'
|
|
35
36
|
import { useUpdateApp } from '../../hooks/useUpdateApp'
|
|
36
37
|
import { useAppCatalogContext } from '../../context/AppCatalogContext'
|
|
@@ -54,6 +55,8 @@ export interface AppCatalogGridProps {
|
|
|
54
55
|
totalAppsCount?: number
|
|
55
56
|
/** Callback to clear all filters and search */
|
|
56
57
|
onClearFilters?: () => void
|
|
58
|
+
/** Called when the user closes the detail panel (Esc or X) — e.g. to navigate back to the catalog URL */
|
|
59
|
+
onClosePanel?: () => void
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
function getIconUrl(iconName: string): string {
|
|
@@ -395,10 +398,12 @@ function AppDetails({
|
|
|
395
398
|
placeholder="Description"
|
|
396
399
|
className="min-h-[4rem] resize-y text-sm text-muted-foreground"
|
|
397
400
|
/>
|
|
401
|
+
) : app.description ? (
|
|
402
|
+
<MarkdownText className="prose prose-sm max-w-none text-sm text-muted-foreground [&_p]:m-0">
|
|
403
|
+
{app.description}
|
|
404
|
+
</MarkdownText>
|
|
398
405
|
) : (
|
|
399
|
-
<p className="text-sm text-muted-foreground">
|
|
400
|
-
{app.description || '—'}
|
|
401
|
-
</p>
|
|
406
|
+
<p className="text-sm text-muted-foreground">—</p>
|
|
402
407
|
)}
|
|
403
408
|
</div>
|
|
404
409
|
|
|
@@ -694,6 +699,7 @@ export function AppCatalogGrid({
|
|
|
694
699
|
searchQuery,
|
|
695
700
|
totalAppsCount,
|
|
696
701
|
onClearFilters,
|
|
702
|
+
onClosePanel,
|
|
697
703
|
}: AppCatalogGridProps) {
|
|
698
704
|
const selectedApp = selectedAppSlug
|
|
699
705
|
? apps.find((a) => a.slug === selectedAppSlug)
|
|
@@ -859,6 +865,7 @@ export function AppCatalogGrid({
|
|
|
859
865
|
|
|
860
866
|
const handleClosePanel = () => {
|
|
861
867
|
setHasUserClosed(true)
|
|
868
|
+
onClosePanel?.()
|
|
862
869
|
}
|
|
863
870
|
|
|
864
871
|
return (
|