Rename vendors to third parties
Renames the user-facing 'vendor' concept to 'third party' across the entire codebase. The shared common_third_parties reference table is unchanged. Migration. Renames the vendor_category enum, the vendors and vendor_<entity> tables (contacts, services, compliance_reports, business_associate_agreements, data_privacy_agreements, risk_assessments) and their vendor_id columns, the asset_vendors / data_vendors / processing_activity_vendors junction tables, generated_documents.vendors_document_id, the webhook_event_type 'vendor:<verb>' values, and the snapshots_type 'VENDORS' value. Backend. Renames coredata models and SQL queries, probo services, GraphQL / MCP API surface, console / trust / webhook resolvers and types, the CLI (prb vendor* -> prb third-party*; pkg/cmd/vendormgmt -> pkg/cmd/thirdpartymgmt), the document generator, vetting agent prompts, and the common-third-parties-import command. Frontend, packages, n8n, e2e. Renames apps/console pages, components, hooks, routes, dialogs, and tabs; the shared @probo/vendors package (now @probo/third-parties); the @probo/ui Vendors atoms (now ThirdParties, VendorLogo -> ThirdPartyLogo); the n8n community node actions/vendor folder (now actions/thirdParty); and the e2e Go test suite (console and MCP). Filesystem and URL paths use kebab-case (third-parties), GraphQL fields and TypeScript identifiers use camelCase (thirdParty / thirdParties), Go types use PascalCase (ThirdParty), and human-facing text uses 'third party' with a space. Co-authored-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -22,12 +22,12 @@ The `pages/` folder **is** the route tree. Every route segment maps to a folder
|
||||
// Bad — separate routes/ folder duplicates pages/ structure
|
||||
src/
|
||||
routes/
|
||||
vendorRoutes.ts # route definitions for vendors
|
||||
thirdPartyRoutes.ts # route definitions for third parties
|
||||
assetRoutes.ts # route definitions for assets
|
||||
pages/
|
||||
organizations/
|
||||
vendors/
|
||||
VendorsPage.tsx
|
||||
third-parties/
|
||||
ThirdPartiesPage.tsx
|
||||
assets/
|
||||
AssetsPage.tsx
|
||||
```
|
||||
@@ -37,9 +37,9 @@ src/
|
||||
src/
|
||||
pages/
|
||||
organizations/
|
||||
vendors/
|
||||
routes.ts # route definitions for vendors
|
||||
VendorsPage.tsx
|
||||
third-parties/
|
||||
routes.ts # route definitions for third parties
|
||||
ThirdPartiesPage.tsx
|
||||
assets/
|
||||
routes.ts # route definitions for assets
|
||||
AssetsPage.tsx
|
||||
@@ -77,11 +77,11 @@ Use the correct suffix so the role is clear from the file name alone:
|
||||
|
||||
```text
|
||||
// Bad — a layout route named as a "Page"
|
||||
VendorDetailPage.tsx # renders <Outlet />, wraps child routes
|
||||
ThirdPartyDetailPage.tsx # renders <Outlet />, wraps child routes
|
||||
CookieBannerConfigPage.tsx # renders tabs + <Outlet />
|
||||
|
||||
// Good — layout routes use the "Layout" suffix
|
||||
VendorDetailLayout.tsx
|
||||
ThirdPartyDetailLayout.tsx
|
||||
CookieBannerConfigLayout.tsx
|
||||
```
|
||||
|
||||
@@ -142,26 +142,26 @@ export default function CookieBannerLayout() {
|
||||
Contains route objects for the current folder's feature, exported as a named array and spread into the parent. Keep imports minimal — only `lazy`, skeleton components, and typing.
|
||||
|
||||
```ts
|
||||
// pages/organizations/vendors/routes.ts
|
||||
// pages/organizations/third-parties/routes.ts
|
||||
import { lazy } from "@probo/react-lazy";
|
||||
import type { AppRoute } from "@probo/routes";
|
||||
|
||||
import { VendorsPageSkeleton } from "./VendorsPageSkeleton";
|
||||
import { ThirdPartiesPageSkeleton } from "./ThirdPartiesPageSkeleton";
|
||||
|
||||
export const vendorRoutes = [
|
||||
export const thirdPartyRoutes = [
|
||||
{
|
||||
path: "vendors",
|
||||
Fallback: VendorsPageSkeleton,
|
||||
Component: lazy(() => import("./VendorsPageLoader")),
|
||||
path: "third-parties",
|
||||
Fallback: ThirdPartiesPageSkeleton,
|
||||
Component: lazy(() => import("./ThirdPartiesPageLoader")),
|
||||
},
|
||||
{
|
||||
path: "vendors/:vendorId",
|
||||
Fallback: VendorsPageSkeleton,
|
||||
Component: lazy(() => import("./VendorDetailLayoutLoader")),
|
||||
path: "third-parties/:thirdPartyId",
|
||||
Fallback: ThirdPartiesPageSkeleton,
|
||||
Component: lazy(() => import("./ThirdPartyDetailLayoutLoader")),
|
||||
children: [
|
||||
{
|
||||
path: "overview",
|
||||
Component: lazy(() => import("./overview/VendorOverviewPage")),
|
||||
Component: lazy(() => import("./overview/ThirdPartyOverviewPage")),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -173,35 +173,35 @@ export const vendorRoutes = [
|
||||
The loader is the **lazy bundle entry point**. It sets up providers, triggers the Relay query, shows a skeleton until the query resolves, then renders the page.
|
||||
|
||||
```tsx
|
||||
// pages/organizations/vendors/VendorsPageLoader.tsx
|
||||
// pages/organizations/third-parties/ThirdPartiesPageLoader.tsx
|
||||
import { Suspense, useEffect } from "react";
|
||||
import { useQueryLoader } from "react-relay";
|
||||
|
||||
import type { VendorsPageQuery } from "#/__generated__/core/VendorsPageQuery.graphql";
|
||||
import type { ThirdPartiesPageQuery } from "#/__generated__/core/ThirdPartiesPageQuery.graphql";
|
||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||
|
||||
import VendorsPage, { vendorsPageQuery } from "./VendorsPage";
|
||||
import { VendorsPageSkeleton } from "./VendorsPageSkeleton";
|
||||
import ThirdPartiesPage, { thirdPartiesPageQuery } from "./ThirdPartiesPage";
|
||||
import { ThirdPartiesPageSkeleton } from "./ThirdPartiesPageSkeleton";
|
||||
|
||||
function VendorsPageQueryLoader() {
|
||||
function ThirdPartiesPageQueryLoader() {
|
||||
const organizationId = useOrganizationId();
|
||||
const [queryRef, loadQuery] = useQueryLoader<VendorsPageQuery>(vendorsPageQuery);
|
||||
const [queryRef, loadQuery] = useQueryLoader<ThirdPartiesPageQuery>(thirdPartiesPageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({ organizationId });
|
||||
}, [loadQuery, organizationId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <VendorsPageSkeleton />;
|
||||
return <ThirdPartiesPageSkeleton />;
|
||||
}
|
||||
|
||||
return <VendorsPage queryRef={queryRef} />
|
||||
return <ThirdPartiesPage queryRef={queryRef} />
|
||||
}
|
||||
|
||||
export default function VendorsPageLoader() {
|
||||
export default function ThirdPartiesPageLoader() {
|
||||
return (
|
||||
<CoreRelayProvider>
|
||||
<VendorsPageQueryLoader />
|
||||
<ThirdPartiesPageQueryLoader />
|
||||
</CoreRelayProvider>
|
||||
);
|
||||
}
|
||||
@@ -212,9 +212,9 @@ export default function VendorsPageLoader() {
|
||||
Receives the `queryRef` from the loader and renders the UI. Default export so `lazy()` can import it.
|
||||
|
||||
```tsx
|
||||
// pages/organizations/vendors/VendorsPage.tsx
|
||||
export default function VendorsPage({ queryRef }: VendorsPageProps) {
|
||||
const data = usePreloadedQuery(vendorsPageQuery, queryRef);
|
||||
// pages/organizations/third-parties/ThirdPartiesPage.tsx
|
||||
export default function ThirdPartiesPage({ queryRef }: ThirdPartiesPageProps) {
|
||||
const data = usePreloadedQuery(thirdPartiesPageQuery, queryRef);
|
||||
return (/* … */);
|
||||
}
|
||||
```
|
||||
@@ -224,8 +224,8 @@ export default function VendorsPage({ queryRef }: VendorsPageProps) {
|
||||
A lightweight loading placeholder. Keep it free of data-fetching logic so it loads instantly.
|
||||
|
||||
```tsx
|
||||
// pages/organizations/vendors/VendorsPageSkeleton.tsx
|
||||
export function VendorsPageSkeleton() {
|
||||
// pages/organizations/third-parties/ThirdPartiesPageSkeleton.tsx
|
||||
export function ThirdPartiesPageSkeleton() {
|
||||
return (/* pulse / skeleton UI */);
|
||||
}
|
||||
```
|
||||
@@ -235,8 +235,8 @@ export function VendorsPageSkeleton() {
|
||||
Rendered by the route error boundary when the page throws.
|
||||
|
||||
```tsx
|
||||
// pages/organizations/vendors/VendorsPageError.tsx
|
||||
export function VendorsPageError() {
|
||||
// pages/organizations/third-parties/ThirdPartiesPageError.tsx
|
||||
export function ThirdPartiesPageError() {
|
||||
const error = useRouteError();
|
||||
return (/* error UI */);
|
||||
}
|
||||
@@ -244,24 +244,24 @@ export function VendorsPageError() {
|
||||
|
||||
## File naming
|
||||
|
||||
Component files (`.tsx` that export a React component) use **PascalCase**: `VendorsPage.tsx`, `VendorContactRow.tsx`, `VendorsPageSkeleton.tsx`.
|
||||
Component files (`.tsx` that export a React component) use **PascalCase**: `ThirdPartiesPage.tsx`, `ThirdPartyContactRow.tsx`, `ThirdPartiesPageSkeleton.tsx`.
|
||||
|
||||
All other helper files (utilities, hooks, constants, configuration) use **camelCase**: `routes.ts`, `useVendorFilters.ts`, `formatCurrency.ts`, `constants.ts`.
|
||||
All other helper files (utilities, hooks, constants, configuration) use **camelCase**: `routes.ts`, `useThirdPartyFilters.ts`, `formatCurrency.ts`, `constants.ts`.
|
||||
|
||||
### Do / don't: file naming
|
||||
|
||||
```text
|
||||
// Bad — helper file in PascalCase
|
||||
pages/organizations/vendors/FormatVendorStatus.ts
|
||||
pages/organizations/vendors/UseVendorFilters.ts
|
||||
pages/organizations/vendors/Routes.ts
|
||||
pages/organizations/third-parties/FormatThirdPartyStatus.ts
|
||||
pages/organizations/third-parties/UseThirdPartyFilters.ts
|
||||
pages/organizations/third-parties/Routes.ts
|
||||
|
||||
// Good — helpers are camelCase, components are PascalCase
|
||||
pages/organizations/vendors/formatVendorStatus.ts
|
||||
pages/organizations/vendors/useVendorFilters.ts
|
||||
pages/organizations/vendors/routes.ts
|
||||
pages/organizations/vendors/VendorsPage.tsx
|
||||
pages/organizations/vendors/VendorsPageSkeleton.tsx
|
||||
pages/organizations/third-parties/formatThirdPartyStatus.ts
|
||||
pages/organizations/third-parties/useThirdPartyFilters.ts
|
||||
pages/organizations/third-parties/routes.ts
|
||||
pages/organizations/third-parties/ThirdPartiesPage.tsx
|
||||
pages/organizations/third-parties/ThirdPartiesPageSkeleton.tsx
|
||||
```
|
||||
|
||||
## `_components` folder
|
||||
@@ -270,7 +270,7 @@ Sub-components that are used **only** by a single page live in a `_components/`
|
||||
|
||||
| Situation | Where the component lives |
|
||||
| ------------------------------------------ | ---------------------------------------------------------------------------------- |
|
||||
| Used by one page only | `pages/organizations/vendors/_components/` |
|
||||
| Used by one page only | `pages/organizations/third-parties/_components/` |
|
||||
| Used by multiple pages in the same feature | Nearest common ancestor's `_components/` (e.g. `pages/organizations/_components/`) |
|
||||
| Reusable UI primitive | `@probo/ui` package |
|
||||
|
||||
@@ -278,8 +278,8 @@ Sub-components that are used **only** by a single page live in a `_components/`
|
||||
|
||||
```text
|
||||
// Bad — shared component buried in a single page's _components
|
||||
pages/organizations/vendors/_components/StatusBadge.tsx # also used by risks page
|
||||
pages/organizations/risks/SomeRiskPage.tsx # imports ../../vendors/_components/StatusBadge
|
||||
pages/organizations/third-parties/_components/StatusBadge.tsx # also used by risks page
|
||||
pages/organizations/risks/SomeRiskPage.tsx # imports ../../third-parties/_components/StatusBadge
|
||||
|
||||
// Good — shared component hoisted to common ancestor
|
||||
pages/organizations/_components/StatusBadge.tsx
|
||||
@@ -287,10 +287,10 @@ pages/organizations/_components/StatusBadge.tsx
|
||||
|
||||
```text
|
||||
// Bad — page-specific helper placed in a global folder
|
||||
src/components/VendorContactRow.tsx # only used by VendorContactsTab
|
||||
src/components/ThirdPartyContactRow.tsx # only used by ThirdPartyContactsTab
|
||||
|
||||
// Good — scoped to the page that uses it
|
||||
pages/organizations/vendors/_components/VendorContactRow.tsx
|
||||
pages/organizations/third-parties/_components/ThirdPartyContactRow.tsx
|
||||
```
|
||||
|
||||
## Child-route folder naming
|
||||
@@ -303,40 +303,40 @@ Folders that contain child-route pages are named after the **resource or concept
|
||||
// Bad — folder named after a UI element
|
||||
configuration/
|
||||
tabs/ # "tabs" is a UI component, not a resource
|
||||
VendorOverviewTab.tsx
|
||||
VendorComplianceTab.tsx
|
||||
ThirdPartyOverviewTab.tsx
|
||||
ThirdPartyComplianceTab.tsx
|
||||
|
||||
// Good — folders named after the resource each child route represents
|
||||
configuration/
|
||||
overview/
|
||||
VendorOverviewPage.tsx
|
||||
ThirdPartyOverviewPage.tsx
|
||||
compliance/
|
||||
VendorCompliancePage.tsx
|
||||
ThirdPartyCompliancePage.tsx
|
||||
```
|
||||
|
||||
This also means child-route components use the `*Page` suffix (not `*Tab`), because they are pages in their own right — the fact that a tab bar navigates between them is an implementation detail of the parent layout.
|
||||
|
||||
## Full example tree
|
||||
|
||||
Target layout for a `vendors` feature under `pages/organizations/`:
|
||||
Target layout for a `third-parties` feature under `pages/organizations/`:
|
||||
|
||||
```text
|
||||
pages/organizations/vendors/
|
||||
routes.ts # route definitions for vendors
|
||||
VendorsPageLoader.tsx # lazy entry — providers + Suspense + query loader
|
||||
VendorsPage.tsx # page component (usePreloadedQuery)
|
||||
VendorsPageSkeleton.tsx # loading fallback
|
||||
VendorDetailLayoutLoader.tsx # lazy entry for detail layout
|
||||
VendorDetailLayout.tsx # layout — breadcrumbs, tabs, <Outlet />
|
||||
VendorDetailLayoutSkeleton.tsx # detail loading fallback
|
||||
NewVendorPage.tsx # mutation-only page — default export, wraps itself in the Relay provider
|
||||
_components/ # sub-components used only by vendor pages
|
||||
VendorContactRow.tsx
|
||||
VendorRiskSummary.tsx
|
||||
overview/ # child route: /vendors/:vendorId/overview
|
||||
VendorOverviewPage.tsx
|
||||
compliance/ # child route: /vendors/:vendorId/compliance
|
||||
VendorCompliancePage.tsx
|
||||
contacts/ # child route: /vendors/:vendorId/contacts
|
||||
VendorContactsPage.tsx
|
||||
pages/organizations/third-parties/
|
||||
routes.ts # route definitions for third parties
|
||||
ThirdPartiesPageLoader.tsx # lazy entry — providers + Suspense + query loader
|
||||
ThirdPartiesPage.tsx # page component (usePreloadedQuery)
|
||||
ThirdPartiesPageSkeleton.tsx # loading fallback
|
||||
ThirdPartyDetailLayoutLoader.tsx # lazy entry for detail layout
|
||||
ThirdPartyDetailLayout.tsx # layout — breadcrumbs, tabs, <Outlet />
|
||||
ThirdPartyDetailLayoutSkeleton.tsx # detail loading fallback
|
||||
NewThirdPartyPage.tsx # mutation-only page — default export, wraps itself in the Relay provider
|
||||
_components/ # sub-components used only by third party pages
|
||||
ThirdPartyContactRow.tsx
|
||||
ThirdPartyRiskSummary.tsx
|
||||
overview/ # child route: /third-parties/:thirdPartyId/overview
|
||||
ThirdPartyOverviewPage.tsx
|
||||
compliance/ # child route: /third-parties/:thirdPartyId/compliance
|
||||
ThirdPartyCompliancePage.tsx
|
||||
contacts/ # child route: /third-parties/:thirdPartyId/contacts
|
||||
ThirdPartyContactsPage.tsx
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user