Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/app/components/render-pages-doc.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RenderBlocks } from '@codeware/shared/ui/payload-components';
import type { NavigationDoc } from '@codeware/shared/util/payload-api';
import type { NavigationDoc } from '@codeware/shared/util/payload-types';

/**
* Render a pages collection document.
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/components/render-posts-doc.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RichText } from '@codeware/shared/ui/payload-components';
import type { NavigationDoc } from '@codeware/shared/util/payload-api';
import type { NavigationDoc } from '@codeware/shared/util/payload-types';

/**
* Render a posts collection document.
Expand Down
12 changes: 1 addition & 11 deletions apps/web/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ import {
getSiteSettings
} from '@codeware/shared/util/payload-api';
import type { SiteSetting } from '@codeware/shared/util/payload-types';
import type {
LinksFunction,
LoaderFunctionArgs,
MetaFunction
} from '@remix-run/node';
import type { LinksFunction, LoaderFunctionArgs } from '@remix-run/node';
import {
Link,
Links,
Expand All @@ -40,12 +36,6 @@ import { ClientHintCheck, getHints } from './utils/client-hints';
import { getPayloadRequestOptions } from './utils/get-payload-request-options';
import { type Theme, getTheme } from './utils/theme.server';

export const meta: MetaFunction<typeof loader> = ({ data }) => [
{
title: data?.siteSettings?.general?.appName ?? ''
}
];

export const links: LinksFunction = () => [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{
Expand Down
20 changes: 11 additions & 9 deletions apps/web/app/routes/($collection).$slug.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { findNavigationDoc } from '@codeware/shared/util/payload-api';
import { resolveMeta } from '@codeware/shared/util/payload-utils';
import type { LoaderFunctionArgs, MetaFunction } from '@remix-run/node';
import { json, useLoaderData, useRouteError } from '@remix-run/react';

import { Container } from '../components/container';
import { ErrorContainer } from '../components/error-container';
import { RenderPagesDoc } from '../components/render-pages-doc';
import { RenderPostsDoc } from '../components/render-posts-doc';
import { defaultAppName } from '../utils/default-app-name';
import { getPayloadRequestOptions } from '../utils/get-payload-request-options';
import { getSiteSettingsFromRoot } from '../utils/get-site-settings-from-root';

type LoaderError = {
message: string;
status: number;
};

// TODO: How to use it properly?
export const meta: MetaFunction<typeof loader> = ({ data }) => {
if (data?.doc.collection === 'pages') {
return [{ title: data.doc.name }];
}
if (data?.doc.collection === 'posts') {
return [{ title: data.doc.title }];
}
return [{ title: 'Page Not Found' }];
export const meta: MetaFunction<typeof loader> = ({ data, matches }) => {
// Get site settings from root loader data
const siteSettings = getSiteSettingsFromRoot(matches);
const appName = siteSettings?.general?.appName ?? defaultAppName;

const meta = resolveMeta(data?.doc);

return [{ title: `${appName} - ${meta?.title ?? 'Page'}` }];
};

/**
Expand Down
15 changes: 11 additions & 4 deletions apps/web/app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { RenderBlocks } from '@codeware/shared/ui/payload-components';
import { resolveMeta } from '@codeware/shared/util/payload-utils';
import { type MetaFunction, useRouteError } from '@remix-run/react';

import { Container } from '../components/container';
import { ErrorContainer } from '../components/error-container';
import { defaultAppName } from '../utils/default-app-name';
import { getSiteSettingsFromRoot } from '../utils/get-site-settings-from-root';
import { useSiteSettings } from '../utils/use-site-settings';

type LoaderError = {
message: string;
status: number;
};

// TODO: How to use it properly?
export const meta: MetaFunction = () => {
const { landingPage } = useSiteSettings();
return [{ title: landingPage?.name }];
export const meta: MetaFunction = ({ matches }) => {
// Get site settings from root loader data
const siteSettings = getSiteSettingsFromRoot(matches);
const appName = siteSettings?.general?.appName ?? defaultAppName;

const meta = resolveMeta(siteSettings);

return [{ title: `${appName} - ${meta?.title ?? 'Home'}` }];
};

export default function Index() {
Expand Down
4 changes: 4 additions & 0 deletions apps/web/app/utils/default-app-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* The default app name when the site settings are not found.
*/
export const defaultAppName = 'App' as const;
18 changes: 18 additions & 0 deletions apps/web/app/utils/get-site-settings-from-root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { SiteSetting } from '@codeware/shared/util/payload-types';
import type { MetaFunction } from '@remix-run/react';

/**
* Server side utility to get the site settings from the root loader data.
*
* @param matches - The `matches` object from the root loader.
* @returns The site settings or `null` if not found.
*/
export const getSiteSettingsFromRoot = (
matches: Parameters<MetaFunction>[0]['matches']
) => {
const rootData = matches.find((match) => match.id === 'root')?.data as
| Record<'siteSettings', SiteSetting>
| undefined;

return rootData?.siteSettings ?? null;
};
1 change: 0 additions & 1 deletion libs/shared/util/payload-api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export { apiKeyPrefix, authorizationHeader } from './lib/utils/definitions';
export type {
MethodOptions,
NavigationDoc,
NavigationItem,
RequestBaseOptions,
RequestMethod
Expand Down
19 changes: 18 additions & 1 deletion libs/shared/util/payload-api/src/lib/find-navigation-doc.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type {
NavigationDoc,
NavigationReferenceCollection,
Page,
Post
} from '@codeware/shared/util/payload-types';

import { invokeRequest } from './utils/invoke-request';
import type { NavigationDoc, RequestBaseOptions } from './utils/types';
import type { RequestBaseOptions } from './utils/types';

/**
* Find a navigation document by the URL collection and slug parameters.
Expand Down Expand Up @@ -45,6 +46,14 @@ export const findNavigationDoc = async (
collection: lookupCollection,
header: page.header,
layout: page.layout,
meta: {
description: page.meta?.description ?? undefined,
image:
(typeof page.meta?.image === 'object'
? page.meta?.image
: undefined) ?? undefined,
title: page.meta?.title ?? undefined
},
name: page.name
};
}
Expand All @@ -56,6 +65,14 @@ export const findNavigationDoc = async (
content: post.content,
heroImage:
typeof post.heroImage === 'object' ? post.heroImage : undefined,
meta: {
description: post.meta?.description ?? undefined,
image:
(typeof post.meta?.image === 'object'
? post.meta?.image
: undefined) ?? undefined,
title: post.meta?.title ?? undefined
},
title: post.title
};
}
Expand Down
21 changes: 1 addition & 20 deletions libs/shared/util/payload-api/src/lib/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import type {
Media,
NavigationReferenceCollection,
Page,
Post
} from '@codeware/shared/util/payload-types';
import type { NavigationReferenceCollection } from '@codeware/shared/util/payload-types';

/**
* Available request options depending on the request method.
Expand Down Expand Up @@ -36,20 +31,6 @@ export type MethodOptions<T extends RequestMethod> = T extends 'GET'
body: Record<string, unknown>;
};

/**
* Document details for a navigation item.
*/
export type NavigationDoc =
// limit what can be exposed client side
| ({
collection: 'pages';
} & Pick<Page, 'header' | 'layout' | 'name'>)
| ({
collection: 'posts';
} & Pick<Post, 'content' | 'title'> & {
heroImage?: Media | null | undefined;
});

/**
* Navigation tree item.
*/
Expand Down
34 changes: 34 additions & 0 deletions libs/shared/util/payload-types/src/lib/custom-types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { StripTypes } from '@codeware/shared/util/typesafe';
import type { ClientUser, User } from 'payload';

import type {
Expand All @@ -6,7 +7,10 @@ import type {
ContentBlock,
Form,
FormSubmission,
Media,
Navigation,
Page,
Post,
Tenant,
TenantsArrayField
} from './payload-types';
Expand Down Expand Up @@ -69,6 +73,36 @@ export type FormSubmissionData = NonNullable<
NonNullable<FormSubmission['submissionData']>
>;

type PageMetaDefined = NonNullable<Page['meta']>;
type PostMetaDefined = NonNullable<Post['meta']>;

/** Page meta type */
export type PageMeta = {
[K in keyof PageMetaDefined]: StripTypes<PageMetaDefined[K], number | null>;
};

/** Post meta type */
export type PostMeta = {
[K in keyof PostMetaDefined]: StripTypes<PostMetaDefined[K], number | null>;
};

/**
* Document details for a navigation item.
*/
export type NavigationDoc =
// limit what can be exposed client side
| ({
collection: 'pages';
} & Pick<Page, 'header' | 'layout' | 'name'> & {
meta: PageMeta;
})
| ({
collection: 'posts';
} & Pick<Post, 'content' | 'title'> & {
heroImage?: Media | null | undefined;
meta: PostMeta;
});

/** Navigation reference collection */
export type NavigationReferenceCollection = NonNullable<
NonNullable<Navigation['items']>[number]
Expand Down
3 changes: 3 additions & 0 deletions libs/shared/util/payload-utils/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import baseConfig from '../../../../eslint.config.mjs';

export default [...baseConfig];
8 changes: 8 additions & 0 deletions libs/shared/util/payload-utils/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "shared-util-payload-utils",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/shared/util/payload-utils/src",
"projectType": "library",
"tags": ["scope:shared", "type:util", "domain:payload"],
"targets": {}
}
1 change: 1 addition & 0 deletions libs/shared/util/payload-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { resolveMeta } from './lib/resolve-meta';
47 changes: 47 additions & 0 deletions libs/shared/util/payload-utils/src/lib/resolve-meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type {
NavigationDoc,
PageMeta,
PostMeta,
SiteSetting
} from '@codeware/shared/util/payload-types';

/**
* Resolve the meta for a navigation document or the landing page from site settings.
*
* @param data - The navigation document or site settings to resolve the meta for.
* @returns Page or post meta or `null` if the meta data is not found.
*/
export const resolveMeta = (
data: NavigationDoc | SiteSetting | null | undefined
): PageMeta | PostMeta | null => {
if (!data) {
return null;
}

// Resolve site settings landing page meta
if ('general' in data) {
const { landingPage } = data.general;
if (typeof landingPage === 'object') {
const { description, image, title } = landingPage.meta ?? {};
return {
description: description ?? undefined,
image: (typeof image === 'object' ? image : undefined) ?? undefined,
title: title ?? undefined
};
}
}

// Resolve collection page or post meta
if ('collection' in data) {
const { collection } = data;
switch (collection) {
case 'pages':
case 'posts':
return data.meta;
default:
return null;
}
}

return null;
};
22 changes: 22 additions & 0 deletions libs/shared/util/payload-utils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": true,
"importHelpers": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
17 changes: 17 additions & 0 deletions libs/shared/util/payload-utils/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.spec.ts",
"src/**/*.spec.tsx"
]
}
22 changes: 22 additions & 0 deletions libs/shared/util/payload-utils/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
]
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.spec.ts",
"src/**/*.spec.tsx",
"src/**/*.d.ts"
]
}
Loading