kofi-stack-template-generator 2.1.27 → 2.1.36

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 (117) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/dist/index.js +1359 -13
  4. package/package.json +8 -8
  5. package/src/generator.ts +57 -0
  6. package/src/templates.generated.ts +108 -15
  7. package/templates/admin/next.config.ts.hbs +7 -0
  8. package/templates/admin/package.json.hbs +38 -0
  9. package/templates/admin/postcss.config.mjs.hbs +7 -0
  10. package/templates/admin/src/app/analytics/page.tsx.hbs +39 -0
  11. package/templates/admin/src/app/globals.css.hbs +2 -0
  12. package/templates/admin/src/app/layout.tsx.hbs +33 -0
  13. package/templates/admin/src/app/page.tsx.hbs +47 -0
  14. package/templates/admin/src/app/settings/page.tsx.hbs +74 -0
  15. package/templates/admin/src/app/users/page.tsx.hbs +16 -0
  16. package/templates/admin/src/components/admin-sidebar.tsx.hbs +51 -0
  17. package/templates/admin/src/components/stats-cards.tsx.hbs +28 -0
  18. package/templates/admin/src/components/user-table.tsx.hbs +65 -0
  19. package/templates/admin/tsconfig.json.hbs +15 -0
  20. package/templates/design-system/next.config.ts.hbs +7 -0
  21. package/templates/design-system/package.json.hbs +35 -0
  22. package/templates/design-system/postcss.config.mjs.hbs +7 -0
  23. package/templates/design-system/src/app/blocks/page.tsx.hbs +140 -0
  24. package/templates/design-system/src/app/colors/page.tsx.hbs +34 -0
  25. package/templates/design-system/src/app/components/page.tsx.hbs +110 -0
  26. package/templates/design-system/src/app/globals.css.hbs +2 -0
  27. package/templates/design-system/src/app/layout.tsx.hbs +46 -0
  28. package/templates/design-system/src/app/page.tsx.hbs +65 -0
  29. package/templates/design-system/src/app/typography/page.tsx.hbs +112 -0
  30. package/templates/design-system/src/components/color-palette.tsx.hbs +117 -0
  31. package/templates/design-system/tsconfig.json.hbs +13 -0
  32. package/templates/marketing/nextjs/package.json.hbs +1 -1
  33. package/templates/marketing/payload/package.json.hbs +1 -1
  34. package/templates/marketing/payload/src/Footer/config.ts.hbs +178 -0
  35. package/templates/marketing/payload/src/Footer/hooks/revalidateFooter.ts.hbs +13 -0
  36. package/templates/marketing/payload/src/Footer/index.ts.hbs +1 -0
  37. package/templates/marketing/payload/src/Header/RowLabel.tsx.hbs +21 -0
  38. package/templates/marketing/payload/src/Header/config.ts.hbs +208 -0
  39. package/templates/marketing/payload/src/Header/hooks/revalidateHeader.ts.hbs +13 -0
  40. package/templates/marketing/payload/src/Header/index.ts.hbs +1 -0
  41. package/templates/marketing/payload/src/access/anyone.ts.hbs +3 -0
  42. package/templates/marketing/payload/src/access/authenticated.ts.hbs +9 -0
  43. package/templates/marketing/payload/src/access/authenticatedOrPublished.ts.hbs +13 -0
  44. package/templates/marketing/payload/src/access/index.ts.hbs +3 -0
  45. package/templates/marketing/payload/src/app/(frontend)/next/seed/route.ts.hbs +31 -0
  46. package/templates/marketing/payload/src/collections/Categories/index.ts.hbs +28 -0
  47. package/templates/marketing/payload/src/collections/FAQs/index.ts.hbs +100 -0
  48. package/templates/marketing/payload/src/collections/Media.ts.hbs +148 -28
  49. package/templates/marketing/payload/src/collections/Pages/hooks/revalidatePage.ts.hbs +43 -0
  50. package/templates/marketing/payload/src/collections/Pages/index.ts.hbs +142 -0
  51. package/templates/marketing/payload/src/collections/Posts/hooks/populateAuthors.ts.hbs +41 -0
  52. package/templates/marketing/payload/src/collections/Posts/hooks/revalidatePost.ts.hbs +44 -0
  53. package/templates/marketing/payload/src/collections/Posts/index.ts.hbs +244 -0
  54. package/templates/marketing/payload/src/collections/Users/index.ts.hbs +26 -0
  55. package/templates/marketing/payload/src/collections/index.ts.hbs +6 -4
  56. package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.scss.hbs +12 -0
  57. package/templates/marketing/payload/src/components/BeforeDashboard/SeedButton/index.tsx.hbs +89 -0
  58. package/templates/marketing/payload/src/components/BeforeDashboard/index.scss.hbs +24 -0
  59. package/templates/marketing/payload/src/components/BeforeDashboard/index.tsx.hbs +69 -0
  60. package/templates/marketing/payload/src/components/BeforeLogin/index.tsx.hbs +14 -0
  61. package/templates/marketing/payload/src/components/Link/index.tsx.hbs +79 -0
  62. package/templates/marketing/payload/src/components/Media/index.tsx.hbs +67 -0
  63. package/templates/marketing/payload/src/components/RichText/index.tsx.hbs +44 -0
  64. package/templates/marketing/payload/src/endpoints/seed/home.ts.hbs +76 -0
  65. package/templates/marketing/payload/src/endpoints/seed/image-1.ts.hbs +5 -0
  66. package/templates/marketing/payload/src/endpoints/seed/image-2.ts.hbs +5 -0
  67. package/templates/marketing/payload/src/endpoints/seed/image-hero.ts.hbs +5 -0
  68. package/templates/marketing/payload/src/endpoints/seed/index.ts.hbs +235 -0
  69. package/templates/marketing/payload/src/endpoints/seed/post-1.ts.hbs +252 -0
  70. package/templates/marketing/payload/src/fields/defaultLexical.ts.hbs +73 -0
  71. package/templates/marketing/payload/src/fields/index.ts.hbs +3 -0
  72. package/templates/marketing/payload/src/fields/link.ts.hbs +139 -0
  73. package/templates/marketing/payload/src/fields/linkGroup.ts.hbs +28 -0
  74. package/templates/marketing/payload/src/globals/index.ts.hbs +2 -2
  75. package/templates/marketing/payload/src/heros/HighImpact/index.tsx.hbs +53 -0
  76. package/templates/marketing/payload/src/heros/LowImpact/index.tsx.hbs +48 -0
  77. package/templates/marketing/payload/src/heros/MediumImpact/index.tsx.hbs +46 -0
  78. package/templates/marketing/payload/src/heros/PostHero/index.tsx.hbs +68 -0
  79. package/templates/marketing/payload/src/heros/ProductShowcase/index.tsx.hbs +88 -0
  80. package/templates/marketing/payload/src/heros/RenderHero.tsx.hbs +27 -0
  81. package/templates/marketing/payload/src/heros/config.ts.hbs +112 -0
  82. package/templates/marketing/payload/src/heros/index.ts.hbs +7 -0
  83. package/templates/marketing/payload/src/hooks/index.ts.hbs +2 -0
  84. package/templates/marketing/payload/src/hooks/populatePublishedAt.ts.hbs +15 -0
  85. package/templates/marketing/payload/src/hooks/revalidateRedirects.ts.hbs +11 -0
  86. package/templates/marketing/payload/src/payload.config.ts.hbs +32 -8
  87. package/templates/marketing/payload/src/providers/HeaderTheme/index.tsx.hbs +34 -0
  88. package/templates/marketing/payload/src/providers/Theme/InitTheme/index.tsx.hbs +44 -0
  89. package/templates/marketing/payload/src/providers/Theme/index.tsx.hbs +60 -0
  90. package/templates/marketing/payload/src/providers/Theme/shared.ts.hbs +17 -0
  91. package/templates/marketing/payload/src/providers/Theme/types.ts.hbs +10 -0
  92. package/templates/marketing/payload/src/providers/index.tsx.hbs +18 -0
  93. package/templates/marketing/payload/src/utilities/canUseDOM.ts.hbs +1 -0
  94. package/templates/marketing/payload/src/utilities/deepMerge.ts.hbs +35 -0
  95. package/templates/marketing/payload/src/utilities/formatAuthors.ts.hbs +24 -0
  96. package/templates/marketing/payload/src/utilities/formatDateTime.ts.hbs +13 -0
  97. package/templates/marketing/payload/src/utilities/generateMeta.ts.hbs +87 -0
  98. package/templates/marketing/payload/src/utilities/generatePreviewPath.ts.hbs +33 -0
  99. package/templates/marketing/payload/src/utilities/getURL.ts.hbs +26 -0
  100. package/templates/marketing/payload/src/utilities/index.ts.hbs +8 -0
  101. package/templates/marketing/payload/src/utilities/mergeOpenGraph.ts.hbs +26 -0
  102. package/templates/mobile/app.json.hbs +39 -0
  103. package/templates/mobile/babel.config.js.hbs +6 -0
  104. package/templates/mobile/package.json.hbs +30 -0
  105. package/templates/mobile/src/app/(tabs)/_layout.tsx.hbs +27 -0
  106. package/templates/mobile/src/app/(tabs)/index.tsx.hbs +28 -0
  107. package/templates/mobile/src/app/(tabs)/profile.tsx.hbs +44 -0
  108. package/templates/mobile/src/app/_layout.tsx.hbs +13 -0
  109. package/templates/mobile/src/app/index.tsx.hbs +5 -0
  110. package/templates/mobile/tsconfig.json.hbs +10 -0
  111. package/templates/monorepo/package.json.hbs +4 -1
  112. package/templates/web/package.json.hbs +1 -1
  113. package/templates/marketing/payload/src/collections/Pages.ts.hbs +0 -66
  114. package/templates/marketing/payload/src/collections/Posts.ts.hbs +0 -65
  115. package/templates/marketing/payload/src/collections/Users.ts.hbs +0 -25
  116. package/templates/marketing/payload/src/globals/Navigation.ts.hbs +0 -51
  117. package/templates/marketing/payload/src/globals/SiteSettings.ts.hbs +0 -49
@@ -0,0 +1,13 @@
1
+ import type { GlobalAfterChangeHook } from "payload"
2
+
3
+ import { revalidateTag } from "next/cache"
4
+
5
+ export const revalidateFooter: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
6
+ if (!context.disableRevalidate) {
7
+ payload.logger.info("Revalidating footer")
8
+
9
+ revalidateTag("global_footer")
10
+ }
11
+
12
+ return doc
13
+ }
@@ -0,0 +1 @@
1
+ export { Footer } from "./config"
@@ -0,0 +1,21 @@
1
+ "use client"
2
+ import type { Header } from "@/payload-types"
3
+ import { type RowLabelProps, useRowLabel } from "@payloadcms/ui"
4
+
5
+ export const RowLabel: React.FC<RowLabelProps> = () => {
6
+ const data = useRowLabel<NonNullable<Header["navItems"]>[number]>()
7
+
8
+ const itemLabel = data?.data?.label
9
+ const itemType = data?.data?.type === "megaMenu" ? "Mega Menu" : "Link"
10
+ const isRight = (data?.data?.position || "left") === "right"
11
+ const itemPosition = isRight ? "Right" : "Left"
12
+ const itemAppearance = isRight
13
+ ? ` · ${(data?.data?.appearance || "button") === "button" ? "Button" : "Link"}`
14
+ : ""
15
+
16
+ const label = itemLabel
17
+ ? `${data.rowNumber !== undefined ? data.rowNumber + 1 : ""}. ${itemLabel} — ${itemType} (${itemPosition}${itemAppearance})`
18
+ : "Row"
19
+
20
+ return <div>{label}</div>
21
+ }
@@ -0,0 +1,208 @@
1
+ import type { GlobalConfig } from "payload"
2
+
3
+ import { link } from "@/fields/link"
4
+ import { revalidateHeader } from "./hooks/revalidateHeader"
5
+
6
+ export const Header: GlobalConfig = {
7
+ slug: "header",
8
+ access: {
9
+ read: () => true,
10
+ },
11
+ fields: [
12
+ {
13
+ name: "navItems",
14
+ type: "array",
15
+ maxRows: 8,
16
+ admin: {
17
+ initCollapsed: true,
18
+ components: {
19
+ RowLabel: "@/Header/RowLabel#RowLabel",
20
+ },
21
+ },
22
+ fields: [
23
+ {
24
+ name: "type",
25
+ type: "select",
26
+ defaultValue: "link",
27
+ options: [
28
+ { label: "Simple Link", value: "link" },
29
+ { label: "Mega Menu", value: "megaMenu" },
30
+ ],
31
+ admin: {
32
+ description: "Choose between a simple link or a mega menu dropdown",
33
+ },
34
+ },
35
+ {
36
+ name: "label",
37
+ type: "text",
38
+ required: true,
39
+ label: "Nav Item Label",
40
+ },
41
+ {
42
+ name: "position",
43
+ type: "select",
44
+ defaultValue: "left",
45
+ options: [
46
+ { label: "Left (Near Logo)", value: "left" },
47
+ { label: "Right (CTA Section)", value: "right" },
48
+ ],
49
+ admin: {
50
+ description: "Control where this nav item appears in the header",
51
+ },
52
+ },
53
+ {
54
+ name: "appearance",
55
+ type: "select",
56
+ defaultValue: "button",
57
+ options: [
58
+ { label: "Button", value: "button" },
59
+ { label: "Link", value: "link" },
60
+ ],
61
+ admin: {
62
+ description: "How this item should be displayed",
63
+ condition: (_, siblingData) => siblingData?.position === "right",
64
+ },
65
+ },
66
+ // Simple link fields
67
+ link({
68
+ appearances: false,
69
+ disableLabel: true,
70
+ overrides: {
71
+ admin: {
72
+ condition: (_, siblingData) => siblingData?.type === "link",
73
+ },
74
+ },
75
+ }),
76
+ // Mega menu fields
77
+ {
78
+ name: "megaMenuColumns",
79
+ type: "array",
80
+ label: "Menu Columns",
81
+ maxRows: 4,
82
+ admin: {
83
+ condition: (_, siblingData) => siblingData?.type === "megaMenu",
84
+ },
85
+ fields: [
86
+ {
87
+ name: "columnLabel",
88
+ type: "text",
89
+ label: "Column Heading",
90
+ },
91
+ {
92
+ name: "columnDescription",
93
+ type: "textarea",
94
+ label: "Column Description",
95
+ admin: {
96
+ description: "Optional description shown below the column heading",
97
+ },
98
+ },
99
+ {
100
+ name: "items",
101
+ type: "array",
102
+ label: "Menu Items",
103
+ maxRows: 8,
104
+ fields: [
105
+ {
106
+ name: "label",
107
+ type: "text",
108
+ required: true,
109
+ label: "Item Label",
110
+ },
111
+ {
112
+ name: "description",
113
+ type: "text",
114
+ label: "Item Description",
115
+ admin: {
116
+ description: "Short description shown below the label",
117
+ },
118
+ },
119
+ {
120
+ name: "icon",
121
+ type: "select",
122
+ label: "Icon",
123
+ options: [
124
+ { label: "None", value: "none" },
125
+ { label: "Layout", value: "layout" },
126
+ { label: "Dollar Sign", value: "dollarSign" },
127
+ { label: "Search", value: "search" },
128
+ { label: "Settings", value: "settings" },
129
+ { label: "Zap", value: "zap" },
130
+ { label: "Layers", value: "layers" },
131
+ { label: "Users", value: "users" },
132
+ { label: "Building", value: "building" },
133
+ { label: "Globe", value: "globe" },
134
+ { label: "Store", value: "store" },
135
+ { label: "Rocket", value: "rocket" },
136
+ { label: "Target", value: "target" },
137
+ { label: "BarChart", value: "barChart" },
138
+ { label: "Shield", value: "shield" },
139
+ { label: "Database", value: "database" },
140
+ ],
141
+ defaultValue: "none",
142
+ },
143
+ link({
144
+ appearances: false,
145
+ disableLabel: true,
146
+ }),
147
+ ],
148
+ },
149
+ ],
150
+ },
151
+ // Featured section for mega menu
152
+ {
153
+ name: "featuredItem",
154
+ type: "group",
155
+ label: "Featured Section",
156
+ admin: {
157
+ condition: (_, siblingData) => siblingData?.type === "megaMenu",
158
+ description: "Optional featured content shown in the mega menu",
159
+ },
160
+ fields: [
161
+ {
162
+ name: "enabled",
163
+ type: "checkbox",
164
+ label: "Show Featured Section",
165
+ defaultValue: false,
166
+ },
167
+ {
168
+ name: "heading",
169
+ type: "text",
170
+ label: "Featured Heading",
171
+ admin: {
172
+ condition: (_, siblingData) => siblingData?.enabled,
173
+ },
174
+ },
175
+ {
176
+ name: "description",
177
+ type: "textarea",
178
+ label: "Featured Description",
179
+ admin: {
180
+ condition: (_, siblingData) => siblingData?.enabled,
181
+ },
182
+ },
183
+ {
184
+ name: "image",
185
+ type: "upload",
186
+ relationTo: "media",
187
+ label: "Featured Image",
188
+ admin: {
189
+ condition: (_, siblingData) => siblingData?.enabled,
190
+ },
191
+ },
192
+ link({
193
+ appearances: false,
194
+ overrides: {
195
+ admin: {
196
+ condition: (_, siblingData) => siblingData?.enabled,
197
+ },
198
+ },
199
+ }),
200
+ ],
201
+ },
202
+ ],
203
+ },
204
+ ],
205
+ hooks: {
206
+ afterChange: [revalidateHeader],
207
+ },
208
+ }
@@ -0,0 +1,13 @@
1
+ import type { GlobalAfterChangeHook } from "payload"
2
+
3
+ import { revalidateTag } from "next/cache"
4
+
5
+ export const revalidateHeader: GlobalAfterChangeHook = ({ doc, req: { payload, context } }) => {
6
+ if (!context.disableRevalidate) {
7
+ payload.logger.info("Revalidating header")
8
+
9
+ revalidateTag("global_header")
10
+ }
11
+
12
+ return doc
13
+ }
@@ -0,0 +1 @@
1
+ export { Header } from "./config"
@@ -0,0 +1,3 @@
1
+ import type { Access } from "payload"
2
+
3
+ export const anyone: Access = () => true
@@ -0,0 +1,9 @@
1
+ import type { AccessArgs } from "payload"
2
+
3
+ import type { User } from "@/payload-types"
4
+
5
+ type isAuthenticated = (args: AccessArgs<User>) => boolean
6
+
7
+ export const authenticated: isAuthenticated = ({ req: { user } }) => {
8
+ return Boolean(user)
9
+ }
@@ -0,0 +1,13 @@
1
+ import type { Access } from "payload"
2
+
3
+ export const authenticatedOrPublished: Access = ({ req: { user } }) => {
4
+ if (user) {
5
+ return true
6
+ }
7
+
8
+ return {
9
+ _status: {
10
+ equals: "published",
11
+ },
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ export { anyone } from "./anyone"
2
+ export { authenticated } from "./authenticated"
3
+ export { authenticatedOrPublished } from "./authenticatedOrPublished"
@@ -0,0 +1,31 @@
1
+ import { seed } from "@/endpoints/seed"
2
+ import config from "@payload-config"
3
+ import { headers } from "next/headers"
4
+ import { createLocalReq, getPayload } from "payload"
5
+
6
+ export const maxDuration = 60 // This function can run for a maximum of 60 seconds
7
+
8
+ export async function POST(): Promise<Response> {
9
+ const payload = await getPayload({ config })
10
+ const requestHeaders = await headers()
11
+
12
+ // Authenticate by passing request headers
13
+ const { user } = await payload.auth({ headers: requestHeaders })
14
+
15
+ if (!user) {
16
+ return new Response("Action forbidden.", { status: 403 })
17
+ }
18
+
19
+ try {
20
+ // Create a Payload request object to pass to the Local API for transactions
21
+ // At this point you should pass in a user, locale, and any other context you need for the Local API
22
+ const payloadReq = await createLocalReq({ user }, payload)
23
+
24
+ await seed({ payload, req: payloadReq })
25
+
26
+ return Response.json({ success: true })
27
+ } catch (e) {
28
+ payload.logger.error({ err: e, message: "Error seeding data" })
29
+ return new Response("Error seeding data.", { status: 500 })
30
+ }
31
+ }
@@ -0,0 +1,28 @@
1
+ import type { CollectionConfig } from "payload"
2
+
3
+ import { slugField } from "payload"
4
+ import { anyone } from "../../access/anyone"
5
+ import { authenticated } from "../../access/authenticated"
6
+
7
+ export const Categories: CollectionConfig = {
8
+ slug: "categories",
9
+ access: {
10
+ create: authenticated,
11
+ delete: authenticated,
12
+ read: anyone,
13
+ update: authenticated,
14
+ },
15
+ admin: {
16
+ useAsTitle: "title",
17
+ },
18
+ fields: [
19
+ {
20
+ name: "title",
21
+ type: "text",
22
+ required: true,
23
+ },
24
+ slugField({
25
+ position: undefined,
26
+ }),
27
+ ],
28
+ }
@@ -0,0 +1,100 @@
1
+ import type { CollectionConfig } from "payload"
2
+
3
+ import {
4
+ AlignFeature,
5
+ BlockquoteFeature,
6
+ ChecklistFeature,
7
+ EXPERIMENTAL_TableFeature,
8
+ FixedToolbarFeature,
9
+ IndentFeature,
10
+ InlineCodeFeature,
11
+ InlineToolbarFeature,
12
+ OrderedListFeature,
13
+ RelationshipFeature,
14
+ StrikethroughFeature,
15
+ SubscriptFeature,
16
+ SuperscriptFeature,
17
+ UnorderedListFeature,
18
+ UploadFeature,
19
+ lexicalEditor,
20
+ } from "@payloadcms/richtext-lexical"
21
+ import { anyone } from "../../access/anyone"
22
+ import { authenticated } from "../../access/authenticated"
23
+
24
+ export const FAQs: CollectionConfig = {
25
+ slug: "faqs",
26
+ access: {
27
+ create: authenticated,
28
+ delete: authenticated,
29
+ read: anyone,
30
+ update: authenticated,
31
+ },
32
+ admin: {
33
+ useAsTitle: "question",
34
+ defaultColumns: ["question", "category", "order", "updatedAt"],
35
+ group: "Content",
36
+ },
37
+ fields: [
38
+ {
39
+ name: "question",
40
+ type: "text",
41
+ required: true,
42
+ label: "Question",
43
+ },
44
+ {
45
+ name: "answer",
46
+ type: "richText",
47
+ required: true,
48
+ label: "Answer",
49
+ editor: lexicalEditor({
50
+ features: ({ rootFeatures }) => {
51
+ return [
52
+ ...rootFeatures,
53
+ FixedToolbarFeature(),
54
+ InlineToolbarFeature(),
55
+ StrikethroughFeature(),
56
+ SubscriptFeature(),
57
+ SuperscriptFeature(),
58
+ InlineCodeFeature(),
59
+ BlockquoteFeature(),
60
+ UnorderedListFeature(),
61
+ OrderedListFeature(),
62
+ ChecklistFeature(),
63
+ AlignFeature(),
64
+ IndentFeature(),
65
+ RelationshipFeature(),
66
+ UploadFeature(),
67
+ EXPERIMENTAL_TableFeature(),
68
+ ]
69
+ },
70
+ }),
71
+ },
72
+ {
73
+ name: "category",
74
+ type: "select",
75
+ required: true,
76
+ defaultValue: "general",
77
+ options: [
78
+ { label: "General", value: "general" },
79
+ { label: "Pricing", value: "pricing" },
80
+ { label: "Features", value: "features" },
81
+ { label: "Getting Started", value: "getting-started" },
82
+ { label: "Technical", value: "technical" },
83
+ { label: "Support", value: "support" },
84
+ ],
85
+ admin: {
86
+ position: "sidebar",
87
+ },
88
+ },
89
+ {
90
+ name: "order",
91
+ type: "number",
92
+ defaultValue: 0,
93
+ admin: {
94
+ position: "sidebar",
95
+ description: "Lower numbers appear first",
96
+ },
97
+ },
98
+ ],
99
+ defaultSort: "order",
100
+ }
@@ -1,44 +1,164 @@
1
- import type { CollectionConfig } from 'payload'
1
+ import type { CollectionConfig } from "payload"
2
+
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+ import {
6
+ AlignFeature,
7
+ BlockquoteFeature,
8
+ ChecklistFeature,
9
+ EXPERIMENTAL_TableFeature,
10
+ FixedToolbarFeature,
11
+ IndentFeature,
12
+ InlineCodeFeature,
13
+ InlineToolbarFeature,
14
+ OrderedListFeature,
15
+ RelationshipFeature,
16
+ StrikethroughFeature,
17
+ SubscriptFeature,
18
+ SuperscriptFeature,
19
+ UnorderedListFeature,
20
+ UploadFeature,
21
+ lexicalEditor,
22
+ } from "@payloadcms/richtext-lexical"
23
+
24
+ import { anyone } from "../access/anyone"
25
+ import { authenticated } from "../access/authenticated"
26
+
27
+ const filename = fileURLToPath(import.meta.url)
28
+ const dirname = path.dirname(filename)
29
+
30
+ {{#if (eq integrations.payloadStorage 'local')}}
31
+ // Local storage configuration
32
+ const useLocalStorage = true
33
+ {{else}}
34
+ // Only use local staticDir when storage token is not set (local development)
35
+ // In production, cloud storage handles file storage
36
+ const useLocalStorage = !process.env.BLOB_READ_WRITE_TOKEN && !process.env.S3_BUCKET && !process.env.R2_BUCKET && !process.env.GCS_BUCKET
37
+ {{/if}}
2
38
 
3
39
  export const Media: CollectionConfig = {
4
- slug: 'media',
40
+ slug: "media",
41
+ folders: true,
5
42
  access: {
6
- read: () => true,
43
+ create: authenticated,
44
+ delete: authenticated,
45
+ read: anyone,
46
+ update: authenticated,
7
47
  },
48
+ fields: [
49
+ {
50
+ name: "alt",
51
+ type: "text",
52
+ },
53
+ {
54
+ name: "caption",
55
+ type: "richText",
56
+ editor: lexicalEditor({
57
+ features: ({ rootFeatures }) => {
58
+ return [
59
+ ...rootFeatures,
60
+ FixedToolbarFeature(),
61
+ InlineToolbarFeature(),
62
+ StrikethroughFeature(),
63
+ SubscriptFeature(),
64
+ SuperscriptFeature(),
65
+ InlineCodeFeature(),
66
+ BlockquoteFeature(),
67
+ UnorderedListFeature(),
68
+ OrderedListFeature(),
69
+ ChecklistFeature(),
70
+ AlignFeature(),
71
+ IndentFeature(),
72
+ RelationshipFeature(),
73
+ UploadFeature(),
74
+ EXPERIMENTAL_TableFeature(),
75
+ ]
76
+ },
77
+ }),
78
+ },
79
+ ],
8
80
  upload: {
9
- staticDir: 'media',
81
+ // Only use local staticDir for development; production uses cloud storage
82
+ ...(useLocalStorage && {
83
+ staticDir: path.resolve(dirname, "../../public/media"),
84
+ }),
85
+ adminThumbnail: "thumbnail",
86
+ focalPoint: true,
87
+ // Allow common image and video file types
88
+ mimeTypes: [
89
+ "image/*",
90
+ "video/mp4",
91
+ "video/webm",
92
+ "video/quicktime", // .mov files
93
+ ],
94
+ // Convert original uploaded image to WebP format for better compression
95
+ // Note: formatOptions only applies to images, videos are uploaded as-is
96
+ formatOptions: {
97
+ format: "webp",
98
+ options: {
99
+ quality: 85,
100
+ },
101
+ },
10
102
  imageSizes: [
11
103
  {
12
- name: 'thumbnail',
13
- width: 400,
14
- height: 300,
15
- position: 'centre',
104
+ name: "thumbnail",
105
+ width: 300,
106
+ formatOptions: {
107
+ format: "webp",
108
+ options: { quality: 80 },
109
+ },
16
110
  },
17
111
  {
18
- name: 'card',
19
- width: 768,
20
- height: 432,
21
- position: 'centre',
112
+ name: "square",
113
+ width: 500,
114
+ height: 500,
115
+ formatOptions: {
116
+ format: "webp",
117
+ options: { quality: 80 },
118
+ },
22
119
  },
23
120
  {
24
- name: 'hero',
121
+ name: "small",
122
+ width: 600,
123
+ formatOptions: {
124
+ format: "webp",
125
+ options: { quality: 80 },
126
+ },
127
+ },
128
+ {
129
+ name: "medium",
130
+ width: 900,
131
+ formatOptions: {
132
+ format: "webp",
133
+ options: { quality: 80 },
134
+ },
135
+ },
136
+ {
137
+ name: "large",
138
+ width: 1400,
139
+ formatOptions: {
140
+ format: "webp",
141
+ options: { quality: 80 },
142
+ },
143
+ },
144
+ {
145
+ name: "xlarge",
25
146
  width: 1920,
26
- height: 1080,
27
- position: 'centre',
147
+ formatOptions: {
148
+ format: "webp",
149
+ options: { quality: 80 },
150
+ },
151
+ },
152
+ {
153
+ name: "og",
154
+ width: 1200,
155
+ height: 630,
156
+ crop: "center",
157
+ formatOptions: {
158
+ format: "webp",
159
+ options: { quality: 85 },
160
+ },
28
161
  },
29
162
  ],
30
- adminThumbnail: 'thumbnail',
31
- mimeTypes: ['image/*'],
32
163
  },
33
- fields: [
34
- {
35
- name: 'alt',
36
- type: 'text',
37
- required: true,
38
- },
39
- {
40
- name: 'caption',
41
- type: 'text',
42
- },
43
- ],
44
164
  }