@payloadcms/figma 0.1.0-alpha.2 → 0.1.0-alpha.3

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.
Files changed (151) hide show
  1. package/dist/api/control-plane.d.ts +1 -1
  2. package/dist/api/control-plane.js +1 -1
  3. package/dist/cli.d.ts +30 -0
  4. package/dist/cli.js +33 -33
  5. package/dist/commands/deploy.d.ts +2 -0
  6. package/dist/commands/deploy.js +22 -12
  7. package/dist/db-content-api/index.d.ts +1 -1
  8. package/dist/db-content-api/index.js +70 -40
  9. package/dist/db-content-api/utilities/schema/getDocumentSchema.d.ts +3 -0
  10. package/dist/db-content-api/utilities/schema/getDocumentSchema.js +24 -0
  11. package/dist/exports/client.d.ts +2 -0
  12. package/dist/exports/client.js +2 -0
  13. package/dist/plugin/build-config.js +32 -3
  14. package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +9 -0
  15. package/dist/plugin/cloud-limits/CloudLimitModal/index.js +78 -0
  16. package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +51 -0
  17. package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +7 -0
  18. package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +169 -0
  19. package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +2 -0
  20. package/dist/plugin/cloud-limits/SlugRegistryContext/index.js +3 -0
  21. package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +9 -0
  22. package/dist/plugin/cloud-limits/SlugTracker/index.js +23 -0
  23. package/dist/plugin/cloud-limits/icons/ExternalLinkIcon.d.ts +2 -0
  24. package/dist/plugin/cloud-limits/icons/ExternalLinkIcon.js +30 -0
  25. package/dist/plugin/cloud-limits/shared.d.ts +10 -0
  26. package/dist/plugin/cloud-limits/shared.js +27 -0
  27. package/dist/translations/ar.d.ts +12 -0
  28. package/dist/translations/ar.js +12 -0
  29. package/dist/translations/az.d.ts +12 -0
  30. package/dist/translations/az.js +12 -0
  31. package/dist/translations/bg.d.ts +12 -0
  32. package/dist/translations/bg.js +12 -0
  33. package/dist/translations/bnBd.d.ts +12 -0
  34. package/dist/translations/bnBd.js +12 -0
  35. package/dist/translations/bnIn.d.ts +12 -0
  36. package/dist/translations/bnIn.js +12 -0
  37. package/dist/translations/ca.d.ts +12 -0
  38. package/dist/translations/ca.js +12 -0
  39. package/dist/translations/cs.d.ts +12 -0
  40. package/dist/translations/cs.js +12 -0
  41. package/dist/translations/da.d.ts +12 -0
  42. package/dist/translations/da.js +12 -0
  43. package/dist/translations/de.d.ts +12 -0
  44. package/dist/translations/de.js +12 -0
  45. package/dist/translations/en.d.ts +12 -0
  46. package/dist/translations/en.js +12 -0
  47. package/dist/translations/es.d.ts +12 -0
  48. package/dist/translations/es.js +12 -0
  49. package/dist/translations/et.d.ts +12 -0
  50. package/dist/translations/et.js +12 -0
  51. package/dist/translations/fa.d.ts +12 -0
  52. package/dist/translations/fa.js +12 -0
  53. package/dist/translations/fr.d.ts +12 -0
  54. package/dist/translations/fr.js +12 -0
  55. package/dist/translations/he.d.ts +12 -0
  56. package/dist/translations/he.js +12 -0
  57. package/dist/translations/hr.d.ts +12 -0
  58. package/dist/translations/hr.js +12 -0
  59. package/dist/translations/hu.d.ts +12 -0
  60. package/dist/translations/hu.js +12 -0
  61. package/dist/translations/hy.d.ts +12 -0
  62. package/dist/translations/hy.js +12 -0
  63. package/dist/translations/id.d.ts +12 -0
  64. package/dist/translations/id.js +12 -0
  65. package/dist/translations/index.d.ts +533 -0
  66. package/dist/translations/index.js +90 -0
  67. package/dist/translations/is.d.ts +12 -0
  68. package/dist/translations/is.js +12 -0
  69. package/dist/translations/it.d.ts +12 -0
  70. package/dist/translations/it.js +12 -0
  71. package/dist/translations/ja.d.ts +12 -0
  72. package/dist/translations/ja.js +12 -0
  73. package/dist/translations/ko.d.ts +12 -0
  74. package/dist/translations/ko.js +12 -0
  75. package/dist/translations/lt.d.ts +12 -0
  76. package/dist/translations/lt.js +12 -0
  77. package/dist/translations/lv.d.ts +12 -0
  78. package/dist/translations/lv.js +12 -0
  79. package/dist/translations/my.d.ts +12 -0
  80. package/dist/translations/my.js +12 -0
  81. package/dist/translations/nb.d.ts +12 -0
  82. package/dist/translations/nb.js +12 -0
  83. package/dist/translations/nl.d.ts +12 -0
  84. package/dist/translations/nl.js +12 -0
  85. package/dist/translations/pl.d.ts +12 -0
  86. package/dist/translations/pl.js +12 -0
  87. package/dist/translations/pt.d.ts +12 -0
  88. package/dist/translations/pt.js +12 -0
  89. package/dist/translations/ro.d.ts +12 -0
  90. package/dist/translations/ro.js +12 -0
  91. package/dist/translations/rs.d.ts +12 -0
  92. package/dist/translations/rs.js +12 -0
  93. package/dist/translations/rsLatin.d.ts +12 -0
  94. package/dist/translations/rsLatin.js +12 -0
  95. package/dist/translations/ru.d.ts +12 -0
  96. package/dist/translations/ru.js +12 -0
  97. package/dist/translations/sk.d.ts +12 -0
  98. package/dist/translations/sk.js +12 -0
  99. package/dist/translations/sl.d.ts +12 -0
  100. package/dist/translations/sl.js +12 -0
  101. package/dist/translations/sv.d.ts +12 -0
  102. package/dist/translations/sv.js +12 -0
  103. package/dist/translations/ta.d.ts +12 -0
  104. package/dist/translations/ta.js +12 -0
  105. package/dist/translations/th.d.ts +12 -0
  106. package/dist/translations/th.js +12 -0
  107. package/dist/translations/tr.d.ts +12 -0
  108. package/dist/translations/tr.js +12 -0
  109. package/dist/translations/uk.d.ts +12 -0
  110. package/dist/translations/uk.js +12 -0
  111. package/dist/translations/vi.d.ts +12 -0
  112. package/dist/translations/vi.js +12 -0
  113. package/dist/translations/zh.d.ts +12 -0
  114. package/dist/translations/zh.js +12 -0
  115. package/dist/translations/zhTw.d.ts +12 -0
  116. package/dist/translations/zhTw.js +12 -0
  117. package/dist/types.d.ts +2 -0
  118. package/dist/utils/adapters/nextjs.d.ts +3 -3
  119. package/dist/utils/adapters/nextjs.js +9 -7
  120. package/dist/utils/adapters/nitro.d.ts +3 -3
  121. package/dist/utils/adapters/nitro.js +73 -77
  122. package/dist/utils/adapters/vite.d.ts +1 -1
  123. package/dist/utils/adapters/vite.js +8 -7
  124. package/dist/utils/assert-never.d.ts +1 -0
  125. package/dist/utils/assert-never.js +3 -0
  126. package/dist/utils/asset-collection.d.ts +2 -2
  127. package/dist/utils/asset-collection.js +10 -15
  128. package/dist/utils/build-detection.d.ts +6 -1
  129. package/dist/utils/build-detection.js +68 -89
  130. package/dist/utils/build-lambda-zip.d.ts +5 -6
  131. package/dist/utils/build-lambda-zip.js +31 -48
  132. package/dist/utils/deploy-adapter.d.ts +4 -4
  133. package/dist/utils/deploy-adapter.js +12 -8
  134. package/dist/utils/deploy-output/inspectNextjsOutput/getNextRelativeAppDir.d.ts +1 -0
  135. package/dist/utils/deploy-output/inspectNextjsOutput/getNextRelativeAppDir.js +15 -0
  136. package/dist/utils/deploy-output/inspectNextjsOutput.d.ts +12 -0
  137. package/dist/utils/deploy-output/inspectNextjsOutput.js +28 -0
  138. package/dist/utils/deploy-output/inspectNitroOutput.d.ts +9 -0
  139. package/dist/utils/deploy-output/inspectNitroOutput.js +13 -0
  140. package/dist/utils/deploy-output/isFile.d.ts +1 -0
  141. package/dist/utils/deploy-output/isFile.js +4 -0
  142. package/dist/utils/deploy-output/readJson.d.ts +1 -0
  143. package/dist/utils/deploy-output/readJson.js +9 -0
  144. package/dist/utils/deploy-output/resolveNitroOutputLayout.d.ts +8 -0
  145. package/dist/utils/deploy-output/resolveNitroOutputLayout.js +16 -0
  146. package/dist/utils/deploy-output/resolveOutputPath.d.ts +2 -0
  147. package/dist/utils/deploy-output/resolveOutputPath.js +9 -0
  148. package/dist/utils/deploy-output/resolveWithin.d.ts +1 -0
  149. package/dist/utils/deploy-output/resolveWithin.js +12 -0
  150. package/dist/utils/messages.js +2 -0
  151. package/package.json +5 -5
@@ -1,6 +1,7 @@
1
1
  import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage';
2
2
  import { lexicalEditor } from '@payloadcms/richtext-lexical';
3
3
  import { buildConfig as payloadBuildConfig } from 'payload';
4
+ import { deepMergeSimple } from 'payload/shared';
4
5
  import { getBootstrapInfo } from '../api/control-plane.js';
5
6
  import { getValidCredential } from '../auth/oauth-flow.js';
6
7
  import { getTokenStore } from '../auth/token-store.js';
@@ -12,6 +13,7 @@ import { schema } from '../endpoints/schema.js';
12
13
  import { oAuth2Plugin } from '../oauth/index.js';
13
14
  import { createStorageClient } from '../storage-content-api/client.js';
14
15
  import { contentApiStorageAdapter } from '../storage-content-api/index.js';
16
+ import { translations as figmaTranslations } from '../translations/index.js';
15
17
  import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
16
18
  import { getEnvVarSync } from '../utils/env-management.js';
17
19
  import * as log from '../utils/log.js';
@@ -323,7 +325,14 @@ export async function buildFigmaConfig(config) {
323
325
  avatar: {
324
326
  Component: '@payloadcms/figma/client#FigmaAvatar'
325
327
  },
326
- ...config.admin
328
+ ...config.admin,
329
+ components: {
330
+ ...config.admin?.components,
331
+ providers: [
332
+ ...config.admin?.components?.providers ?? [],
333
+ '@payloadcms/figma/client#FigmaCloudLimitProvider'
334
+ ]
335
+ }
327
336
  },
328
337
  custom: {
329
338
  figma: {
@@ -339,6 +348,10 @@ export async function buildFigmaConfig(config) {
339
348
  health,
340
349
  schema
341
350
  ],
351
+ i18n: {
352
+ ...config.i18n,
353
+ translations: deepMergeSimple(figmaTranslations, config.i18n?.translations ?? {})
354
+ },
342
355
  // Gate /api/payload-jobs/run and /api/payload-jobs/handle-schedules. Honor the
343
356
  // sites-worker trust marker so Figma infra job runs never break, defer to the
344
357
  // tenant's jobs.access.run if they set one, otherwise fall back to Payload's
@@ -403,16 +416,32 @@ export async function buildFigmaConfig(config) {
403
416
  };
404
417
  // Inject figma fields into users collection
405
418
  const collections = configWithFigmaDefaults.collections?.map((col)=>{
419
+ const withSlugTracker = {
420
+ ...col,
421
+ admin: {
422
+ ...col.admin,
423
+ components: {
424
+ ...col.admin?.components,
425
+ edit: {
426
+ ...col.admin?.components?.edit,
427
+ beforeDocumentControls: [
428
+ '@payloadcms/figma/client#FigmaCloudLimitSlugTracker',
429
+ ...col.admin?.components?.edit?.beforeDocumentControls ?? []
430
+ ]
431
+ }
432
+ }
433
+ }
434
+ };
406
435
  if (col.slug === 'users') {
407
436
  return {
408
- ...col,
437
+ ...withSlugTracker,
409
438
  fields: [
410
439
  ...col.fields || [],
411
440
  ...figmaUserFields
412
441
  ]
413
442
  };
414
443
  }
415
- return col;
444
+ return withSlugTracker;
416
445
  });
417
446
  if (collections) {
418
447
  configWithFigmaDefaults.collections = collections;
@@ -0,0 +1,9 @@
1
+ import './index.scss';
2
+ type Props = {
3
+ adminRoute: string;
4
+ collectionLabel?: string;
5
+ collectionSlug?: string;
6
+ onRetry: () => void;
7
+ };
8
+ export declare function FigmaCloudLimitDocumentCountModal({ adminRoute, collectionLabel, collectionSlug, onRetry, }: Props): import("react").JSX.Element;
9
+ export {};
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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
+ import { ExternalLinkIcon } from '../icons/ExternalLinkIcon.js';
7
+ import { cloudLimitDocumentCountModalSlug } from '../shared.js';
8
+ import './index.scss';
9
+ const baseClass = 'figma-cloud-limit-modal';
10
+ export function FigmaCloudLimitDocumentCountModal({ adminRoute, collectionLabel, collectionSlug, onRetry }) {
11
+ const { t } = useTranslation();
12
+ 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
+ return /*#__PURE__*/ _jsxs(DialogModal, {
24
+ size: "small",
25
+ slug: cloudLimitDocumentCountModalSlug,
26
+ children: [
27
+ /*#__PURE__*/ _jsx(DialogHeader, {
28
+ showClose: true,
29
+ title: t('figma:cloudLimitModalHeading')
30
+ }),
31
+ /*#__PURE__*/ _jsx(DialogBody, {
32
+ children: /*#__PURE__*/ _jsx("p", {
33
+ className: `${baseClass}__body`,
34
+ children: t('figma:cloudLimitModalBody')
35
+ })
36
+ }),
37
+ /*#__PURE__*/ _jsx(DialogFooter, {
38
+ children: /*#__PURE__*/ _jsxs("div", {
39
+ className: `${baseClass}__footer`,
40
+ children: [
41
+ collectionLabel && collectionSlug ? /*#__PURE__*/ _jsxs("button", {
42
+ className: `${baseClass}__review-btn`,
43
+ onClick: handleReviewClick,
44
+ type: "button",
45
+ children: [
46
+ /*#__PURE__*/ _jsx("span", {
47
+ className: `${baseClass}__review-btn-label`,
48
+ children: t('figma:cloudLimitModalReviewCollection', {
49
+ collection: collectionLabel
50
+ })
51
+ }),
52
+ /*#__PURE__*/ _jsx(ExternalLinkIcon, {})
53
+ ]
54
+ }) : null,
55
+ /*#__PURE__*/ _jsxs("div", {
56
+ className: `${baseClass}__actions`,
57
+ children: [
58
+ /*#__PURE__*/ _jsx(DialogCancel, {
59
+ label: t('figma:cloudLimitModalConfirm')
60
+ }),
61
+ /*#__PURE__*/ _jsx(Button, {
62
+ buttonStyle: "primary",
63
+ margin: false,
64
+ onClick: ()=>{
65
+ closeModal(cloudLimitDocumentCountModalSlug);
66
+ onRetry();
67
+ },
68
+ size: "medium",
69
+ children: t('figma:cloudLimitModalRetry')
70
+ })
71
+ ]
72
+ })
73
+ ]
74
+ })
75
+ })
76
+ ]
77
+ });
78
+ }
@@ -0,0 +1,51 @@
1
+ .figma-cloud-limit-modal {
2
+ &__body {
3
+ margin: 0;
4
+ }
5
+
6
+ &__footer {
7
+ align-items: center;
8
+ display: flex;
9
+ gap: var(--spacer-4);
10
+ width: 100%;
11
+ }
12
+
13
+ &__review-btn {
14
+ align-items: center;
15
+ background: none;
16
+ border: none;
17
+ color: var(--color-text-brand);
18
+ cursor: pointer;
19
+ display: inline-flex;
20
+ flex-shrink: 1;
21
+ font-size: var(--text-body-medium-font-size);
22
+ gap: var(--spacer-1);
23
+ min-width: 0;
24
+ padding: 0;
25
+
26
+ &:hover {
27
+ color: var(--ramp-blue-700);
28
+ }
29
+
30
+ &:active {
31
+ color: var(--ramp-blue-800);
32
+ }
33
+ }
34
+
35
+ &__review-btn-label {
36
+ overflow: hidden;
37
+ text-overflow: ellipsis;
38
+ white-space: nowrap;
39
+ }
40
+
41
+ // Prevents the icon from shrinking when the label ellipses.
42
+ &__review-btn svg {
43
+ flex-shrink: 0;
44
+ }
45
+
46
+ &__actions {
47
+ display: flex;
48
+ gap: var(--spacer-2);
49
+ margin-inline-start: auto;
50
+ }
51
+ }
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Surfaces Content API cloud-limit errors in the admin UI.
4
+ */
5
+ export declare function FigmaCloudLimitProvider({ children }: {
6
+ children: ReactNode;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,169 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { getTranslation } from '@payloadcms/translations';
4
+ import { FormErrorHandlerContext, toast, useConfig, useFormErrorHandler, useModal, useTranslation } from '@payloadcms/ui';
5
+ import { hasAutosaveEnabled } from 'payload/shared';
6
+ import { useCallback, useEffect, useRef, useState } from 'react';
7
+ import { FigmaCloudLimitDocumentCountModal } from '../CloudLimitModal/index.js';
8
+ import { cloudLimitDocumentCountModalSlug, cloudLimitErrorCodes, cloudLimitToastKeyByCode, getCloudLimitErrorCode } from '../shared.js';
9
+ import { FigmaCloudLimitSlugRegistryContext } from '../SlugRegistryContext/index.js';
10
+ const AUTOSAVE_TOAST_ID = 'figma-cloud-limit-doc-count';
11
+ let isUploadFetchPatched = false;
12
+ function slugFromPathSuffix(suffix) {
13
+ return /^\/collections\/([^/?#]+)/.exec(suffix)?.[1];
14
+ }
15
+ /**
16
+ * Surfaces Content API cloud-limit errors in the admin UI.
17
+ */ export function FigmaCloudLimitProvider({ children }) {
18
+ const { openModal } = useModal();
19
+ const { i18n, t } = useTranslation();
20
+ const { config, getEntityConfig } = useConfig();
21
+ const parentErrorHandler = useFormErrorHandler();
22
+ const adminRouteRef = useRef(config.routes?.admin ?? '/admin');
23
+ const apiRouteRef = useRef(config.routes?.api ?? '/api');
24
+ const [collectionSlug, setCollectionSlug] = useState();
25
+ const [collectionLabel, setCollectionLabel] = useState();
26
+ const retryRef = useRef(undefined);
27
+ // Ref so the handler always calls the latest getEntityConfig without being recreated.
28
+ const getEntityConfigRef = useRef(getEntityConfig);
29
+ getEntityConfigRef.current = getEntityConfig;
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
+ ];
44
+ return ()=>{
45
+ slugStackRef.current = slugStackRef.current.filter((e)=>e.id !== id);
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);
61
+ if (index !== -1) {
62
+ pendingSuppressions.splice(index, 1);
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
+ }
101
+ }
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
+ };
110
+ }, []);
111
+ const handleNonFieldError = useCallback((err, retry)=>{
112
+ const code = getCloudLimitErrorCode({
113
+ errors: [
114
+ err
115
+ ]
116
+ });
117
+ if (!code) {
118
+ return parentErrorHandler?.(err, retry) ?? false;
119
+ }
120
+ if (code === cloudLimitErrorCodes.documentCount) {
121
+ // Prefer the tracker-provided slug (correct even in drawers) over URL parsing.
122
+ const stack = slugStackRef.current;
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);
140
+ return true;
141
+ }
142
+ const messageKey = cloudLimitToastKeyByCode[code];
143
+ if (messageKey) {
144
+ toast.info(t(messageKey));
145
+ return true;
146
+ }
147
+ return false;
148
+ }, [
149
+ i18n,
150
+ openModal,
151
+ parentErrorHandler,
152
+ t
153
+ ]);
154
+ return /*#__PURE__*/ _jsx(FigmaCloudLimitSlugRegistryContext, {
155
+ value: registerCollectionSlug,
156
+ children: /*#__PURE__*/ _jsxs(FormErrorHandlerContext, {
157
+ value: handleNonFieldError,
158
+ children: [
159
+ children,
160
+ /*#__PURE__*/ _jsx(FigmaCloudLimitDocumentCountModal, {
161
+ adminRoute: adminRouteRef.current,
162
+ collectionLabel: collectionLabel,
163
+ collectionSlug: collectionSlug,
164
+ onRetry: ()=>retryRef.current?.()
165
+ })
166
+ ]
167
+ })
168
+ });
169
+ }
@@ -0,0 +1,2 @@
1
+ export type RegisterCollectionSlug = (slug: string | undefined) => () => void;
2
+ export declare const FigmaCloudLimitSlugRegistryContext: import("react").Context<RegisterCollectionSlug>;
@@ -0,0 +1,3 @@
1
+ 'use client';
2
+ import { createContext } from 'react';
3
+ export const FigmaCloudLimitSlugRegistryContext = /*#__PURE__*/ createContext(()=>()=>{});
@@ -0,0 +1,9 @@
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
+ export declare function FigmaCloudLimitSlugTracker(): null;
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { useDocumentInfo } from '@payloadcms/ui';
3
+ import { use, useEffect } from 'react';
4
+ 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() {
13
+ const register = use(FigmaCloudLimitSlugRegistryContext);
14
+ const { collectionSlug } = useDocumentInfo();
15
+ useEffect(()=>{
16
+ const unregister = register(collectionSlug);
17
+ return unregister;
18
+ }, [
19
+ collectionSlug,
20
+ register
21
+ ]);
22
+ return null;
23
+ }
@@ -0,0 +1,2 @@
1
+ /** External link icon (box with arrow pointing out). Used in cloud-limit UI. */
2
+ export declare function ExternalLinkIcon(): import("react").JSX.Element;
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /** External link icon (box with arrow pointing out). Used in cloud-limit UI. */ export function ExternalLinkIcon() {
4
+ return /*#__PURE__*/ _jsxs("svg", {
5
+ "aria-hidden": "true",
6
+ fill: "none",
7
+ height: "14",
8
+ stroke: "currentColor",
9
+ strokeLinecap: "round",
10
+ strokeLinejoin: "round",
11
+ strokeWidth: "2",
12
+ viewBox: "0 0 24 24",
13
+ width: "14",
14
+ xmlns: "http://www.w3.org/2000/svg",
15
+ children: [
16
+ /*#__PURE__*/ _jsx("path", {
17
+ d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
18
+ }),
19
+ /*#__PURE__*/ _jsx("polyline", {
20
+ points: "15 3 21 3 21 9"
21
+ }),
22
+ /*#__PURE__*/ _jsx("line", {
23
+ x1: "10",
24
+ x2: "21",
25
+ y1: "14",
26
+ y2: "3"
27
+ })
28
+ ]
29
+ });
30
+ }
@@ -0,0 +1,10 @@
1
+ export declare const cloudLimitErrorCodes: {
2
+ readonly assetStorage: "cloud_limit.cms_assets_bytes_exceeded";
3
+ readonly documentCount: "cloud_limit.document_count_exceeded";
4
+ readonly documentDataSize: "cloud_limit.per_document_data_bytes_exceeded";
5
+ };
6
+ export type CloudLimitErrorCode = (typeof cloudLimitErrorCodes)[keyof typeof cloudLimitErrorCodes];
7
+ export declare function isCloudLimitErrorCode(code: unknown): code is CloudLimitErrorCode;
8
+ export declare function getCloudLimitErrorCode(json: unknown): CloudLimitErrorCode | undefined;
9
+ export declare const cloudLimitDocumentCountModalSlug = "figma-cloud-limit-document-count";
10
+ export declare const cloudLimitToastKeyByCode: Partial<Record<CloudLimitErrorCode, string>>;
@@ -0,0 +1,27 @@
1
+ export const cloudLimitErrorCodes = {
2
+ assetStorage: 'cloud_limit.cms_assets_bytes_exceeded',
3
+ documentCount: 'cloud_limit.document_count_exceeded',
4
+ documentDataSize: 'cloud_limit.per_document_data_bytes_exceeded'
5
+ };
6
+ const knownCloudLimitCodes = Object.values(cloudLimitErrorCodes);
7
+ export function isCloudLimitErrorCode(code) {
8
+ return typeof code === 'string' && knownCloudLimitCodes.includes(code);
9
+ }
10
+ export function getCloudLimitErrorCode(json) {
11
+ const errors = json?.errors;
12
+ if (!Array.isArray(errors)) {
13
+ return undefined;
14
+ }
15
+ for (const err of errors){
16
+ const code = err?.data?.code;
17
+ if (isCloudLimitErrorCode(code)) {
18
+ return code;
19
+ }
20
+ }
21
+ return undefined;
22
+ }
23
+ export const cloudLimitDocumentCountModalSlug = 'figma-cloud-limit-document-count';
24
+ export const cloudLimitToastKeyByCode = {
25
+ [cloudLimitErrorCodes.assetStorage]: 'figma:cloudLimitAssetStorageError',
26
+ [cloudLimitErrorCodes.documentDataSize]: 'figma:cloudLimitDocumentDataSizeError'
27
+ };
@@ -0,0 +1,12 @@
1
+ export declare const arTranslations: {
2
+ figma: {
3
+ cloudLimitAssetStorageError: string;
4
+ cloudLimitDocumentCountError: string;
5
+ cloudLimitDocumentDataSizeError: string;
6
+ cloudLimitModalBody: string;
7
+ cloudLimitModalConfirm: string;
8
+ cloudLimitModalHeading: string;
9
+ cloudLimitModalRetry: string;
10
+ cloudLimitModalReviewCollection: string;
11
+ };
12
+ };
@@ -0,0 +1,12 @@
1
+ export const arTranslations = {
2
+ figma: {
3
+ cloudLimitAssetStorageError: 'يتعذر التحميل. يرجى تحرير بعض المساحة للمتابعة.',
4
+ cloudLimitDocumentCountError: 'غير قادر على إنشاء وثيقة. تم الوصول إلى الحد الأقصى لسعة التخزين.',
5
+ cloudLimitDocumentDataSizeError: 'تعذر حفظ المستند، النص كبير جدًا.',
6
+ cloudLimitModalBody: 'لقد وصلت إلى الحد الأقصى لعدد الوثائق. يرجى حذف بعض الوثائق الحالية لمواصلة الإنشاء.',
7
+ cloudLimitModalConfirm: 'تم فهم ذلك',
8
+ cloudLimitModalHeading: 'تعذر حفظ المستند',
9
+ cloudLimitModalRetry: 'إعادة المحاولة',
10
+ cloudLimitModalReviewCollection: 'مراجعة {{collection}}'
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ export declare const azTranslations: {
2
+ figma: {
3
+ cloudLimitAssetStorageError: string;
4
+ cloudLimitDocumentCountError: string;
5
+ cloudLimitDocumentDataSizeError: string;
6
+ cloudLimitModalBody: string;
7
+ cloudLimitModalConfirm: string;
8
+ cloudLimitModalHeading: string;
9
+ cloudLimitModalRetry: string;
10
+ cloudLimitModalReviewCollection: string;
11
+ };
12
+ };
@@ -0,0 +1,12 @@
1
+ export const azTranslations = {
2
+ figma: {
3
+ cloudLimitAssetStorageError: 'Yükləmək mümkün deyil. Davam etmək üçün yer boşaldın.',
4
+ cloudLimitDocumentCountError: 'Sənəd yaratmaq mümkün deyil. Saxlama limiti aşılmışdır.',
5
+ cloudLimitDocumentDataSizeError: 'Sənədi saxlamaq mümkün deyil, mətn çox böyükdür.',
6
+ cloudLimitModalBody: 'Sənəd limitinizə çatdınız. Yenilərini yaratmaq üçün mövcud sənədlərdən bəzilərini silin.',
7
+ cloudLimitModalConfirm: 'Başa düşdüm',
8
+ cloudLimitModalHeading: 'Sənədi yadda saxlamaq mümkün olmadı',
9
+ cloudLimitModalRetry: 'Təkrar cəhd edin',
10
+ cloudLimitModalReviewCollection: '{{collection}}-ə baxış'
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ export declare const bgTranslations: {
2
+ figma: {
3
+ cloudLimitAssetStorageError: string;
4
+ cloudLimitDocumentCountError: string;
5
+ cloudLimitDocumentDataSizeError: string;
6
+ cloudLimitModalBody: string;
7
+ cloudLimitModalConfirm: string;
8
+ cloudLimitModalHeading: string;
9
+ cloudLimitModalRetry: string;
10
+ cloudLimitModalReviewCollection: string;
11
+ };
12
+ };
@@ -0,0 +1,12 @@
1
+ export const bgTranslations = {
2
+ figma: {
3
+ cloudLimitAssetStorageError: 'Не може да се качи. Освободете пространство, за да продължите.',
4
+ cloudLimitDocumentCountError: 'Неуспешно създаване на документ. Достигнат е лимитът за съхранение.',
5
+ cloudLimitDocumentDataSizeError: 'Неуспешно записване на документа, текстът е твърде голям.',
6
+ cloudLimitModalBody: 'Достигнали сте лимита на вашите документи. Изтрийте някои съществуващи документи, за да продължите да създавате нови.',
7
+ cloudLimitModalConfirm: 'Разбрах',
8
+ cloudLimitModalHeading: 'Неуспешно запазване на документа',
9
+ cloudLimitModalRetry: 'Опитайте отново.',
10
+ cloudLimitModalReviewCollection: 'Преглед на {{collection}}'
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ export declare const bnBdTranslations: {
2
+ figma: {
3
+ cloudLimitAssetStorageError: string;
4
+ cloudLimitDocumentCountError: string;
5
+ cloudLimitDocumentDataSizeError: string;
6
+ cloudLimitModalBody: string;
7
+ cloudLimitModalConfirm: string;
8
+ cloudLimitModalHeading: string;
9
+ cloudLimitModalRetry: string;
10
+ cloudLimitModalReviewCollection: string;
11
+ };
12
+ };
@@ -0,0 +1,12 @@
1
+ export const bnBdTranslations = {
2
+ figma: {
3
+ cloudLimitAssetStorageError: 'আপলোড করা সম্ভব হচ্ছে না। চালিয়ে যেতে হলে স্থান খালি করুন।',
4
+ cloudLimitDocumentCountError: 'দস্তাবেজ তৈরি করা সম্ভব নয়। সংরক্ষণ সীমা অতিক্রম করা হয়েছে।',
5
+ cloudLimitDocumentDataSizeError: 'ডকুমেন্ট সংরক্ষণ করা সম্ভব নয়, কারণ পাঠ্যটি খুব বড়।',
6
+ cloudLimitModalBody: 'আপনি আপনার document সীমা অতিক্রম করেছেন। নতুনভাবে তৈরি করতে হলে কিছু বিদ্যমান document মুছে ফেলুন।',
7
+ cloudLimitModalConfirm: 'বুঝেছি',
8
+ cloudLimitModalHeading: 'ডকুমেন্ট সংরক্ষণ করা যায়নি',
9
+ cloudLimitModalRetry: 'পুনঃচেষ্টা করুন',
10
+ cloudLimitModalReviewCollection: '{{collection}} পর্যালোচনা করুন'
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ export declare const bnInTranslations: {
2
+ figma: {
3
+ cloudLimitAssetStorageError: string;
4
+ cloudLimitDocumentCountError: string;
5
+ cloudLimitDocumentDataSizeError: string;
6
+ cloudLimitModalBody: string;
7
+ cloudLimitModalConfirm: string;
8
+ cloudLimitModalHeading: string;
9
+ cloudLimitModalRetry: string;
10
+ cloudLimitModalReviewCollection: string;
11
+ };
12
+ };
@@ -0,0 +1,12 @@
1
+ export const bnInTranslations = {
2
+ figma: {
3
+ cloudLimitAssetStorageError: 'আপলোড করা সম্ভব হচ্ছে না। চালিয়ে যেতে হলে স্থান খালি করুন।',
4
+ cloudLimitDocumentCountError: 'ডকুমেন্ট তৈরি করা সম্ভব হয়নি। সংরক্ষণ সীমা অতিক্রম করা হয়েছে।',
5
+ cloudLimitDocumentDataSizeError: 'নথিটি সংরক্ষণ করা সম্ভব নয়, কারণ টেক্সটের আকার অত্যন্ত বড়।',
6
+ cloudLimitModalBody: 'আপনি আপনার document সীমায় পৌঁছেছেন। নতুন তৈরি করতে থাকলে কিছু বিদ্যমান document মুছে ফেলুন।',
7
+ cloudLimitModalConfirm: 'বুঝেছি',
8
+ cloudLimitModalHeading: 'ডকুমেন্ট সংরক্ষণ করা যায়নি',
9
+ cloudLimitModalRetry: 'পুনরায় চেষ্টা করুন',
10
+ cloudLimitModalReviewCollection: '{{collection}} পর্যালোচনা করুন'
11
+ }
12
+ };