@payloadcms/figma 0.1.0-alpha.4 → 0.1.0-alpha.6
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/db-content-api/index.js +31 -17
- package/dist/db-content-api/utilities/joins.js +5 -1
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.d.ts +10 -0
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.js +95 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.d.ts +9 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.js +112 -0
- package/dist/db-content-api/utilities/where.d.ts +2 -6
- package/dist/db-content-api/utilities/where.js +20 -11
- package/dist/exports/views.d.ts +1 -0
- package/dist/exports/views.js +1 -0
- package/dist/imageSizeOptions.d.ts +23 -0
- package/dist/imageSizeOptions.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +5 -2
- package/dist/oauth/index.js +8 -7
- package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +1 -1
- package/dist/oauth/utilities/getAuthorizeURL.d.ts +1 -0
- package/dist/oauth/utilities/getAuthorizeURL.js +5 -1
- package/dist/plugin/build-config.js +22 -9
- package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +2 -3
- package/dist/plugin/cloud-limits/CloudLimitModal/index.js +4 -16
- package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +5 -0
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +0 -3
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +65 -124
- package/dist/plugin/cloud-limits/LimitReachedView/index.css +14 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.d.ts +4 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.js +52 -0
- package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +7 -2
- package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +0 -8
- package/dist/plugin/cloud-limits/SlugTracker/index.js +14 -10
- package/dist/plugin/cloud-limits/routes.d.ts +2 -0
- package/dist/plugin/cloud-limits/routes.js +4 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.d.ts +9 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.js +28 -0
- package/dist/plugin/cloud-limits/shared.d.ts +1 -1
- package/dist/plugin/cloud-limits/shared.js +25 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.d.ts +2 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.js +134 -0
- package/dist/plugin/cloud-limits/utilities/toasts.d.ts +4 -0
- package/dist/plugin/cloud-limits/utilities/toasts.js +19 -0
- package/dist/storage-content-api/buildBaseMediaResponseHeaders.d.ts +1 -0
- package/dist/storage-content-api/buildBaseMediaResponseHeaders.js +6 -0
- package/dist/storage-content-api/buildClientUploadFetchBackHeaders.d.ts +10 -0
- package/dist/storage-content-api/buildClientUploadFetchBackHeaders.js +21 -0
- package/dist/storage-content-api/buildMediaResponseHeaders.d.ts +4 -0
- package/dist/storage-content-api/buildMediaResponseHeaders.js +45 -0
- package/dist/storage-content-api/getInlineContentType.d.ts +1 -0
- package/dist/storage-content-api/getInlineContentType.js +33 -0
- package/dist/storage-content-api/hasSupportedContentEncoding.d.ts +1 -0
- package/dist/storage-content-api/hasSupportedContentEncoding.js +4 -0
- package/dist/storage-content-api/mediaErrorResponse.d.ts +1 -0
- package/dist/storage-content-api/mediaErrorResponse.js +12 -0
- package/dist/storage-content-api/staticHandler.js +82 -21
- package/dist/utils/adapters/nextjs.js +8 -7
- package/dist/utils/asset-collection.d.ts +9 -1
- package/dist/utils/asset-collection.js +64 -2
- package/dist/utils/s3-upload.js +4 -1
- package/package.json +14 -2
- package/dist/db-content-api/utilities/data/stringifyNumericIds.d.ts +0 -1
- package/dist/db-content-api/utilities/data/stringifyNumericIds.js +0 -19
|
@@ -11,6 +11,7 @@ import { scheduleExtractPlugin } from '../deploy/schedule-extract-plugin.js';
|
|
|
11
11
|
import { health } from '../endpoints/health.js';
|
|
12
12
|
import { schema } from '../endpoints/schema.js';
|
|
13
13
|
import { oAuth2Plugin } from '../oauth/index.js';
|
|
14
|
+
import { getAdminCollectionSlug } from '../oauth/utilities/getAdminCollectionSlug.js';
|
|
14
15
|
import { createStorageClient } from '../storage-content-api/client.js';
|
|
15
16
|
import { contentApiStorageAdapter } from '../storage-content-api/index.js';
|
|
16
17
|
import { translations as figmaTranslations } from '../translations/index.js';
|
|
@@ -19,6 +20,7 @@ import { getEnvVarSync } from '../utils/env-management.js';
|
|
|
19
20
|
import * as log from '../utils/log.js';
|
|
20
21
|
import { logMissingCliAuth } from './auth-preflight.js';
|
|
21
22
|
import { logMissingContentSystemId } from './bootstrap-preflight.js';
|
|
23
|
+
import { LIMIT_REACHED_VIEW_KEY, LIMIT_REACHED_VIEW_PATH } from './cloud-limits/routes.js';
|
|
22
24
|
import { getDevCookieNames } from './dev-cookie-names.js';
|
|
23
25
|
import { createSandboxUploadFetchPlugin } from './sandbox-upload-fetch.js';
|
|
24
26
|
/**
|
|
@@ -30,7 +32,7 @@ import { createSandboxUploadFetchPlugin } from './sandbox-upload-fetch.js';
|
|
|
30
32
|
* KEEP_IN_SYNC(cloudflare/wrangler/sites-worker/src/auth.ts): FIGMA_INTERNAL_FORWARDED_HAS_AUTH_BYPASS_HEADER
|
|
31
33
|
*/ export const FIGMA_INTERNAL_FORWARDED_HAS_AUTH_BYPASS_HEADER = 'figma-internal-forwarded-has-auth-bypass';
|
|
32
34
|
/**
|
|
33
|
-
* Fields added to
|
|
35
|
+
* Fields added to the configured Payload Admin collection for Figma profile info
|
|
34
36
|
*/ const figmaUserFields = [
|
|
35
37
|
{
|
|
36
38
|
name: 'figmaHandle',
|
|
@@ -280,7 +282,8 @@ export async function buildFigmaConfig(config) {
|
|
|
280
282
|
}
|
|
281
283
|
// Determine database adapter based on environment
|
|
282
284
|
let db;
|
|
283
|
-
|
|
285
|
+
const usesContentSystem = config.figma.useContentSystem !== false;
|
|
286
|
+
if (!usesContentSystem) {
|
|
284
287
|
db = config.db;
|
|
285
288
|
} else if (process.env.FIGMA_CONTENT_API_ACCESS_KEY) {
|
|
286
289
|
db = contentAPIAdapter({
|
|
@@ -316,6 +319,7 @@ export async function buildFigmaConfig(config) {
|
|
|
316
319
|
});
|
|
317
320
|
}
|
|
318
321
|
const userJobsAccessRun = config.jobs?.access?.run;
|
|
322
|
+
const adminCollectionSlug = getAdminCollectionSlug(config);
|
|
319
323
|
// Build complete config with Figma platform defaults
|
|
320
324
|
const configWithFigmaDefaults = {
|
|
321
325
|
...config,
|
|
@@ -386,7 +390,7 @@ export async function buildFigmaConfig(config) {
|
|
|
386
390
|
oAuth2Plugin({
|
|
387
391
|
collections: [
|
|
388
392
|
{
|
|
389
|
-
slug:
|
|
393
|
+
slug: adminCollectionSlug,
|
|
390
394
|
// Use adapter because Figma app doesn't support OIDC scopes
|
|
391
395
|
authorizationURLParams: {
|
|
392
396
|
response_mode: 'query',
|
|
@@ -414,9 +418,10 @@ export async function buildFigmaConfig(config) {
|
|
|
414
418
|
})
|
|
415
419
|
]
|
|
416
420
|
};
|
|
417
|
-
// Inject
|
|
421
|
+
// Inject Figma-managed identity fields only into the Payload Admin collection.
|
|
418
422
|
const collections = configWithFigmaDefaults.collections?.map((col)=>{
|
|
419
|
-
const
|
|
423
|
+
const views = col.admin?.components?.views;
|
|
424
|
+
const withLimitView = usesContentSystem ? {
|
|
420
425
|
...col,
|
|
421
426
|
admin: {
|
|
422
427
|
...col.admin,
|
|
@@ -428,20 +433,28 @@ export async function buildFigmaConfig(config) {
|
|
|
428
433
|
'@payloadcms/figma/client#FigmaCloudLimitSlugTracker',
|
|
429
434
|
...col.admin?.components?.edit?.beforeDocumentControls ?? []
|
|
430
435
|
]
|
|
436
|
+
},
|
|
437
|
+
views: {
|
|
438
|
+
...views,
|
|
439
|
+
[LIMIT_REACHED_VIEW_KEY]: {
|
|
440
|
+
Component: '@payloadcms/figma/views#LimitReachedView',
|
|
441
|
+
exact: true,
|
|
442
|
+
path: LIMIT_REACHED_VIEW_PATH
|
|
443
|
+
}
|
|
431
444
|
}
|
|
432
445
|
}
|
|
433
446
|
}
|
|
434
|
-
};
|
|
435
|
-
if (col.slug ===
|
|
447
|
+
} : col;
|
|
448
|
+
if (col.slug === adminCollectionSlug) {
|
|
436
449
|
return {
|
|
437
|
-
...
|
|
450
|
+
...withLimitView,
|
|
438
451
|
fields: [
|
|
439
452
|
...col.fields || [],
|
|
440
453
|
...figmaUserFields
|
|
441
454
|
]
|
|
442
455
|
};
|
|
443
456
|
}
|
|
444
|
-
return
|
|
457
|
+
return withLimitView;
|
|
445
458
|
});
|
|
446
459
|
if (collections) {
|
|
447
460
|
configWithFigmaDefaults.collections = collections;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import './index.scss';
|
|
2
2
|
type Props = {
|
|
3
|
-
adminRoute: string;
|
|
4
3
|
collectionLabel?: string;
|
|
5
|
-
|
|
4
|
+
collectionListURL?: string;
|
|
6
5
|
onRetry: () => void;
|
|
7
6
|
};
|
|
8
|
-
export declare function FigmaCloudLimitDocumentCountModal({
|
|
7
|
+
export declare function FigmaCloudLimitDocumentCountModal({ collectionLabel, collectionListURL, onRetry, }: Props): import("react").JSX.Element;
|
|
9
8
|
export {};
|
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Button, DialogBody, DialogCancel, DialogFooter, DialogHeader, DialogModal, useModal, useTranslation } from '@payloadcms/ui';
|
|
4
|
-
import { useRouter } from 'next/navigation.js';
|
|
5
|
-
import { useCallback } from 'react';
|
|
6
4
|
import { ExternalLinkIcon } from '../icons/ExternalLinkIcon.js';
|
|
7
5
|
import { cloudLimitDocumentCountModalSlug } from '../shared.js';
|
|
8
6
|
import './index.scss';
|
|
9
7
|
const baseClass = 'figma-cloud-limit-modal';
|
|
10
|
-
export function FigmaCloudLimitDocumentCountModal({
|
|
8
|
+
export function FigmaCloudLimitDocumentCountModal({ collectionLabel, collectionListURL, onRetry }) {
|
|
11
9
|
const { t } = useTranslation();
|
|
12
10
|
const { closeModal } = useModal();
|
|
13
|
-
const router = useRouter();
|
|
14
|
-
const handleReviewClick = useCallback(()=>{
|
|
15
|
-
closeModal(cloudLimitDocumentCountModalSlug);
|
|
16
|
-
router.push(`${adminRoute}/collections/${collectionSlug}`);
|
|
17
|
-
}, [
|
|
18
|
-
adminRoute,
|
|
19
|
-
closeModal,
|
|
20
|
-
collectionSlug,
|
|
21
|
-
router
|
|
22
|
-
]);
|
|
23
11
|
return /*#__PURE__*/ _jsxs(DialogModal, {
|
|
24
12
|
size: "small",
|
|
25
13
|
slug: cloudLimitDocumentCountModalSlug,
|
|
@@ -38,10 +26,10 @@ export function FigmaCloudLimitDocumentCountModal({ adminRoute, collectionLabel,
|
|
|
38
26
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
39
27
|
className: `${baseClass}__footer`,
|
|
40
28
|
children: [
|
|
41
|
-
collectionLabel &&
|
|
29
|
+
collectionLabel && collectionListURL ? /*#__PURE__*/ _jsxs("a", {
|
|
42
30
|
className: `${baseClass}__review-btn`,
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
href: collectionListURL,
|
|
32
|
+
onClick: ()=>closeModal(cloudLimitDocumentCountModalSlug),
|
|
45
33
|
children: [
|
|
46
34
|
/*#__PURE__*/ _jsx("span", {
|
|
47
35
|
className: `${baseClass}__review-btn-label`,
|
|
@@ -1,113 +1,40 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { getTranslation } from '@payloadcms/translations';
|
|
4
|
-
import { FormErrorHandlerContext,
|
|
5
|
-
import {
|
|
4
|
+
import { FormErrorHandlerContext, useConfig, useFormErrorHandler, useModal, useTranslation } from '@payloadcms/ui';
|
|
5
|
+
import { DrawerDepthContext } from '@payloadcms/ui/elements/Drawer';
|
|
6
|
+
import { formatAdminURL, hasAutosaveEnabled } from 'payload/shared';
|
|
6
7
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
7
8
|
import { FigmaCloudLimitDocumentCountModal } from '../CloudLimitModal/index.js';
|
|
8
|
-
import { cloudLimitDocumentCountModalSlug, cloudLimitErrorCodes,
|
|
9
|
+
import { cloudLimitDocumentCountModalSlug, cloudLimitErrorCodes, getCloudLimitErrorCode } from '../shared.js';
|
|
9
10
|
import { FigmaCloudLimitSlugRegistryContext } from '../SlugRegistryContext/index.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function
|
|
13
|
-
return /^\/collections\/([^/?#]+)/.exec(suffix)?.[1];
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Surfaces Content API cloud-limit errors in the admin UI.
|
|
17
|
-
*/ export function FigmaCloudLimitProvider({ children }) {
|
|
11
|
+
import { cloudLimitInterceptor } from '../utilities/cloudLimitInterceptor.js';
|
|
12
|
+
import { dismissSubmittingToast, showCloudLimitToast } from '../utilities/toasts.js';
|
|
13
|
+
export function FigmaCloudLimitProvider({ children }) {
|
|
18
14
|
const { openModal } = useModal();
|
|
19
15
|
const { i18n, t } = useTranslation();
|
|
20
16
|
const { config, getEntityConfig } = useConfig();
|
|
21
17
|
const parentErrorHandler = useFormErrorHandler();
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const [
|
|
25
|
-
|
|
18
|
+
const adminRoute = config.routes?.admin ?? '/admin';
|
|
19
|
+
const apiRoute = config.routes?.api ?? '/api';
|
|
20
|
+
const [modal, setModal] = useState({
|
|
21
|
+
drawerDepth: 0
|
|
22
|
+
});
|
|
26
23
|
const retryRef = useRef(undefined);
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
const tRef = useRef(t);
|
|
31
|
-
tRef.current = t;
|
|
32
|
-
// Stack of active collection slugs. FigmaCloudLimitSlugTracker components push/pop as
|
|
33
|
-
// documents mount/unmount (including drawers). The last entry is the active form's collection.
|
|
34
|
-
const slugStackRef = useRef([]);
|
|
35
|
-
const registerCollectionSlug = useCallback((slug)=>{
|
|
36
|
-
const id = Symbol();
|
|
37
|
-
slugStackRef.current = [
|
|
38
|
-
...slugStackRef.current,
|
|
39
|
-
{
|
|
40
|
-
id,
|
|
41
|
-
slug
|
|
42
|
-
}
|
|
43
|
-
];
|
|
24
|
+
const registrationsRef = useRef([]);
|
|
25
|
+
const register = useCallback((registration)=>{
|
|
26
|
+
registrationsRef.current.push(registration);
|
|
44
27
|
return ()=>{
|
|
45
|
-
|
|
46
|
-
};
|
|
47
|
-
}, []);
|
|
48
|
-
// Detect upload related cloud-limit that bypass form state
|
|
49
|
-
useEffect(()=>{
|
|
50
|
-
if (isUploadFetchPatched) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
isUploadFetchPatched = true;
|
|
54
|
-
const originalFetch = window.fetch.bind(window);
|
|
55
|
-
const originalToastError = toast.error;
|
|
56
|
-
const toastObj = toast;
|
|
57
|
-
const pendingSuppressions = [];
|
|
58
|
-
const removeSuppression = (suppression)=>{
|
|
59
|
-
window.clearTimeout(suppression.timeout);
|
|
60
|
-
const index = pendingSuppressions.indexOf(suppression);
|
|
28
|
+
const index = registrationsRef.current.indexOf(registration);
|
|
61
29
|
if (index !== -1) {
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
toastObj.error = (...args)=>{
|
|
66
|
-
// prevent the thrown error from sending a duplicate toast
|
|
67
|
-
const msg = typeof args[0] === 'string' ? args[0] : args[0]?.props?.errorMessage ?? '';
|
|
68
|
-
const suppression = pendingSuppressions.find(({ message })=>msg.includes(message));
|
|
69
|
-
if (suppression) {
|
|
70
|
-
removeSuppression(suppression);
|
|
71
|
-
return '';
|
|
72
|
-
}
|
|
73
|
-
return originalToastError(...args);
|
|
74
|
-
};
|
|
75
|
-
window.fetch = async function(...args) {
|
|
76
|
-
const response = await originalFetch(...args);
|
|
77
|
-
if (response.status === 409) {
|
|
78
|
-
const url = args[0] instanceof Request ? args[0].url : String(args[0]);
|
|
79
|
-
const uploadInstructionsPath = `${apiRouteRef.current.replace(/\/$/, '')}/upload-instructions`;
|
|
80
|
-
if (new URL(url, window.location.origin).pathname === uploadInstructionsPath) {
|
|
81
|
-
try {
|
|
82
|
-
const json = await response.clone().json();
|
|
83
|
-
const code = getCloudLimitErrorCode(json);
|
|
84
|
-
const messageKey = code !== undefined ? cloudLimitToastKeyByCode[code] : undefined;
|
|
85
|
-
if (messageKey) {
|
|
86
|
-
toast.info(tRef.current(messageKey));
|
|
87
|
-
const rawMessage = json?.errors?.[0]?.message;
|
|
88
|
-
if (rawMessage) {
|
|
89
|
-
const suppression = {
|
|
90
|
-
message: rawMessage,
|
|
91
|
-
timeout: 0
|
|
92
|
-
};
|
|
93
|
-
suppression.timeout = window.setTimeout(()=>removeSuppression(suppression), 0);
|
|
94
|
-
pendingSuppressions.push(suppression);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
// ignore JSON parse failures
|
|
99
|
-
}
|
|
100
|
-
}
|
|
30
|
+
registrationsRef.current.splice(index, 1);
|
|
101
31
|
}
|
|
102
|
-
return response;
|
|
103
|
-
};
|
|
104
|
-
return ()=>{
|
|
105
|
-
pendingSuppressions.forEach(({ timeout })=>window.clearTimeout(timeout));
|
|
106
|
-
window.fetch = originalFetch;
|
|
107
|
-
toastObj.error = originalToastError;
|
|
108
|
-
isUploadFetchPatched = false;
|
|
109
32
|
};
|
|
110
33
|
}, []);
|
|
34
|
+
useEffect(()=>cloudLimitInterceptor(apiRoute, (key)=>t(key)), [
|
|
35
|
+
apiRoute,
|
|
36
|
+
t
|
|
37
|
+
]);
|
|
111
38
|
const handleNonFieldError = useCallback((err, retry)=>{
|
|
112
39
|
const code = getCloudLimitErrorCode({
|
|
113
40
|
errors: [
|
|
@@ -117,51 +44,65 @@ function slugFromPathSuffix(suffix) {
|
|
|
117
44
|
if (!code) {
|
|
118
45
|
return parentErrorHandler?.(err, retry) ?? false;
|
|
119
46
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const slug = stack.length > 0 ? stack[stack.length - 1]?.slug : slugFromPathSuffix(window.location.pathname.slice(adminRouteRef.current.length));
|
|
124
|
-
const collectionConfig = slug ? getEntityConfigRef.current({
|
|
125
|
-
collectionSlug: slug
|
|
126
|
-
}) : undefined;
|
|
127
|
-
const isAutosave = collectionConfig ? hasAutosaveEnabled(collectionConfig) : false;
|
|
128
|
-
if (isAutosave) {
|
|
129
|
-
// Stable ID prevents a new toast stacking on every autosave attempt.
|
|
130
|
-
toast.info(t('figma:cloudLimitDocumentCountError'), {
|
|
131
|
-
id: AUTOSAVE_TOAST_ID
|
|
132
|
-
});
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
const label = collectionConfig?.labels?.plural != null ? getTranslation(collectionConfig.labels.plural, i18n) : slug;
|
|
136
|
-
setCollectionSlug(slug);
|
|
137
|
-
setCollectionLabel(label);
|
|
138
|
-
retryRef.current = retry;
|
|
139
|
-
openModal(cloudLimitDocumentCountModalSlug);
|
|
47
|
+
dismissSubmittingToast(t('general:submitting'));
|
|
48
|
+
if (code !== cloudLimitErrorCodes.documentCount) {
|
|
49
|
+
showCloudLimitToast(code, t);
|
|
140
50
|
return true;
|
|
141
51
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
52
|
+
const registrations = registrationsRef.current;
|
|
53
|
+
let activeRegistration;
|
|
54
|
+
for(let index = registrations.length - 1; index >= 0; index--){
|
|
55
|
+
if (registrations[index]?.isSubmitting) {
|
|
56
|
+
activeRegistration = registrations[index];
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
activeRegistration ??= registrations[registrations.length - 1];
|
|
61
|
+
const adminPath = formatAdminURL({
|
|
62
|
+
adminRoute,
|
|
63
|
+
includeBasePath: true
|
|
64
|
+
});
|
|
65
|
+
const slug = activeRegistration?.slug ?? /^\/collections\/([^/?#]+)/.exec(window.location.pathname.slice(adminPath.length))?.[1];
|
|
66
|
+
const collectionConfig = slug ? getEntityConfig({
|
|
67
|
+
collectionSlug: slug
|
|
68
|
+
}) : undefined;
|
|
69
|
+
if (collectionConfig && hasAutosaveEnabled(collectionConfig)) {
|
|
70
|
+
showCloudLimitToast(code, t);
|
|
145
71
|
return true;
|
|
146
72
|
}
|
|
147
|
-
|
|
73
|
+
setModal({
|
|
74
|
+
collectionLabel: collectionConfig?.labels?.plural != null ? getTranslation(collectionConfig.labels.plural, i18n) : slug,
|
|
75
|
+
collectionListURL: slug ? formatAdminURL({
|
|
76
|
+
adminRoute,
|
|
77
|
+
includeBasePath: true,
|
|
78
|
+
path: `/collections/${slug}`
|
|
79
|
+
}) : undefined,
|
|
80
|
+
drawerDepth: activeRegistration?.drawerDepth ?? 0
|
|
81
|
+
});
|
|
82
|
+
retryRef.current = retry;
|
|
83
|
+
openModal(cloudLimitDocumentCountModalSlug);
|
|
84
|
+
return true;
|
|
148
85
|
}, [
|
|
86
|
+
adminRoute,
|
|
87
|
+
getEntityConfig,
|
|
149
88
|
i18n,
|
|
150
89
|
openModal,
|
|
151
90
|
parentErrorHandler,
|
|
152
91
|
t
|
|
153
92
|
]);
|
|
154
93
|
return /*#__PURE__*/ _jsx(FigmaCloudLimitSlugRegistryContext, {
|
|
155
|
-
value:
|
|
94
|
+
value: register,
|
|
156
95
|
children: /*#__PURE__*/ _jsxs(FormErrorHandlerContext, {
|
|
157
96
|
value: handleNonFieldError,
|
|
158
97
|
children: [
|
|
159
98
|
children,
|
|
160
|
-
/*#__PURE__*/ _jsx(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
99
|
+
/*#__PURE__*/ _jsx(DrawerDepthContext, {
|
|
100
|
+
value: modal.drawerDepth,
|
|
101
|
+
children: /*#__PURE__*/ _jsx(FigmaCloudLimitDocumentCountModal, {
|
|
102
|
+
collectionLabel: modal.collectionLabel,
|
|
103
|
+
collectionListURL: modal.collectionListURL,
|
|
104
|
+
onRetry: ()=>retryRef.current?.()
|
|
105
|
+
})
|
|
165
106
|
})
|
|
166
107
|
]
|
|
167
108
|
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.limit-hit-on-create {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
gap: var(--spacer-3);
|
|
7
|
+
min-block-size: calc(100dvh - var(--app-header-height, 0px));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.limit-hit-on-create__message {
|
|
11
|
+
margin: 0;
|
|
12
|
+
color: var(--color-text);
|
|
13
|
+
font-size: var(--text-body-medium-font-size);
|
|
14
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { getTranslation } from '@payloadcms/translations';
|
|
3
|
+
import { Button, SetStepNav } from '@payloadcms/ui';
|
|
4
|
+
import { formatAdminURL } from 'payload/shared';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import './index.css';
|
|
7
|
+
const baseClass = 'limit-hit-on-create';
|
|
8
|
+
export const LimitReachedView = ({ collectionConfig, initPageResult })=>{
|
|
9
|
+
const { req } = initPageResult;
|
|
10
|
+
const adminRoute = req.payload.config.routes.admin;
|
|
11
|
+
const tFigma = req.t;
|
|
12
|
+
if (!collectionConfig) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const collectionLabel = getTranslation(collectionConfig.labels.plural, req.i18n);
|
|
16
|
+
const collectionListURL = formatAdminURL({
|
|
17
|
+
adminRoute,
|
|
18
|
+
includeBasePath: true,
|
|
19
|
+
path: `/collections/${collectionConfig.slug}`
|
|
20
|
+
});
|
|
21
|
+
return /*#__PURE__*/ _jsxs("main", {
|
|
22
|
+
className: baseClass,
|
|
23
|
+
children: [
|
|
24
|
+
/*#__PURE__*/ _jsx(SetStepNav, {
|
|
25
|
+
nav: [
|
|
26
|
+
{
|
|
27
|
+
label: collectionLabel,
|
|
28
|
+
url: collectionListURL
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: req.t('general:createNew')
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ _jsx("p", {
|
|
36
|
+
className: `${baseClass}__message`,
|
|
37
|
+
children: tFigma('figma:cloudLimitDocumentCountError')
|
|
38
|
+
}),
|
|
39
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
40
|
+
buttonStyle: "primary",
|
|
41
|
+
el: "link",
|
|
42
|
+
margin: false,
|
|
43
|
+
size: "medium",
|
|
44
|
+
url: formatAdminURL({
|
|
45
|
+
adminRoute,
|
|
46
|
+
includeBasePath: true
|
|
47
|
+
}),
|
|
48
|
+
children: req.t('general:backToDashboard')
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
};
|
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export type
|
|
2
|
-
|
|
1
|
+
export type CollectionFormRegistration = {
|
|
2
|
+
drawerDepth: number;
|
|
3
|
+
isSubmitting: boolean;
|
|
4
|
+
slug: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
export type RegisterCollectionForm = (registration: CollectionFormRegistration) => () => void;
|
|
7
|
+
export declare const FigmaCloudLimitSlugRegistryContext: import("react").Context<RegisterCollectionForm>;
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Invisible component injected into every collection's
|
|
3
|
-
* `admin.components.edit.beforeDocumentControls`. Renders inside
|
|
4
|
-
* DocumentInfoProvider so it can read the correct collectionSlug even inside
|
|
5
|
-
* relationship drawers. Pushes to / pops from the slug stack owned by
|
|
6
|
-
* FigmaCloudLimitProvider so the error handler always knows which collection
|
|
7
|
-
* the active form belongs to.
|
|
8
|
-
*/
|
|
9
1
|
export declare function FigmaCloudLimitSlugTracker(): null;
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useDocumentInfo } from '@payloadcms/ui';
|
|
2
|
+
import { useDocumentInfo, useFormBackgroundProcessing, useFormProcessing } from '@payloadcms/ui';
|
|
3
|
+
import { useDrawerDepth } from '@payloadcms/ui/elements/Drawer';
|
|
3
4
|
import { use, useEffect } from 'react';
|
|
4
5
|
import { FigmaCloudLimitSlugRegistryContext } from '../SlugRegistryContext/index.js';
|
|
5
|
-
|
|
6
|
-
* Invisible component injected into every collection's
|
|
7
|
-
* `admin.components.edit.beforeDocumentControls`. Renders inside
|
|
8
|
-
* DocumentInfoProvider so it can read the correct collectionSlug even inside
|
|
9
|
-
* relationship drawers. Pushes to / pops from the slug stack owned by
|
|
10
|
-
* FigmaCloudLimitProvider so the error handler always knows which collection
|
|
11
|
-
* the active form belongs to.
|
|
12
|
-
*/ export function FigmaCloudLimitSlugTracker() {
|
|
6
|
+
export function FigmaCloudLimitSlugTracker() {
|
|
13
7
|
const register = use(FigmaCloudLimitSlugRegistryContext);
|
|
14
8
|
const { collectionSlug } = useDocumentInfo();
|
|
9
|
+
const drawerDepth = useDrawerDepth();
|
|
10
|
+
const isBackgroundProcessing = useFormBackgroundProcessing();
|
|
11
|
+
const isProcessing = useFormProcessing();
|
|
15
12
|
useEffect(()=>{
|
|
16
|
-
const unregister = register(
|
|
13
|
+
const unregister = register({
|
|
14
|
+
slug: collectionSlug,
|
|
15
|
+
drawerDepth,
|
|
16
|
+
isSubmitting: isBackgroundProcessing || isProcessing
|
|
17
|
+
});
|
|
17
18
|
return unregister;
|
|
18
19
|
}, [
|
|
19
20
|
collectionSlug,
|
|
21
|
+
drawerDepth,
|
|
22
|
+
isBackgroundProcessing,
|
|
23
|
+
isProcessing,
|
|
20
24
|
register
|
|
21
25
|
]);
|
|
22
26
|
return null;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const LIMIT_REACHED_VIEW_KEY = 'figmaLimitReached';
|
|
2
|
+
// Reserved collection-level admin route. Applications using custom string IDs must not use
|
|
3
|
+
// "limit-reached"; the custom view intentionally takes precedence over a document edit route.
|
|
4
|
+
export const LIMIT_REACHED_VIEW_PATH = '/limit-reached';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
type Args = {
|
|
3
|
+
collectionSlug: string;
|
|
4
|
+
errorCode: unknown;
|
|
5
|
+
payload: Payload;
|
|
6
|
+
req?: Partial<Pick<PayloadRequest, 'pathname' | 'server'>>;
|
|
7
|
+
};
|
|
8
|
+
export declare function redirectInitialAutosaveCreate({ collectionSlug, errorCode, payload, req, }: Args): void;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { formatAdminURL, hasAutosaveEnabled } from 'payload/shared';
|
|
2
|
+
import { LIMIT_REACHED_VIEW_PATH } from '../routes.js';
|
|
3
|
+
import { cloudLimitErrorCodes } from '../shared.js';
|
|
4
|
+
export function redirectInitialAutosaveCreate({ collectionSlug, errorCode, payload, req }) {
|
|
5
|
+
// Initial autosave creates an empty document, so documentDataSize is not expected in
|
|
6
|
+
// production. Keep it as a defensive/test-only fallback and intentionally route it
|
|
7
|
+
// through the same terminal create-failure view.
|
|
8
|
+
const isSupportedLimit = errorCode === cloudLimitErrorCodes.documentCount || errorCode === cloudLimitErrorCodes.documentDataSize;
|
|
9
|
+
const collectionConfig = payload.collections[collectionSlug]?.config;
|
|
10
|
+
const server = req?.server;
|
|
11
|
+
if (!isSupportedLimit || !collectionConfig || !server || !hasAutosaveEnabled(collectionConfig)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const { admin: adminRoute } = payload.config.routes;
|
|
15
|
+
const createPath = formatAdminURL({
|
|
16
|
+
adminRoute,
|
|
17
|
+
includeBasePath: true,
|
|
18
|
+
path: `/collections/${collectionSlug}/create`
|
|
19
|
+
});
|
|
20
|
+
if (req.pathname?.replace(/\/$/, '') !== createPath.replace(/\/$/, '')) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
server.redirect(formatAdminURL({
|
|
24
|
+
adminRoute,
|
|
25
|
+
includeBasePath: true,
|
|
26
|
+
path: `/collections/${collectionSlug}${LIMIT_REACHED_VIEW_PATH}`
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
@@ -7,4 +7,4 @@ export type CloudLimitErrorCode = (typeof cloudLimitErrorCodes)[keyof typeof clo
|
|
|
7
7
|
export declare function isCloudLimitErrorCode(code: unknown): code is CloudLimitErrorCode;
|
|
8
8
|
export declare function getCloudLimitErrorCode(json: unknown): CloudLimitErrorCode | undefined;
|
|
9
9
|
export declare const cloudLimitDocumentCountModalSlug = "figma-cloud-limit-document-count";
|
|
10
|
-
export declare const cloudLimitToastKeyByCode:
|
|
10
|
+
export declare const cloudLimitToastKeyByCode: Record<CloudLimitErrorCode, string>;
|
|
@@ -7,6 +7,20 @@ const knownCloudLimitCodes = Object.values(cloudLimitErrorCodes);
|
|
|
7
7
|
export function isCloudLimitErrorCode(code) {
|
|
8
8
|
return typeof code === 'string' && knownCloudLimitCodes.includes(code);
|
|
9
9
|
}
|
|
10
|
+
const cloudLimitCodeByMessagePattern = [
|
|
11
|
+
{
|
|
12
|
+
code: cloudLimitErrorCodes.documentCount,
|
|
13
|
+
pattern: /document count/i
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: cloudLimitErrorCodes.documentDataSize,
|
|
17
|
+
pattern: /document data size/i
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
code: cloudLimitErrorCodes.assetStorage,
|
|
21
|
+
pattern: /asset storage limit/i
|
|
22
|
+
}
|
|
23
|
+
];
|
|
10
24
|
export function getCloudLimitErrorCode(json) {
|
|
11
25
|
const errors = json?.errors;
|
|
12
26
|
if (!Array.isArray(errors)) {
|
|
@@ -17,11 +31,22 @@ export function getCloudLimitErrorCode(json) {
|
|
|
17
31
|
if (isCloudLimitErrorCode(code)) {
|
|
18
32
|
return code;
|
|
19
33
|
}
|
|
34
|
+
const message = err?.message;
|
|
35
|
+
if (message?.startsWith('Content API ')) {
|
|
36
|
+
// Payload's bulk-upload response currently drops APIError.data.code. Match only the
|
|
37
|
+
// identifying phrase from each Content API message so variable values and minor wording
|
|
38
|
+
// changes do not break the fallback. Prefer the structured code above whenever present.
|
|
39
|
+
const match = cloudLimitCodeByMessagePattern.find(({ pattern })=>pattern.test(message));
|
|
40
|
+
if (match) {
|
|
41
|
+
return match.code;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
20
44
|
}
|
|
21
45
|
return undefined;
|
|
22
46
|
}
|
|
23
47
|
export const cloudLimitDocumentCountModalSlug = 'figma-cloud-limit-document-count';
|
|
24
48
|
export const cloudLimitToastKeyByCode = {
|
|
25
49
|
[cloudLimitErrorCodes.assetStorage]: 'figma:cloudLimitAssetStorageError',
|
|
50
|
+
[cloudLimitErrorCodes.documentCount]: 'figma:cloudLimitDocumentCountError',
|
|
26
51
|
[cloudLimitErrorCodes.documentDataSize]: 'figma:cloudLimitDocumentDataSizeError'
|
|
27
52
|
};
|