From 45017ae38f0946da6dcd82a0d2b6d9180bf9f69e Mon Sep 17 00:00:00 2001 From: alasdiel Date: Tue, 21 Jul 2026 19:35:42 +0800 Subject: [PATCH] feat(globals): add OrganizationStatus global with access control, drafts, and PSF partner component --- src/access/checkCollectionAccess.ts | 2 +- src/app/(payload)/admin/importMap.js | 3 - src/components/OrganizationStatus/index.tsx | 33 +++++++++ src/constants/collections.ts | 4 +- .../durianpy-website/OrganizationStatus.ts | 68 +++++++++++++++++++ src/payload-types.ts | 40 +++++++++-- src/payload.config.ts | 2 + src/utilities/getGlobals.ts | 25 +++++++ 8 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 src/components/OrganizationStatus/index.tsx create mode 100644 src/globals/durianpy-website/OrganizationStatus.ts create mode 100644 src/utilities/getGlobals.ts diff --git a/src/access/checkCollectionAccess.ts b/src/access/checkCollectionAccess.ts index bc86c97..f4406f2 100644 --- a/src/access/checkCollectionAccess.ts +++ b/src/access/checkCollectionAccess.ts @@ -13,7 +13,7 @@ import { COLLECTION_PERMISSION_TO_ACCESS_TYPES } from '@/constants/collectionPer export function checkCollectionAccess( { req }: AccessArgs, - collectionSlug: CollectionSlug, + collectionSlug: CollectionSlug | string, accessType?: AccessType, ) { const user = req.user diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js index 2ed42a6..34dee8c 100644 --- a/src/app/(payload)/admin/importMap.js +++ b/src/app/(payload)/admin/importMap.js @@ -15,7 +15,6 @@ import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1e import { FolderTypeField as FolderTypeField_2b8867833a34864a02ddf429b0728a40 } from '@payloadcms/next/client' import { default as default_1a7510af427896d367a49dbf838d2de6 } from '@/components/BeforeDashboard' import { default as default_8a7ab0eb7ab5c511aba12e68480bfe5e } from '@/components/BeforeLogin' -import { S3ClientUploadHandler as S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24 } from '@payloadcms/storage-s3/client' import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc' export const importMap = { @@ -47,7 +46,5 @@ export const importMap = { '@payloadcms/next/client#FolderTypeField': FolderTypeField_2b8867833a34864a02ddf429b0728a40, '@/components/BeforeDashboard#default': default_1a7510af427896d367a49dbf838d2de6, '@/components/BeforeLogin#default': default_8a7ab0eb7ab5c511aba12e68480bfe5e, - '@payloadcms/storage-s3/client#S3ClientUploadHandler': - S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24, '@payloadcms/next/rsc#CollectionCards': CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1, } diff --git a/src/components/OrganizationStatus/index.tsx b/src/components/OrganizationStatus/index.tsx new file mode 100644 index 0000000..4a5f84f --- /dev/null +++ b/src/components/OrganizationStatus/index.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import Image from 'next/image' +import { getGlobal } from '@/utilities/getGlobals' +import { getMediaUrl } from '@/utilities/getMediaUrl' +import type { Media } from '@/payload-types' + +export async function OrganizationStatusComponent() { + const data = await getGlobal('organization-status', 1) + + if (!data?.isPSFPartner) { + return null + } + + const logoObj = typeof data.psfPartnerLogo === 'object' ? (data.psfPartnerLogo as Media) : null + const logoUrl = getMediaUrl(logoObj?.url) + const altText = logoObj?.alt || 'PSF Partner Logo' + + if (!logoUrl) { + return null + } + + return ( +
+ {altText} +
+ ) +} diff --git a/src/constants/collections.ts b/src/constants/collections.ts index d091b70..2650483 100644 --- a/src/constants/collections.ts +++ b/src/constants/collections.ts @@ -47,8 +47,8 @@ export function getCollectionGroupLabel(groupSlug: CollectionGroupSlug) { return COLLECTION_GROUPS_LABEL[groupSlug] } -export const COLLECTION_GROUP_ITEMS: Record = { - 'durianpy-website': [], +export const COLLECTION_GROUP_ITEMS: Record = { + 'durianpy-website': ['sample', 'organization-status'], admin: ['users'], } as const diff --git a/src/globals/durianpy-website/OrganizationStatus.ts b/src/globals/durianpy-website/OrganizationStatus.ts new file mode 100644 index 0000000..ea373ab --- /dev/null +++ b/src/globals/durianpy-website/OrganizationStatus.ts @@ -0,0 +1,68 @@ +import type { AccessArgs, GlobalConfig } from 'payload' + +import { checkCollectionAccess } from '@/access/checkCollectionAccess' +import { AccessType } from '@/constants/accessTypes' +import { getCollectionGroupLabel } from '@/constants/collections' + +const checkOrganizationStatusAccess = (accessType?: AccessType) => (access: AccessArgs) => + checkCollectionAccess(access, 'organization-status', accessType) + +export const OrganizationStatus: GlobalConfig = { + slug: 'organization-status', + access: { + read: (access: AccessArgs) => { + const isDraft = + Boolean((access.req as { draft?: boolean }).draft) || access.req.query?.draft === 'true' + if (isDraft) { + return checkOrganizationStatusAccess('read')(access) + } + return true + }, + update: checkOrganizationStatusAccess('update'), + }, + admin: { + group: getCollectionGroupLabel('durianpy-website'), + }, + versions: { + drafts: { + autosave: { + showSaveDraftButton: true, + }, + schedulePublish: true, + }, + max: 50, + }, + fields: [ + { + name: 'isPSFPartner', + type: 'checkbox', + label: 'Is PSF Partner', + defaultValue: false, + }, + { + name: 'psfPartnerLogo', + type: 'upload', + relationTo: 'media', + label: 'PSF Partner Logo', + admin: { + condition: (data) => Boolean(data?.isPSFPartner), + }, + }, + { + name: 'status', + type: 'select', + options: [ + { label: 'Active', value: 'active' }, + { label: 'Inactive', value: 'inactive' }, + { label: 'Pending', value: 'pending' }, + ], + defaultValue: 'active', + required: true, + }, + { + name: 'message', + type: 'textarea', + label: 'Status Message', + }, + ], +} diff --git a/src/payload-types.ts b/src/payload-types.ts index 0a66d77..8004bc1 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -103,8 +103,12 @@ export interface Config { defaultIDType: string; }; fallbackLocale: null; - globals: {}; - globalsSelect: {}; + globals: { + 'organization-status': OrganizationStatus; + }; + globalsSelect: { + 'organization-status': OrganizationStatusSelect | OrganizationStatusSelect; + }; locale: null; widgets: { collections: CollectionsWidget; @@ -1095,6 +1099,34 @@ export interface PayloadMigrationsSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "organization-status". + */ +export interface OrganizationStatus { + id: string; + isPSFPartner?: boolean | null; + psfPartnerLogo?: (string | null) | Media; + status: 'active' | 'inactive' | 'pending'; + message?: string | null; + _status?: ('draft' | 'published') | null; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "organization-status_select". + */ +export interface OrganizationStatusSelect { + isPSFPartner?: T; + psfPartnerLogo?: T; + status?: T; + message?: T; + _status?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "collections_widget". @@ -1117,7 +1149,7 @@ export interface TaskSchedulePublish { relationTo: 'sample'; value: string | Sample; } | null; - global?: string | null; + global?: 'organization-status' | null; user?: (string | null) | User; }; output?: unknown; @@ -1133,4 +1165,4 @@ export interface Auth { declare module 'payload' { export interface GeneratedTypes extends Config {} -} \ No newline at end of file +} diff --git a/src/payload.config.ts b/src/payload.config.ts index 261a467..312b1af 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -14,6 +14,7 @@ import { plugins } from './plugins' import { defaultLexical } from '@/fields/defaultLexical' import { getServerSideURL, getServerSideOrigin } from './utilities/getURL' import { Sample } from './collections/durianpy-website/sample-website-collection.index' +import { OrganizationStatus } from './globals/durianpy-website/OrganizationStatus' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -94,6 +95,7 @@ export default buildConfig({ }), }), collections: [Media, Categories, Users, Sample], + globals: [OrganizationStatus], cors: [getServerSideOrigin()].filter(Boolean).map((url) => { try { const { origin } = new URL(url!) diff --git a/src/utilities/getGlobals.ts b/src/utilities/getGlobals.ts new file mode 100644 index 0000000..7c19b5f --- /dev/null +++ b/src/utilities/getGlobals.ts @@ -0,0 +1,25 @@ +import type { Config } from '@/payload-types' +import configPromise from '@payload-config' +import { getPayload } from 'payload' +import { draftMode } from 'next/headers' + +type GlobalSlug = keyof Config['globals'] + +/** + * Fetches Global data inside Server Components with Next.js Draft Mode support. + */ +export async function getGlobal( + slug: T, + depth = 1, +): Promise { + const { isEnabled: draft } = await draftMode() + const payload = await getPayload({ config: configPromise }) + + const globalData = await payload.findGlobal({ + slug, + depth, + draft, + }) + + return globalData as Config['globals'][T] +}