pxlr-cms 1.0.0

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 (153) hide show
  1. package/README.md +160 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +264 -0
  4. package/package.json +51 -0
  5. package/templates/blog/frontend/app/blog/[slug]/page.tsx +175 -0
  6. package/templates/blog/frontend/app/blog/page.tsx +102 -0
  7. package/templates/blog/frontend/app/components/footer.tsx +21 -0
  8. package/templates/blog/frontend/app/components/header.tsx +45 -0
  9. package/templates/blog/frontend/app/globals.css +30 -0
  10. package/templates/blog/frontend/app/layout.tsx +38 -0
  11. package/templates/blog/frontend/app/lib/cms.ts +71 -0
  12. package/templates/blog/frontend/app/page.tsx +155 -0
  13. package/templates/blog/frontend/next.config.ts +16 -0
  14. package/templates/blog/frontend/package.json +24 -0
  15. package/templates/blog/frontend/postcss.config.mjs +7 -0
  16. package/templates/blog/frontend/tsconfig.json +23 -0
  17. package/templates/blog/pxlr-cms/README.md +188 -0
  18. package/templates/blog/pxlr-cms/docker-compose.yml +132 -0
  19. package/templates/blog/pxlr-cms/nginx/nginx.conf +107 -0
  20. package/templates/blog/pxlr-cms/packages/admin/.dockerignore +4 -0
  21. package/templates/blog/pxlr-cms/packages/admin/.env.example +2 -0
  22. package/templates/blog/pxlr-cms/packages/admin/Dockerfile +19 -0
  23. package/templates/blog/pxlr-cms/packages/admin/next-env.d.ts +6 -0
  24. package/templates/blog/pxlr-cms/packages/admin/next.config.ts +22 -0
  25. package/templates/blog/pxlr-cms/packages/admin/package.json +63 -0
  26. package/templates/blog/pxlr-cms/packages/admin/pnpm-lock.yaml +5748 -0
  27. package/templates/blog/pxlr-cms/packages/admin/postcss.config.mjs +9 -0
  28. package/templates/blog/pxlr-cms/packages/admin/src/app/content/[id]/page.tsx +503 -0
  29. package/templates/blog/pxlr-cms/packages/admin/src/app/content/layout.tsx +7 -0
  30. package/templates/blog/pxlr-cms/packages/admin/src/app/content/new/page.tsx +424 -0
  31. package/templates/blog/pxlr-cms/packages/admin/src/app/content/page.tsx +191 -0
  32. package/templates/blog/pxlr-cms/packages/admin/src/app/globals.css +132 -0
  33. package/templates/blog/pxlr-cms/packages/admin/src/app/layout.tsx +25 -0
  34. package/templates/blog/pxlr-cms/packages/admin/src/app/login/page.tsx +119 -0
  35. package/templates/blog/pxlr-cms/packages/admin/src/app/media/layout.tsx +7 -0
  36. package/templates/blog/pxlr-cms/packages/admin/src/app/media/page.tsx +362 -0
  37. package/templates/blog/pxlr-cms/packages/admin/src/app/page.tsx +184 -0
  38. package/templates/blog/pxlr-cms/packages/admin/src/app/profile/layout.tsx +7 -0
  39. package/templates/blog/pxlr-cms/packages/admin/src/app/profile/page.tsx +206 -0
  40. package/templates/blog/pxlr-cms/packages/admin/src/app/schemas/[name]/page.tsx +312 -0
  41. package/templates/blog/pxlr-cms/packages/admin/src/app/schemas/layout.tsx +7 -0
  42. package/templates/blog/pxlr-cms/packages/admin/src/app/schemas/page.tsx +210 -0
  43. package/templates/blog/pxlr-cms/packages/admin/src/app/settings/layout.tsx +7 -0
  44. package/templates/blog/pxlr-cms/packages/admin/src/app/settings/page.tsx +178 -0
  45. package/templates/blog/pxlr-cms/packages/admin/src/components/editor/media-picker.tsx +202 -0
  46. package/templates/blog/pxlr-cms/packages/admin/src/components/editor/rich-text-editor.tsx +387 -0
  47. package/templates/blog/pxlr-cms/packages/admin/src/components/layout/auth-layout.tsx +43 -0
  48. package/templates/blog/pxlr-cms/packages/admin/src/components/layout/header.tsx +79 -0
  49. package/templates/blog/pxlr-cms/packages/admin/src/components/layout/sidebar.tsx +68 -0
  50. package/templates/blog/pxlr-cms/packages/admin/src/components/providers.tsx +29 -0
  51. package/templates/blog/pxlr-cms/packages/admin/src/components/schema-code-generator.tsx +326 -0
  52. package/templates/blog/pxlr-cms/packages/admin/src/components/ui/avatar.tsx +49 -0
  53. package/templates/blog/pxlr-cms/packages/admin/src/components/ui/button.tsx +55 -0
  54. package/templates/blog/pxlr-cms/packages/admin/src/components/ui/dropdown-menu.tsx +194 -0
  55. package/templates/blog/pxlr-cms/packages/admin/src/components/ui/input.tsx +24 -0
  56. package/templates/blog/pxlr-cms/packages/admin/src/components/ui/label.tsx +25 -0
  57. package/templates/blog/pxlr-cms/packages/admin/src/components/ui/toast.tsx +127 -0
  58. package/templates/blog/pxlr-cms/packages/admin/src/components/ui/toaster.tsx +35 -0
  59. package/templates/blog/pxlr-cms/packages/admin/src/components/ui/use-toast.ts +187 -0
  60. package/templates/blog/pxlr-cms/packages/admin/src/lib/api.ts +96 -0
  61. package/templates/blog/pxlr-cms/packages/admin/src/lib/i18n/context.tsx +60 -0
  62. package/templates/blog/pxlr-cms/packages/admin/src/lib/i18n/translations.ts +317 -0
  63. package/templates/blog/pxlr-cms/packages/admin/src/lib/store/auth.ts +51 -0
  64. package/templates/blog/pxlr-cms/packages/admin/src/lib/utils.ts +29 -0
  65. package/templates/blog/pxlr-cms/packages/admin/tailwind.config.ts +57 -0
  66. package/templates/blog/pxlr-cms/packages/admin/tsconfig.json +27 -0
  67. package/templates/blog/pxlr-cms/packages/api/.env.example +23 -0
  68. package/templates/blog/pxlr-cms/packages/api/Dockerfile +26 -0
  69. package/templates/blog/pxlr-cms/packages/api/package.json +42 -0
  70. package/templates/blog/pxlr-cms/packages/api/src/config.ts +39 -0
  71. package/templates/blog/pxlr-cms/packages/api/src/database/index.ts +60 -0
  72. package/templates/blog/pxlr-cms/packages/api/src/database/init.sql +258 -0
  73. package/templates/blog/pxlr-cms/packages/api/src/database/redis.ts +95 -0
  74. package/templates/blog/pxlr-cms/packages/api/src/database/seed.sql +78 -0
  75. package/templates/blog/pxlr-cms/packages/api/src/index.ts +157 -0
  76. package/templates/blog/pxlr-cms/packages/api/src/modules/auth/routes.ts +256 -0
  77. package/templates/blog/pxlr-cms/packages/api/src/modules/content/routes.ts +385 -0
  78. package/templates/blog/pxlr-cms/packages/api/src/modules/media/routes.ts +312 -0
  79. package/templates/blog/pxlr-cms/packages/api/src/modules/realtime/handler.ts +228 -0
  80. package/templates/blog/pxlr-cms/packages/api/src/modules/schema/routes.ts +284 -0
  81. package/templates/blog/pxlr-cms/packages/api/src/modules/versions/routes.ts +70 -0
  82. package/templates/blog/pxlr-cms/packages/api/tsconfig.json +24 -0
  83. package/templates/blog/pxlr-cms/packages/shared/package.json +14 -0
  84. package/templates/blog/pxlr-cms/packages/shared/src/types/index.ts +139 -0
  85. package/templates/blog/pxlr-cms/packages/shared/tsconfig.json +18 -0
  86. package/templates/clean/pxlr-cms/README.md +188 -0
  87. package/templates/clean/pxlr-cms/docker-compose.yml +132 -0
  88. package/templates/clean/pxlr-cms/nginx/nginx.conf +107 -0
  89. package/templates/clean/pxlr-cms/packages/admin/.dockerignore +4 -0
  90. package/templates/clean/pxlr-cms/packages/admin/.env.example +2 -0
  91. package/templates/clean/pxlr-cms/packages/admin/Dockerfile +19 -0
  92. package/templates/clean/pxlr-cms/packages/admin/next-env.d.ts +6 -0
  93. package/templates/clean/pxlr-cms/packages/admin/next.config.ts +22 -0
  94. package/templates/clean/pxlr-cms/packages/admin/package.json +63 -0
  95. package/templates/clean/pxlr-cms/packages/admin/pnpm-lock.yaml +5748 -0
  96. package/templates/clean/pxlr-cms/packages/admin/postcss.config.mjs +9 -0
  97. package/templates/clean/pxlr-cms/packages/admin/src/app/content/[id]/page.tsx +503 -0
  98. package/templates/clean/pxlr-cms/packages/admin/src/app/content/layout.tsx +7 -0
  99. package/templates/clean/pxlr-cms/packages/admin/src/app/content/new/page.tsx +424 -0
  100. package/templates/clean/pxlr-cms/packages/admin/src/app/content/page.tsx +191 -0
  101. package/templates/clean/pxlr-cms/packages/admin/src/app/globals.css +132 -0
  102. package/templates/clean/pxlr-cms/packages/admin/src/app/layout.tsx +25 -0
  103. package/templates/clean/pxlr-cms/packages/admin/src/app/login/page.tsx +119 -0
  104. package/templates/clean/pxlr-cms/packages/admin/src/app/media/layout.tsx +7 -0
  105. package/templates/clean/pxlr-cms/packages/admin/src/app/media/page.tsx +362 -0
  106. package/templates/clean/pxlr-cms/packages/admin/src/app/page.tsx +184 -0
  107. package/templates/clean/pxlr-cms/packages/admin/src/app/profile/layout.tsx +7 -0
  108. package/templates/clean/pxlr-cms/packages/admin/src/app/profile/page.tsx +206 -0
  109. package/templates/clean/pxlr-cms/packages/admin/src/app/schemas/[name]/page.tsx +312 -0
  110. package/templates/clean/pxlr-cms/packages/admin/src/app/schemas/layout.tsx +7 -0
  111. package/templates/clean/pxlr-cms/packages/admin/src/app/schemas/page.tsx +210 -0
  112. package/templates/clean/pxlr-cms/packages/admin/src/app/settings/layout.tsx +7 -0
  113. package/templates/clean/pxlr-cms/packages/admin/src/app/settings/page.tsx +178 -0
  114. package/templates/clean/pxlr-cms/packages/admin/src/components/editor/media-picker.tsx +202 -0
  115. package/templates/clean/pxlr-cms/packages/admin/src/components/editor/rich-text-editor.tsx +387 -0
  116. package/templates/clean/pxlr-cms/packages/admin/src/components/layout/auth-layout.tsx +43 -0
  117. package/templates/clean/pxlr-cms/packages/admin/src/components/layout/header.tsx +79 -0
  118. package/templates/clean/pxlr-cms/packages/admin/src/components/layout/sidebar.tsx +68 -0
  119. package/templates/clean/pxlr-cms/packages/admin/src/components/providers.tsx +29 -0
  120. package/templates/clean/pxlr-cms/packages/admin/src/components/schema-code-generator.tsx +326 -0
  121. package/templates/clean/pxlr-cms/packages/admin/src/components/ui/avatar.tsx +49 -0
  122. package/templates/clean/pxlr-cms/packages/admin/src/components/ui/button.tsx +55 -0
  123. package/templates/clean/pxlr-cms/packages/admin/src/components/ui/dropdown-menu.tsx +194 -0
  124. package/templates/clean/pxlr-cms/packages/admin/src/components/ui/input.tsx +24 -0
  125. package/templates/clean/pxlr-cms/packages/admin/src/components/ui/label.tsx +25 -0
  126. package/templates/clean/pxlr-cms/packages/admin/src/components/ui/toast.tsx +127 -0
  127. package/templates/clean/pxlr-cms/packages/admin/src/components/ui/toaster.tsx +35 -0
  128. package/templates/clean/pxlr-cms/packages/admin/src/components/ui/use-toast.ts +187 -0
  129. package/templates/clean/pxlr-cms/packages/admin/src/lib/api.ts +96 -0
  130. package/templates/clean/pxlr-cms/packages/admin/src/lib/i18n/context.tsx +60 -0
  131. package/templates/clean/pxlr-cms/packages/admin/src/lib/i18n/translations.ts +317 -0
  132. package/templates/clean/pxlr-cms/packages/admin/src/lib/store/auth.ts +51 -0
  133. package/templates/clean/pxlr-cms/packages/admin/src/lib/utils.ts +29 -0
  134. package/templates/clean/pxlr-cms/packages/admin/tailwind.config.ts +57 -0
  135. package/templates/clean/pxlr-cms/packages/admin/tsconfig.json +27 -0
  136. package/templates/clean/pxlr-cms/packages/api/.env.example +23 -0
  137. package/templates/clean/pxlr-cms/packages/api/Dockerfile +26 -0
  138. package/templates/clean/pxlr-cms/packages/api/package.json +42 -0
  139. package/templates/clean/pxlr-cms/packages/api/src/config.ts +39 -0
  140. package/templates/clean/pxlr-cms/packages/api/src/database/index.ts +60 -0
  141. package/templates/clean/pxlr-cms/packages/api/src/database/init.sql +178 -0
  142. package/templates/clean/pxlr-cms/packages/api/src/database/redis.ts +95 -0
  143. package/templates/clean/pxlr-cms/packages/api/src/index.ts +157 -0
  144. package/templates/clean/pxlr-cms/packages/api/src/modules/auth/routes.ts +256 -0
  145. package/templates/clean/pxlr-cms/packages/api/src/modules/content/routes.ts +385 -0
  146. package/templates/clean/pxlr-cms/packages/api/src/modules/media/routes.ts +312 -0
  147. package/templates/clean/pxlr-cms/packages/api/src/modules/realtime/handler.ts +228 -0
  148. package/templates/clean/pxlr-cms/packages/api/src/modules/schema/routes.ts +284 -0
  149. package/templates/clean/pxlr-cms/packages/api/src/modules/versions/routes.ts +70 -0
  150. package/templates/clean/pxlr-cms/packages/api/tsconfig.json +24 -0
  151. package/templates/clean/pxlr-cms/packages/shared/package.json +14 -0
  152. package/templates/clean/pxlr-cms/packages/shared/src/types/index.ts +139 -0
  153. package/templates/clean/pxlr-cms/packages/shared/tsconfig.json +18 -0
@@ -0,0 +1,317 @@
1
+ export const translations = {
2
+ en: {
3
+ // Navigation
4
+ 'nav.dashboard': 'Dashboard',
5
+ 'nav.content': 'Content',
6
+ 'nav.schemas': 'Schemas',
7
+ 'nav.media': 'Media',
8
+ 'nav.settings': 'Settings',
9
+
10
+ // Header
11
+ 'header.contentManagement': 'Content Management',
12
+ 'header.profile': 'Profile',
13
+ 'header.logout': 'Log out',
14
+
15
+ // Dashboard
16
+ 'dashboard.title': 'Dashboard',
17
+ 'dashboard.welcome': 'Welcome to PXLR CMS. Manage your content from here.',
18
+ 'dashboard.contentTypes': 'Content Types',
19
+ 'dashboard.definedSchemas': 'Defined schemas',
20
+ 'dashboard.documents': 'Documents',
21
+ 'dashboard.totalContent': 'Total content items',
22
+ 'dashboard.mediaFiles': 'Media Files',
23
+ 'dashboard.uploadedFiles': 'Uploaded files',
24
+ 'dashboard.systemStatus': 'System Status',
25
+ 'dashboard.online': 'Online',
26
+ 'dashboard.allServices': 'All services running',
27
+ 'dashboard.quickStart': 'Quick Start',
28
+ 'dashboard.step1': 'Create a content schema in the Schemas section',
29
+ 'dashboard.step2': 'Add content documents based on your schemas',
30
+ 'dashboard.step3': 'Upload media files to the Media Library',
31
+ 'dashboard.step4': 'Access your content via the REST API',
32
+ 'dashboard.apiAccess': 'API Access',
33
+ 'dashboard.apiEndpoint': 'API Endpoint',
34
+ 'dashboard.documentation': 'Documentation',
35
+ 'dashboard.viewDocs': 'View Swagger API Docs',
36
+
37
+ // Schemas
38
+ 'schemas.title': 'Content Schemas',
39
+ 'schemas.subtitle': 'Define the structure of your content types',
40
+ 'schemas.newSchema': 'New Schema',
41
+ 'schemas.createNew': 'Create New Schema',
42
+ 'schemas.name': 'Name (API identifier)',
43
+ 'schemas.namePlaceholder': 'e.g., blogPost, article, my_page',
44
+ 'schemas.nameHint': 'Letters, numbers, underscores only. Start with a letter.',
45
+ 'schemas.displayName': 'Title (Display name)',
46
+ 'schemas.description': 'Description',
47
+ 'schemas.optional': 'Optional description',
48
+ 'schemas.create': 'Create Schema',
49
+ 'schemas.cancel': 'Cancel',
50
+ 'schemas.noSchemas': 'No schemas yet',
51
+ 'schemas.createFirst': 'Create your first content schema to get started',
52
+ 'schemas.fields': 'fields',
53
+ 'schemas.singleton': 'Singleton',
54
+ 'schemas.editSchema': 'Edit Schema',
55
+ 'schemas.basicInfo': 'Basic Information',
56
+ 'schemas.singletonHint': 'Singleton (only one document of this type)',
57
+ 'schemas.addField': 'Add Field',
58
+ 'schemas.noFields': 'No fields defined. Add your first field.',
59
+ 'schemas.fieldName': 'Field Name',
60
+ 'schemas.fieldTitle': 'Title',
61
+ 'schemas.fieldType': 'Type',
62
+ 'schemas.required': 'Required',
63
+ 'schemas.preview': 'Schema Preview (JSON)',
64
+ 'schemas.save': 'Save Changes',
65
+
66
+ // Content
67
+ 'content.title': 'Content',
68
+ 'content.subtitle': 'Manage your content documents',
69
+ 'content.newDocument': 'New Document',
70
+ 'content.noSchemas': 'No content schemas defined yet.',
71
+ 'content.createSchema': 'Create a schema first to start adding content.',
72
+ 'content.goToSchemas': 'Go to Schemas',
73
+ 'content.noDocuments': 'No documents yet',
74
+ 'content.createFirst': 'Create your first document',
75
+ 'content.edit': 'Edit',
76
+ 'content.delete': 'Delete',
77
+ 'content.lastUpdated': 'Last updated',
78
+ 'content.createNew': 'Create New Document',
79
+ 'content.selectSchema': 'Select Content Type',
80
+ 'content.back': 'Back to Content',
81
+ 'content.saving': 'Saving...',
82
+ 'content.saveDocument': 'Save Document',
83
+ 'content.editDocument': 'Edit Document',
84
+ 'content.deleteConfirm': 'Are you sure you want to delete this document?',
85
+
86
+ // Media
87
+ 'media.title': 'Media Library',
88
+ 'media.subtitle': 'Manage your media files',
89
+ 'media.upload': 'Upload File',
90
+ 'media.noFiles': 'No media files yet',
91
+ 'media.uploadFirst': 'Upload your first media file',
92
+ 'media.dragDrop': 'Drag and drop files here or click to browse',
93
+ 'media.uploading': 'Uploading...',
94
+ 'media.delete': 'Delete',
95
+ 'media.copy': 'Copy URL',
96
+ 'media.copied': 'URL copied!',
97
+
98
+ // Settings
99
+ 'settings.title': 'Settings',
100
+ 'settings.subtitle': 'Configure your CMS',
101
+ 'settings.general': 'General Settings',
102
+ 'settings.localization': 'Localization',
103
+ 'settings.defaultLocale': 'Default locale',
104
+ 'settings.systemInfo': 'System Information',
105
+ 'settings.version': 'Version',
106
+ 'settings.environment': 'Environment',
107
+ 'settings.language': 'Interface Language',
108
+
109
+ // Profile
110
+ 'profile.title': 'Profile',
111
+ 'profile.subtitle': 'Manage your account settings',
112
+ 'profile.updateProfile': 'Update Profile',
113
+ 'profile.displayName': 'Display Name',
114
+ 'profile.email': 'Email',
115
+ 'profile.emailHint': 'Email cannot be changed',
116
+ 'profile.saveChanges': 'Save Changes',
117
+ 'profile.changePassword': 'Change Password',
118
+ 'profile.currentPassword': 'Current Password',
119
+ 'profile.newPassword': 'New Password',
120
+ 'profile.confirmPassword': 'Confirm New Password',
121
+ 'profile.memberSince': 'Member since',
122
+
123
+ // Login
124
+ 'login.title': 'Sign in to PXLR CMS',
125
+ 'login.email': 'Email',
126
+ 'login.password': 'Password',
127
+ 'login.signIn': 'Sign in',
128
+ 'login.signingIn': 'Signing in...',
129
+
130
+ // Common
131
+ 'common.loading': 'Loading...',
132
+ 'common.save': 'Save',
133
+ 'common.cancel': 'Cancel',
134
+ 'common.delete': 'Delete',
135
+ 'common.edit': 'Edit',
136
+ 'common.create': 'Create',
137
+ 'common.back': 'Back',
138
+ 'common.error': 'Error',
139
+ 'common.success': 'Success',
140
+
141
+ // Field types
142
+ 'fieldType.string': 'Text',
143
+ 'fieldType.text': 'Long Text',
144
+ 'fieldType.number': 'Number',
145
+ 'fieldType.boolean': 'Boolean',
146
+ 'fieldType.date': 'Date',
147
+ 'fieldType.datetime': 'Date & Time',
148
+ 'fieldType.richText': 'Rich Text',
149
+ 'fieldType.image': 'Image',
150
+ 'fieldType.file': 'File',
151
+ 'fieldType.slug': 'Slug',
152
+ 'fieldType.url': 'URL',
153
+ 'fieldType.email': 'Email',
154
+ 'fieldType.reference': 'Reference',
155
+ 'fieldType.array': 'Array',
156
+ 'fieldType.object': 'Object',
157
+ },
158
+ ru: {
159
+ // Navigation
160
+ 'nav.dashboard': 'Главная',
161
+ 'nav.content': 'Контент',
162
+ 'nav.schemas': 'Схемы',
163
+ 'nav.media': 'Медиа',
164
+ 'nav.settings': 'Настройки',
165
+
166
+ // Header
167
+ 'header.contentManagement': 'Управление контентом',
168
+ 'header.profile': 'Профиль',
169
+ 'header.logout': 'Выйти',
170
+
171
+ // Dashboard
172
+ 'dashboard.title': 'Главная',
173
+ 'dashboard.welcome': 'Добро пожаловать в PXLR CMS. Управляйте контентом отсюда.',
174
+ 'dashboard.contentTypes': 'Типы контента',
175
+ 'dashboard.definedSchemas': 'Определённые схемы',
176
+ 'dashboard.documents': 'Документы',
177
+ 'dashboard.totalContent': 'Всего записей',
178
+ 'dashboard.mediaFiles': 'Медиафайлы',
179
+ 'dashboard.uploadedFiles': 'Загруженные файлы',
180
+ 'dashboard.systemStatus': 'Статус системы',
181
+ 'dashboard.online': 'Онлайн',
182
+ 'dashboard.allServices': 'Все сервисы работают',
183
+ 'dashboard.quickStart': 'Быстрый старт',
184
+ 'dashboard.step1': 'Создайте схему контента в разделе Схемы',
185
+ 'dashboard.step2': 'Добавьте документы на основе ваших схем',
186
+ 'dashboard.step3': 'Загрузите медиафайлы в Медиатеку',
187
+ 'dashboard.step4': 'Получайте контент через REST API',
188
+ 'dashboard.apiAccess': 'Доступ к API',
189
+ 'dashboard.apiEndpoint': 'API Endpoint',
190
+ 'dashboard.documentation': 'Документация',
191
+ 'dashboard.viewDocs': 'Открыть Swagger API Docs',
192
+
193
+ // Schemas
194
+ 'schemas.title': 'Схемы контента',
195
+ 'schemas.subtitle': 'Определите структуру ваших типов контента',
196
+ 'schemas.newSchema': 'Новая схема',
197
+ 'schemas.createNew': 'Создать новую схему',
198
+ 'schemas.name': 'Имя (API идентификатор)',
199
+ 'schemas.namePlaceholder': 'например, blogPost, article, my_page',
200
+ 'schemas.nameHint': 'Только буквы, цифры, подчёркивания. Начинать с буквы.',
201
+ 'schemas.displayName': 'Название (для отображения)',
202
+ 'schemas.description': 'Описание',
203
+ 'schemas.optional': 'Необязательное описание',
204
+ 'schemas.create': 'Создать схему',
205
+ 'schemas.cancel': 'Отмена',
206
+ 'schemas.noSchemas': 'Схем пока нет',
207
+ 'schemas.createFirst': 'Создайте первую схему контента для начала работы',
208
+ 'schemas.fields': 'полей',
209
+ 'schemas.singleton': 'Единственный',
210
+ 'schemas.editSchema': 'Редактировать схему',
211
+ 'schemas.basicInfo': 'Основная информация',
212
+ 'schemas.singletonHint': 'Единственный экземпляр (только один документ этого типа)',
213
+ 'schemas.addField': 'Добавить поле',
214
+ 'schemas.noFields': 'Полей не определено. Добавьте первое поле.',
215
+ 'schemas.fieldName': 'Имя поля',
216
+ 'schemas.fieldTitle': 'Название',
217
+ 'schemas.fieldType': 'Тип',
218
+ 'schemas.required': 'Обязательное',
219
+ 'schemas.preview': 'Предпросмотр схемы (JSON)',
220
+ 'schemas.save': 'Сохранить изменения',
221
+
222
+ // Content
223
+ 'content.title': 'Контент',
224
+ 'content.subtitle': 'Управляйте вашими документами',
225
+ 'content.newDocument': 'Новый документ',
226
+ 'content.noSchemas': 'Схемы контента ещё не определены.',
227
+ 'content.createSchema': 'Сначала создайте схему, чтобы добавлять контент.',
228
+ 'content.goToSchemas': 'Перейти к схемам',
229
+ 'content.noDocuments': 'Документов пока нет',
230
+ 'content.createFirst': 'Создайте первый документ',
231
+ 'content.edit': 'Редактировать',
232
+ 'content.delete': 'Удалить',
233
+ 'content.lastUpdated': 'Обновлено',
234
+ 'content.createNew': 'Создать новый документ',
235
+ 'content.selectSchema': 'Выберите тип контента',
236
+ 'content.back': 'Назад к контенту',
237
+ 'content.saving': 'Сохранение...',
238
+ 'content.saveDocument': 'Сохранить документ',
239
+ 'content.editDocument': 'Редактировать документ',
240
+ 'content.deleteConfirm': 'Вы уверены, что хотите удалить этот документ?',
241
+
242
+ // Media
243
+ 'media.title': 'Медиатека',
244
+ 'media.subtitle': 'Управляйте вашими медиафайлами',
245
+ 'media.upload': 'Загрузить файл',
246
+ 'media.noFiles': 'Медиафайлов пока нет',
247
+ 'media.uploadFirst': 'Загрузите первый медиафайл',
248
+ 'media.dragDrop': 'Перетащите файлы сюда или нажмите для выбора',
249
+ 'media.uploading': 'Загрузка...',
250
+ 'media.delete': 'Удалить',
251
+ 'media.copy': 'Копировать URL',
252
+ 'media.copied': 'URL скопирован!',
253
+
254
+ // Settings
255
+ 'settings.title': 'Настройки',
256
+ 'settings.subtitle': 'Конфигурация CMS',
257
+ 'settings.general': 'Общие настройки',
258
+ 'settings.localization': 'Локализация',
259
+ 'settings.defaultLocale': 'Язык по умолчанию',
260
+ 'settings.systemInfo': 'Информация о системе',
261
+ 'settings.version': 'Версия',
262
+ 'settings.environment': 'Окружение',
263
+ 'settings.language': 'Язык интерфейса',
264
+
265
+ // Profile
266
+ 'profile.title': 'Профиль',
267
+ 'profile.subtitle': 'Управление настройками аккаунта',
268
+ 'profile.updateProfile': 'Обновить профиль',
269
+ 'profile.displayName': 'Отображаемое имя',
270
+ 'profile.email': 'Email',
271
+ 'profile.emailHint': 'Email нельзя изменить',
272
+ 'profile.saveChanges': 'Сохранить изменения',
273
+ 'profile.changePassword': 'Изменить пароль',
274
+ 'profile.currentPassword': 'Текущий пароль',
275
+ 'profile.newPassword': 'Новый пароль',
276
+ 'profile.confirmPassword': 'Подтвердите новый пароль',
277
+ 'profile.memberSince': 'Участник с',
278
+
279
+ // Login
280
+ 'login.title': 'Вход в PXLR CMS',
281
+ 'login.email': 'Email',
282
+ 'login.password': 'Пароль',
283
+ 'login.signIn': 'Войти',
284
+ 'login.signingIn': 'Вход...',
285
+
286
+ // Common
287
+ 'common.loading': 'Загрузка...',
288
+ 'common.save': 'Сохранить',
289
+ 'common.cancel': 'Отмена',
290
+ 'common.delete': 'Удалить',
291
+ 'common.edit': 'Редактировать',
292
+ 'common.create': 'Создать',
293
+ 'common.back': 'Назад',
294
+ 'common.error': 'Ошибка',
295
+ 'common.success': 'Успешно',
296
+
297
+ // Field types
298
+ 'fieldType.string': 'Текст',
299
+ 'fieldType.text': 'Длинный текст',
300
+ 'fieldType.number': 'Число',
301
+ 'fieldType.boolean': 'Логический',
302
+ 'fieldType.date': 'Дата',
303
+ 'fieldType.datetime': 'Дата и время',
304
+ 'fieldType.richText': 'Форматированный текст',
305
+ 'fieldType.image': 'Изображение',
306
+ 'fieldType.file': 'Файл',
307
+ 'fieldType.slug': 'Слаг',
308
+ 'fieldType.url': 'URL',
309
+ 'fieldType.email': 'Email',
310
+ 'fieldType.reference': 'Ссылка',
311
+ 'fieldType.array': 'Массив',
312
+ 'fieldType.object': 'Объект',
313
+ },
314
+ } as const;
315
+
316
+ export type Locale = keyof typeof translations;
317
+ export type TranslationKey = keyof typeof translations.en;
@@ -0,0 +1,51 @@
1
+ import { create } from 'zustand';
2
+ import { persist } from 'zustand/middleware';
3
+
4
+ interface User {
5
+ id: string;
6
+ email: string;
7
+ name: string;
8
+ role: string;
9
+ }
10
+
11
+ interface AuthState {
12
+ token: string | null;
13
+ user: User | null;
14
+ isAuthenticated: boolean;
15
+ isLoading: boolean;
16
+ setAuth: (token: string, user: User) => void;
17
+ logout: () => void;
18
+ setLoading: (loading: boolean) => void;
19
+ }
20
+
21
+ export const useAuthStore = create<AuthState>()(
22
+ persist(
23
+ (set) => ({
24
+ token: null,
25
+ user: null,
26
+ isAuthenticated: false,
27
+ isLoading: true,
28
+ setAuth: (token, user) =>
29
+ set({
30
+ token,
31
+ user,
32
+ isAuthenticated: true,
33
+ isLoading: false,
34
+ }),
35
+ logout: () =>
36
+ set({
37
+ token: null,
38
+ user: null,
39
+ isAuthenticated: false,
40
+ isLoading: false,
41
+ }),
42
+ setLoading: (loading) => set({ isLoading: loading }),
43
+ }),
44
+ {
45
+ name: 'pxlr-auth',
46
+ onRehydrateStorage: () => (state) => {
47
+ state?.setLoading(false);
48
+ },
49
+ }
50
+ )
51
+ );
@@ -0,0 +1,29 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export function formatDate(date: string | Date) {
9
+ return new Intl.DateTimeFormat('en-US', {
10
+ dateStyle: 'medium',
11
+ timeStyle: 'short',
12
+ }).format(new Date(date));
13
+ }
14
+
15
+ export function formatBytes(bytes: number) {
16
+ if (bytes === 0) return '0 Bytes';
17
+ const k = 1024;
18
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
19
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
20
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
21
+ }
22
+
23
+ export function slugify(text: string) {
24
+ return text
25
+ .toLowerCase()
26
+ .replace(/[^\w\s-]/g, '')
27
+ .replace(/[\s_-]+/g, '-')
28
+ .replace(/^-+|-+$/g, '');
29
+ }
@@ -0,0 +1,57 @@
1
+ import type { Config } from 'tailwindcss';
2
+
3
+ const config: Config = {
4
+ darkMode: ['class'],
5
+ content: [
6
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
7
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
8
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
9
+ ],
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ border: 'hsl(var(--border))',
14
+ input: 'hsl(var(--input))',
15
+ ring: 'hsl(var(--ring))',
16
+ background: 'hsl(var(--background))',
17
+ foreground: 'hsl(var(--foreground))',
18
+ primary: {
19
+ DEFAULT: 'hsl(var(--primary))',
20
+ foreground: 'hsl(var(--primary-foreground))',
21
+ },
22
+ secondary: {
23
+ DEFAULT: 'hsl(var(--secondary))',
24
+ foreground: 'hsl(var(--secondary-foreground))',
25
+ },
26
+ destructive: {
27
+ DEFAULT: 'hsl(var(--destructive))',
28
+ foreground: 'hsl(var(--destructive-foreground))',
29
+ },
30
+ muted: {
31
+ DEFAULT: 'hsl(var(--muted))',
32
+ foreground: 'hsl(var(--muted-foreground))',
33
+ },
34
+ accent: {
35
+ DEFAULT: 'hsl(var(--accent))',
36
+ foreground: 'hsl(var(--accent-foreground))',
37
+ },
38
+ popover: {
39
+ DEFAULT: 'hsl(var(--popover))',
40
+ foreground: 'hsl(var(--popover-foreground))',
41
+ },
42
+ card: {
43
+ DEFAULT: 'hsl(var(--card))',
44
+ foreground: 'hsl(var(--card-foreground))',
45
+ },
46
+ },
47
+ borderRadius: {
48
+ lg: 'var(--radius)',
49
+ md: 'calc(var(--radius) - 2px)',
50
+ sm: 'calc(var(--radius) - 4px)',
51
+ },
52
+ },
53
+ },
54
+ plugins: [require('tailwindcss-animate')],
55
+ };
56
+
57
+ export default config;
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1,23 @@
1
+ NODE_ENV=development
2
+ PORT=4000
3
+
4
+ # Database
5
+ DATABASE_URL=postgresql://pxlr:pxlr_secret_2024@localhost:5432/pxlr_cms
6
+
7
+ # Redis
8
+ REDIS_URL=redis://localhost:6379
9
+
10
+ # MinIO S3 Storage
11
+ MINIO_ENDPOINT=localhost
12
+ MINIO_PORT=9000
13
+ MINIO_ACCESS_KEY=pxlr_minio
14
+ MINIO_SECRET_KEY=pxlr_minio_secret_2024
15
+ MINIO_BUCKET=pxlr-media
16
+ MINIO_USE_SSL=false
17
+
18
+ # JWT Authentication
19
+ JWT_SECRET=change_this_to_a_secure_random_string_in_production
20
+ JWT_EXPIRES_IN=7d
21
+
22
+ # CORS
23
+ CORS_ORIGINS=http://localhost:3000
@@ -0,0 +1,26 @@
1
+ # PXLR CMS API Dockerfile
2
+ FROM node:20-alpine
3
+
4
+ WORKDIR /app
5
+
6
+ # Install dependencies for sharp (image processing)
7
+ RUN apk add --no-cache \
8
+ python3 \
9
+ make \
10
+ g++ \
11
+ vips-dev
12
+
13
+ # Copy package files
14
+ COPY package.json ./
15
+
16
+ # Install dependencies
17
+ RUN npm install
18
+
19
+ # Copy source code
20
+ COPY . .
21
+
22
+ # Expose port
23
+ EXPOSE 4000
24
+
25
+ # Start development server
26
+ CMD ["npm", "run", "dev"]
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@pxlr/api",
3
+ "version": "1.0.0",
4
+ "description": "PXLR CMS Backend API",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "dev": "tsx watch src/index.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "lint": "eslint src --ext .ts",
11
+ "typecheck": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@fastify/cors": "^10.0.0",
15
+ "@fastify/helmet": "^12.0.0",
16
+ "@fastify/jwt": "^9.0.0",
17
+ "@fastify/multipart": "^9.0.0",
18
+ "@fastify/rate-limit": "^10.0.0",
19
+ "@fastify/swagger": "^9.0.0",
20
+ "@fastify/swagger-ui": "^5.0.0",
21
+ "@fastify/websocket": "^11.0.0",
22
+ "bcryptjs": "^2.4.3",
23
+ "dotenv": "^16.4.5",
24
+ "fastify": "^5.0.0",
25
+ "ioredis": "^5.4.1",
26
+ "minio": "^8.0.1",
27
+ "pg": "^8.13.0",
28
+ "sharp": "^0.33.5",
29
+ "uuid": "^10.0.0",
30
+ "zod": "^3.23.8",
31
+ "pino-pretty": "^13.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/bcryptjs": "^2.4.6",
35
+ "@types/node": "^22.0.0",
36
+ "@types/pg": "^8.11.10",
37
+ "@types/uuid": "^10.0.0",
38
+ "eslint": "^9.0.0",
39
+ "tsx": "^4.19.0",
40
+ "typescript": "^5.6.0"
41
+ }
42
+ }
@@ -0,0 +1,39 @@
1
+ import 'dotenv/config';
2
+
3
+ export const config = {
4
+ // Environment
5
+ isDev: process.env.NODE_ENV !== 'production',
6
+ port: parseInt(process.env.PORT || '4000', 10),
7
+
8
+ // Database
9
+ databaseUrl: process.env.DATABASE_URL || 'postgresql://pxlr:pxlr_secret_2024@localhost:5432/pxlr_cms',
10
+
11
+ // Redis
12
+ redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
13
+
14
+ // JWT
15
+ jwtSecret: process.env.JWT_SECRET || 'pxlr_jwt_secret_change_in_production_2024',
16
+ jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',
17
+
18
+ // S3 Storage (MinIO, AWS S3, Cloudflare R2, etc.)
19
+ // Supports both MINIO_* and S3_* env variable prefixes
20
+ s3: {
21
+ endpoint: process.env.S3_ENDPOINT || process.env.MINIO_ENDPOINT || 'localhost',
22
+ port: parseInt(process.env.S3_PORT || process.env.MINIO_PORT || '9000', 10),
23
+ accessKey: process.env.S3_ACCESS_KEY || process.env.MINIO_ACCESS_KEY || 'pxlr_minio',
24
+ secretKey: process.env.S3_SECRET_KEY || process.env.MINIO_SECRET_KEY || 'pxlr_minio_secret_2024',
25
+ bucket: process.env.S3_BUCKET || process.env.MINIO_BUCKET || 'pxlr-media',
26
+ useSSL: (process.env.S3_USE_SSL || process.env.MINIO_USE_SSL) === 'true',
27
+ region: process.env.S3_REGION || 'us-east-1',
28
+ // Public URL for browser access
29
+ publicUrl: process.env.S3_PUBLIC_URL || process.env.MINIO_PUBLIC_URL || 'http://localhost:9010',
30
+ },
31
+
32
+ // Legacy alias for backwards compatibility
33
+ get minio() {
34
+ return this.s3;
35
+ },
36
+
37
+ // CORS
38
+ corsOrigins: (process.env.CORS_ORIGINS || process.env.CORS_ORIGIN || 'http://localhost:3000').split(','),
39
+ } as const;
@@ -0,0 +1,60 @@
1
+ import pg from 'pg';
2
+ import { config } from '../config.js';
3
+
4
+ const { Pool } = pg;
5
+
6
+ class Database {
7
+ private pool: pg.Pool | null = null;
8
+
9
+ async connect() {
10
+ this.pool = new Pool({
11
+ connectionString: config.databaseUrl,
12
+ max: 20,
13
+ idleTimeoutMillis: 30000,
14
+ connectionTimeoutMillis: 2000,
15
+ });
16
+
17
+ // Test connection
18
+ const client = await this.pool.connect();
19
+ client.release();
20
+ }
21
+
22
+ async disconnect() {
23
+ if (this.pool) {
24
+ await this.pool.end();
25
+ this.pool = null;
26
+ }
27
+ }
28
+
29
+ async query<T = any>(text: string, params?: any[]): Promise<pg.QueryResult<T>> {
30
+ if (!this.pool) {
31
+ throw new Error('Database not connected');
32
+ }
33
+ return this.pool.query(text, params);
34
+ }
35
+
36
+ async getClient(): Promise<pg.PoolClient> {
37
+ if (!this.pool) {
38
+ throw new Error('Database not connected');
39
+ }
40
+ return this.pool.connect();
41
+ }
42
+
43
+ // Transaction helper
44
+ async transaction<T>(fn: (client: pg.PoolClient) => Promise<T>): Promise<T> {
45
+ const client = await this.getClient();
46
+ try {
47
+ await client.query('BEGIN');
48
+ const result = await fn(client);
49
+ await client.query('COMMIT');
50
+ return result;
51
+ } catch (error) {
52
+ await client.query('ROLLBACK');
53
+ throw error;
54
+ } finally {
55
+ client.release();
56
+ }
57
+ }
58
+ }
59
+
60
+ export const db = new Database();