From f4ca6bd73300a2064ab95962c8a3e986d88edeb3 Mon Sep 17 00:00:00 2001
From: Mobeen Abdullah
+ No plugins match “{query.trim()}”. +
+ )} ++ Plugins published by Nextly. Adding one is a dependency and a line in + your Nextly config, so open a plugin to get both. +
++ {plugin.author} +
++ {presentation.description} +
+ + {label && ( + + {label} + + )} + + ); +} diff --git a/packages/admin/src/pages/dashboard/plugins/index.tsx b/packages/admin/src/pages/dashboard/plugins/index.tsx index 95d4b9d55d..6523d462ac 100644 --- a/packages/admin/src/pages/dashboard/plugins/index.tsx +++ b/packages/admin/src/pages/dashboard/plugins/index.tsx @@ -10,6 +10,7 @@ * @module pages/dashboard/plugins */ +import { Button } from "@nextlyhq/ui"; import type React from "react"; import { Suspense } from "react"; @@ -17,6 +18,7 @@ import { PageContainer } from "@admin/components/layout/page-container"; import { Breadcrumbs } from "@admin/components/shared"; import { PageErrorFallback } from "@admin/components/shared/error-fallbacks"; import { QueryErrorBoundary } from "@admin/components/shared/query-error-boundary"; +import { Link } from "@admin/components/ui/link"; import { ROUTES } from "@admin/constants/routes"; import PluginsTable from "./components/PluginsTable"; @@ -52,6 +54,14 @@ const PluginsOverviewPage: React.FC = () => { whether it is enabled. + + {/* In the header rather than only in the empty state: a project that + already has plugins is the one most likely to want another, and an + action that appears only while the list is empty is unreachable + exactly then. */} + {/* Plugins table */} diff --git a/packages/admin/src/pages/registry.ts b/packages/admin/src/pages/registry.ts index 7bc48ffb8e..0af3262e92 100644 --- a/packages/admin/src/pages/registry.ts +++ b/packages/admin/src/pages/registry.ts @@ -20,6 +20,7 @@ import DashboardPage from "./dashboard/index"; import MediaLibraryPage from "./dashboard/media/index"; import PluginDetailPage from "./dashboard/plugins/[slug]"; import PluginSettingsPage from "./dashboard/plugins/[slug]/settings"; +import PluginBrowsePage from "./dashboard/plugins/browse"; import PluginsOverviewPage from "./dashboard/plugins/index"; import CollectionsLandingRedirect from "./dashboard/redirects/CollectionsLandingRedirect"; import FieldGroupsLandingRedirect from "./dashboard/redirects/FieldGroupsLandingRedirect"; @@ -372,6 +373,17 @@ export const routeConfig: Record- No plugins match “{query.trim()}”. + {normalized + ? `No plugins match “${query.trim()}”.` + : "No plugins to show yet."}
)} From 6a6887ce4b01771857ff47d4bc4bb102ccb80a7e Mon Sep 17 00:00:00 2001 From: Mobeen Abdullah- No installed plugin matches this address. It may have been removed - from your Nextly config. -
-+ No installed plugin matches this address, and it is not in the plugin + directory either. It may have been removed from your Nextly config. +
++ {label} +
+
+ {value}
+
+
+ + {plugin.author} + {label ? ` · ${label}` : ""} +
++ {plugin.description} +
+ ++ Two steps: install the package, then register it in your Nextly + config. Nextly picks it up on the next start. +
++ What this plugin adds — its collections, permissions and API routes — is + only known once it is installed and Nextly has loaded it. +
+ + {plugin.links && ( +
- Two steps: install the package, then register it in your Nextly
- config. Nextly picks it up on the next start.
+ Install the package, then make two edits in{" "}
+ nextly.config.ts: import the
+ plugin at the top of the file, and add it to the{" "}
+ plugins array. Nextly picks it up
+ on the next start.
+ Could not copy to the clipboard. Select the line above and copy it + manually. +
+ )} ); } diff --git a/packages/admin/src/pages/dashboard/plugins/components/PluginPageLoading.tsx b/packages/admin/src/pages/dashboard/plugins/components/PluginPageLoading.tsx new file mode 100644 index 0000000000..c4ff443b8e --- /dev/null +++ b/packages/admin/src/pages/dashboard/plugins/components/PluginPageLoading.tsx @@ -0,0 +1,32 @@ +"use client"; + +import type React from "react"; + +import { Loader2 } from "@admin/components/icons"; + +/** + * What a plugin page shows while the data it suspends on is in flight. + * + * A `Suspense` fallback is not optional for these pages. `QueryErrorBoundary` + * handles errors and nothing else, and the only boundary above it is + * `RootLayout`'s `fallback={null}` — which exists to hide a lazy chunk + * swapping in, not to stand in for a page. Without a local boundary a + * suspending page is a blank screen for as long as the request takes. + * + * @module pages/dashboard/plugins/components/PluginPageLoading + */ +export function PluginPageLoading({ + label = "Loading…", +}: { + label?: string; +}): React.ReactElement { + return ( +{label} + {file && ( + + {file} + + )}
@@ -89,6 +104,15 @@ function CopyLine({ value, label }: { value: string; label: string }) {
manually.
)}
+ {outcome === "copied" && (
+ // Success is announced too, and only to assistive technology. The
+ // button's `aria-label` overrides its descendants, so swapping the
+ // glyph changes nothing a screen reader can perceive — sighted users
+ // already have the tick, and this is the equivalent for everyone else.
+
+ {label} copied to the clipboard.
+
+ )}
+ This plugin also ships admin UI, which is registered by importing + it in your admin route page. Skip it and the plugin still loads — + its editors just fall back to plain inputs. +
+- This plugin also ships admin UI, which is registered by importing - it in your admin route page. Skip it and the plugin still loads — - its editors just fall back to plain inputs. + {(adminImport || adminStyles) && ( +
+ This plugin also ships admin UI, registered by importing it in + your admin route page. Skip these and the plugin still loads — its + editors just fall back to plain inputs + {adminStyles ? ", and render unstyled" : ""}.
-