@nlabs/metropolisjs 0.1.2 → 0.3.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 (329) hide show
  1. package/.github/copilot-instructions.md +167 -0
  2. package/AGENTS.md +13 -0
  3. package/CHANGELOG.md +112 -0
  4. package/GRAPHQL_VALIDATION_RESULTS.md +158 -0
  5. package/PERMISSION_GRAPHQL_INTEGRATION.md +293 -0
  6. package/PERMISSION_IMPLEMENTATION_SUMMARY.md +243 -0
  7. package/README.md +1123 -112
  8. package/SIGNIN_GRAPHQL_VALIDATION.md +125 -0
  9. package/VAULT_GRAPHQL_API_FINDINGS.md +312 -0
  10. package/docs/ACTIONS.md +63 -0
  11. package/docs/COLLECTIONS.md +1038 -0
  12. package/docs/CONNECTIONS.md +1367 -0
  13. package/docs/CRUD_INTEGRATION.md +702 -0
  14. package/examples/connections-usage.tsx +909 -0
  15. package/examples/content-actions-usage.ts +144 -0
  16. package/examples/crud-usage.tsx +847 -0
  17. package/examples/extensibility-usage.tsx +889 -0
  18. package/examples/i18n-import-examples.ts +86 -0
  19. package/examples/i18n-namespace-usage.ts +118 -0
  20. package/examples/i18next-singleton-demo.ts +187 -0
  21. package/examples/permission-system-usage.tsx +415 -0
  22. package/examples/project-details.tsx +397 -0
  23. package/examples/shared-i18n-usage.tsx +203 -0
  24. package/examples/signup-error-handling.ts +100 -0
  25. package/examples/translation-types.ts +86 -0
  26. package/examples/translation-usage.tsx +336 -0
  27. package/index.js +1 -0
  28. package/jest.config.mjs +28 -0
  29. package/lex.config.mjs +11 -2
  30. package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts +22 -0
  31. package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts.map +1 -0
  32. package/lib/__tests__/e2e/helpers/testGraphQLServer.js +197 -0
  33. package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts +3 -0
  34. package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts.map +1 -0
  35. package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.js +52 -0
  36. package/lib/actions/appActions/appActions.d.ts +29 -0
  37. package/lib/actions/appActions/appActions.d.ts.map +1 -0
  38. package/lib/actions/appActions/appActions.js +159 -0
  39. package/lib/actions/connectionActions/connectionActions.d.ts +19 -0
  40. package/lib/actions/connectionActions/connectionActions.d.ts.map +1 -0
  41. package/lib/actions/connectionActions/connectionActions.js +131 -0
  42. package/lib/actions/contentActions/contentActions.d.ts +34 -0
  43. package/lib/actions/contentActions/contentActions.d.ts.map +1 -0
  44. package/lib/actions/contentActions/contentActions.js +314 -0
  45. package/lib/actions/conversationActions/conversationActions.d.ts +30 -0
  46. package/lib/actions/conversationActions/conversationActions.d.ts.map +1 -0
  47. package/lib/actions/conversationActions/conversationActions.js +225 -0
  48. package/lib/actions/eventActions/eventActions.d.ts +27 -7
  49. package/lib/actions/eventActions/eventActions.d.ts.map +1 -0
  50. package/lib/actions/eventActions/eventActions.js +436 -2
  51. package/lib/actions/groupActions/groupActions.d.ts +30 -0
  52. package/lib/actions/groupActions/groupActions.d.ts.map +1 -0
  53. package/lib/actions/groupActions/groupActions.js +229 -0
  54. package/lib/actions/imageActions/imageActions.d.ts +16 -10
  55. package/lib/actions/imageActions/imageActions.d.ts.map +1 -0
  56. package/lib/actions/imageActions/imageActions.js +593 -2
  57. package/lib/actions/index.d.ts +21 -9
  58. package/lib/actions/index.d.ts.map +1 -0
  59. package/lib/actions/index.js +22 -2
  60. package/lib/actions/locationActions/locationActions.d.ts +11 -9
  61. package/lib/actions/locationActions/locationActions.d.ts.map +1 -0
  62. package/lib/actions/locationActions/locationActions.js +388 -2
  63. package/lib/actions/messageActions/messageActions.d.ts +16 -32
  64. package/lib/actions/messageActions/messageActions.d.ts.map +1 -0
  65. package/lib/actions/messageActions/messageActions.js +235 -2
  66. package/lib/actions/permissionActions/permissionActions.d.ts +34 -0
  67. package/lib/actions/permissionActions/permissionActions.d.ts.map +1 -0
  68. package/lib/actions/permissionActions/permissionActions.js +330 -0
  69. package/lib/actions/personaActions/personaActions.d.ts +65 -0
  70. package/lib/actions/personaActions/personaActions.d.ts.map +1 -0
  71. package/lib/actions/personaActions/personaActions.js +572 -0
  72. package/lib/actions/postActions/postActions.d.ts +12 -32
  73. package/lib/actions/postActions/postActions.d.ts.map +1 -0
  74. package/lib/actions/postActions/postActions.js +447 -2
  75. package/lib/actions/reactionActions/reactionActions.d.ts +13 -27
  76. package/lib/actions/reactionActions/reactionActions.d.ts.map +1 -0
  77. package/lib/actions/reactionActions/reactionActions.js +413 -2
  78. package/lib/actions/sseActions/sseActions.d.ts +1 -0
  79. package/lib/actions/sseActions/sseActions.d.ts.map +1 -0
  80. package/lib/actions/sseActions/sseActions.js +148 -2
  81. package/lib/actions/subscriptionActions/subscriptionActions.d.ts +24 -0
  82. package/lib/actions/subscriptionActions/subscriptionActions.d.ts.map +1 -0
  83. package/lib/actions/subscriptionActions/subscriptionActions.js +329 -0
  84. package/lib/actions/tagActions/tagActions.d.ts +13 -31
  85. package/lib/actions/tagActions/tagActions.d.ts.map +1 -0
  86. package/lib/actions/tagActions/tagActions.js +347 -2
  87. package/lib/actions/translationActions/translationActions.d.ts +38 -0
  88. package/lib/actions/translationActions/translationActions.d.ts.map +1 -0
  89. package/lib/actions/translationActions/translationActions.js +189 -0
  90. package/lib/actions/userActions/userActions.d.ts +44 -95
  91. package/lib/actions/userActions/userActions.d.ts.map +1 -0
  92. package/lib/actions/userActions/userActions.js +870 -2
  93. package/lib/actions/videoActions/videoActions.d.ts +41 -0
  94. package/lib/actions/videoActions/videoActions.d.ts.map +1 -0
  95. package/lib/actions/videoActions/videoActions.js +532 -0
  96. package/lib/actions/websocketActions/websocketActions.d.ts +20 -21
  97. package/lib/actions/websocketActions/websocketActions.d.ts.map +1 -0
  98. package/lib/actions/websocketActions/websocketActions.js +377 -2
  99. package/lib/adapters/appAdapter/appAdapter.d.ts +8 -0
  100. package/lib/adapters/appAdapter/appAdapter.d.ts.map +1 -0
  101. package/lib/adapters/appAdapter/appAdapter.js +90 -0
  102. package/lib/adapters/arangoAdapter/arangoAdapter.d.ts +3 -2
  103. package/lib/adapters/arangoAdapter/arangoAdapter.d.ts.map +1 -0
  104. package/lib/adapters/arangoAdapter/arangoAdapter.js +75 -2
  105. package/lib/adapters/contentAdapter/contentAdapter.d.ts +3 -2
  106. package/lib/adapters/contentAdapter/contentAdapter.d.ts.map +1 -0
  107. package/lib/adapters/contentAdapter/contentAdapter.js +107 -2
  108. package/lib/adapters/conversationAdapter/conversationAdapter.d.ts +3 -2
  109. package/lib/adapters/conversationAdapter/conversationAdapter.d.ts.map +1 -0
  110. package/lib/adapters/conversationAdapter/conversationAdapter.js +75 -2
  111. package/lib/adapters/eventAdapter/eventAdapter.d.ts +5 -2
  112. package/lib/adapters/eventAdapter/eventAdapter.d.ts.map +1 -0
  113. package/lib/adapters/eventAdapter/eventAdapter.js +162 -2
  114. package/lib/adapters/fileAdapter/fileAdapter.d.ts +3 -2
  115. package/lib/adapters/fileAdapter/fileAdapter.d.ts.map +1 -0
  116. package/lib/adapters/fileAdapter/fileAdapter.js +118 -2
  117. package/lib/adapters/groupAdapter/groupAdapter.d.ts +28 -0
  118. package/lib/adapters/groupAdapter/groupAdapter.d.ts.map +1 -0
  119. package/lib/adapters/groupAdapter/groupAdapter.js +66 -0
  120. package/lib/adapters/imageAdapter/imageAdapter.d.ts +9 -2
  121. package/lib/adapters/imageAdapter/imageAdapter.d.ts.map +1 -0
  122. package/lib/adapters/imageAdapter/imageAdapter.js +134 -2
  123. package/lib/adapters/index.d.ts +34 -16
  124. package/lib/adapters/index.d.ts.map +1 -0
  125. package/lib/adapters/index.js +19 -2
  126. package/lib/adapters/locationAdapter/locationAdapter.d.ts +3 -2
  127. package/lib/adapters/locationAdapter/locationAdapter.d.ts.map +1 -0
  128. package/lib/adapters/locationAdapter/locationAdapter.js +95 -2
  129. package/lib/adapters/messageAdapter/messageAdapter.d.ts +23 -19
  130. package/lib/adapters/messageAdapter/messageAdapter.d.ts.map +1 -0
  131. package/lib/adapters/messageAdapter/messageAdapter.js +82 -2
  132. package/lib/adapters/permissionAdapter/permissionAdapter.d.ts +41 -0
  133. package/lib/adapters/permissionAdapter/permissionAdapter.d.ts.map +1 -0
  134. package/lib/adapters/permissionAdapter/permissionAdapter.js +120 -0
  135. package/lib/adapters/personaAdapter/personaAdapter.d.ts +28 -2
  136. package/lib/adapters/personaAdapter/personaAdapter.d.ts.map +1 -0
  137. package/lib/adapters/personaAdapter/personaAdapter.js +161 -2
  138. package/lib/adapters/postAdapter/postAdapter.d.ts +3 -2
  139. package/lib/adapters/postAdapter/postAdapter.d.ts.map +1 -0
  140. package/lib/adapters/postAdapter/postAdapter.js +115 -2
  141. package/lib/adapters/reactionAdapter/reactionAdapter.d.ts +5 -2
  142. package/lib/adapters/reactionAdapter/reactionAdapter.d.ts.map +1 -0
  143. package/lib/adapters/reactionAdapter/reactionAdapter.js +81 -2
  144. package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts +3 -2
  145. package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts.map +1 -0
  146. package/lib/adapters/reaktorAdapter/reaktorAdapter.js +58 -2
  147. package/lib/adapters/sessionAdapter/sessionAdapter.d.ts +3 -2
  148. package/lib/adapters/sessionAdapter/sessionAdapter.d.ts.map +1 -0
  149. package/lib/adapters/sessionAdapter/sessionAdapter.js +80 -2
  150. package/lib/adapters/tagAdapter/tagAdapter.d.ts +3 -2
  151. package/lib/adapters/tagAdapter/tagAdapter.d.ts.map +1 -0
  152. package/lib/adapters/tagAdapter/tagAdapter.js +98 -2
  153. package/lib/adapters/translationAdapter/translationAdapter.d.ts +21 -0
  154. package/lib/adapters/translationAdapter/translationAdapter.d.ts.map +1 -0
  155. package/lib/adapters/translationAdapter/translationAdapter.js +55 -0
  156. package/lib/adapters/userAdapter/userAdapter.d.ts +18 -6
  157. package/lib/adapters/userAdapter/userAdapter.d.ts.map +1 -0
  158. package/lib/adapters/userAdapter/userAdapter.js +302 -2
  159. package/lib/adapters/videoAdapter/videoAdapter.d.ts +8 -0
  160. package/lib/adapters/videoAdapter/videoAdapter.d.ts.map +1 -0
  161. package/lib/adapters/videoAdapter/videoAdapter.js +194 -0
  162. package/lib/config/index.d.ts +19 -6
  163. package/lib/config/index.d.ts.map +1 -0
  164. package/lib/config/index.js +97 -2
  165. package/lib/constants/Collections.d.ts +44 -0
  166. package/lib/constants/Collections.d.ts.map +1 -0
  167. package/lib/constants/Collections.js +40 -0
  168. package/lib/constants/MetropolisConstants.d.ts +1 -0
  169. package/lib/constants/MetropolisConstants.d.ts.map +1 -0
  170. package/lib/constants/MetropolisConstants.js +15 -2
  171. package/lib/graphql/message.d.ts +1 -0
  172. package/lib/graphql/message.d.ts.map +1 -0
  173. package/lib/graphql/message.js +6 -4
  174. package/lib/graphql/notification.d.ts +1 -0
  175. package/lib/graphql/notification.d.ts.map +1 -0
  176. package/lib/graphql/notification.js +6 -4
  177. package/lib/graphql/session.d.ts +1 -0
  178. package/lib/graphql/session.d.ts.map +1 -0
  179. package/lib/graphql/session.js +4 -3
  180. package/lib/index.d.ts +45 -22
  181. package/lib/index.d.ts.map +1 -0
  182. package/lib/index.js +248 -2
  183. package/lib/stores/appStore.d.ts +1 -0
  184. package/lib/stores/appStore.d.ts.map +1 -0
  185. package/lib/stores/appStore.js +23 -2
  186. package/lib/stores/contentStore.d.ts +36 -0
  187. package/lib/stores/contentStore.d.ts.map +1 -0
  188. package/lib/stores/contentStore.js +139 -0
  189. package/lib/stores/conversationStore.d.ts +38 -0
  190. package/lib/stores/conversationStore.d.ts.map +1 -0
  191. package/lib/stores/conversationStore.js +117 -0
  192. package/lib/stores/eventStore.d.ts +4 -1
  193. package/lib/stores/eventStore.d.ts.map +1 -0
  194. package/lib/stores/eventStore.js +47 -2
  195. package/lib/stores/groupStore.d.ts +37 -0
  196. package/lib/stores/groupStore.d.ts.map +1 -0
  197. package/lib/stores/groupStore.js +68 -0
  198. package/lib/stores/imageStore.d.ts +6 -1
  199. package/lib/stores/imageStore.d.ts.map +1 -0
  200. package/lib/stores/imageStore.js +53 -2
  201. package/lib/stores/index.d.ts +20 -11
  202. package/lib/stores/index.d.ts.map +1 -0
  203. package/lib/stores/index.js +24 -2
  204. package/lib/stores/locationStore.d.ts +2 -1
  205. package/lib/stores/locationStore.d.ts.map +1 -0
  206. package/lib/stores/locationStore.js +42 -2
  207. package/lib/stores/messageStore.d.ts +19 -4
  208. package/lib/stores/messageStore.d.ts.map +1 -0
  209. package/lib/stores/messageStore.js +137 -2
  210. package/lib/stores/notificationStore.d.ts +33 -0
  211. package/lib/stores/notificationStore.d.ts.map +1 -0
  212. package/lib/stores/notificationStore.js +55 -0
  213. package/lib/stores/permissionStore.d.ts +37 -0
  214. package/lib/stores/permissionStore.d.ts.map +1 -0
  215. package/lib/stores/permissionStore.js +117 -0
  216. package/lib/stores/personaStore.d.ts +26 -0
  217. package/lib/stores/personaStore.d.ts.map +1 -0
  218. package/lib/stores/personaStore.js +118 -0
  219. package/lib/stores/postStore.d.ts +2 -1
  220. package/lib/stores/postStore.d.ts.map +1 -0
  221. package/lib/stores/postStore.js +53 -2
  222. package/lib/stores/reactionStore.d.ts +1 -0
  223. package/lib/stores/reactionStore.d.ts.map +1 -0
  224. package/lib/stores/reactionStore.js +28 -2
  225. package/lib/stores/subscriptionStore.d.ts +41 -0
  226. package/lib/stores/subscriptionStore.d.ts.map +1 -0
  227. package/lib/stores/subscriptionStore.js +85 -0
  228. package/lib/stores/tagStore.d.ts +8 -5
  229. package/lib/stores/tagStore.d.ts.map +1 -0
  230. package/lib/stores/tagStore.js +87 -2
  231. package/lib/stores/translationStore.d.ts +42 -0
  232. package/lib/stores/translationStore.d.ts.map +1 -0
  233. package/lib/stores/translationStore.js +122 -0
  234. package/lib/stores/userStore.d.ts +5 -4
  235. package/lib/stores/userStore.d.ts.map +1 -0
  236. package/lib/stores/userStore.js +319 -2
  237. package/lib/stores/videoStore.d.ts +39 -0
  238. package/lib/stores/videoStore.d.ts.map +1 -0
  239. package/lib/stores/videoStore.js +132 -0
  240. package/lib/stores/websocketStore.d.ts +1 -0
  241. package/lib/stores/websocketStore.d.ts.map +1 -0
  242. package/lib/stores/websocketStore.js +51 -2
  243. package/lib/types/apps.types.d.ts +17 -0
  244. package/lib/types/apps.types.d.ts.map +1 -0
  245. package/lib/types/apps.types.js +6 -0
  246. package/lib/types/common.types.d.ts +31 -0
  247. package/lib/types/common.types.d.ts.map +1 -0
  248. package/lib/types/common.types.js +6 -0
  249. package/lib/types/conversations.types.d.ts +17 -0
  250. package/lib/types/conversations.types.d.ts.map +1 -0
  251. package/lib/types/conversations.types.js +6 -0
  252. package/lib/types/edges.types.d.ts +38 -0
  253. package/lib/types/edges.types.d.ts.map +1 -0
  254. package/lib/types/edges.types.js +6 -0
  255. package/lib/types/groups.types.d.ts +19 -0
  256. package/lib/types/groups.types.d.ts.map +1 -0
  257. package/lib/types/groups.types.js +6 -0
  258. package/lib/types/index.d.ts +12 -0
  259. package/lib/types/index.d.ts.map +1 -0
  260. package/lib/types/index.js +12 -0
  261. package/lib/types/subscriptions.types.d.ts +32 -0
  262. package/lib/types/subscriptions.types.d.ts.map +1 -0
  263. package/lib/types/subscriptions.types.js +6 -0
  264. package/lib/types/videos.types.d.ts +61 -0
  265. package/lib/types/videos.types.d.ts.map +1 -0
  266. package/lib/types/videos.types.js +6 -0
  267. package/lib/utils/MetropolisProvider.d.ts +39 -15
  268. package/lib/utils/MetropolisProvider.d.ts.map +1 -0
  269. package/lib/utils/MetropolisProvider.js +31 -2
  270. package/lib/utils/actionFactory.d.ts +21 -19
  271. package/lib/utils/actionFactory.d.ts.map +1 -0
  272. package/lib/utils/actionFactory.js +95 -2
  273. package/lib/utils/api.d.ts +5 -4
  274. package/lib/utils/api.d.ts.map +1 -0
  275. package/lib/utils/api.js +252 -6
  276. package/lib/utils/app.d.ts +1 -0
  277. package/lib/utils/app.d.ts.map +1 -0
  278. package/lib/utils/app.js +15 -2
  279. package/lib/utils/baseActionFactory.d.ts +5 -23
  280. package/lib/utils/baseActionFactory.d.ts.map +1 -0
  281. package/lib/utils/baseActionFactory.js +72 -2
  282. package/lib/utils/configUtils.d.ts +11 -0
  283. package/lib/utils/configUtils.d.ts.map +1 -0
  284. package/lib/utils/configUtils.js +20 -0
  285. package/lib/utils/dateUtils.d.ts +1 -0
  286. package/lib/utils/dateUtils.d.ts.map +1 -0
  287. package/lib/utils/dateUtils.js +11 -2
  288. package/lib/utils/file.d.ts +3 -1
  289. package/lib/utils/file.d.ts.map +1 -0
  290. package/lib/utils/file.js +156 -2
  291. package/lib/utils/i18n.d.ts +7 -0
  292. package/lib/utils/i18n.d.ts.map +1 -0
  293. package/lib/utils/i18n.js +96 -0
  294. package/lib/utils/index.d.ts +20 -0
  295. package/lib/utils/index.d.ts.map +1 -0
  296. package/lib/utils/index.js +26 -0
  297. package/lib/utils/location.d.ts +4 -3
  298. package/lib/utils/location.d.ts.map +1 -0
  299. package/lib/utils/location.js +82 -2
  300. package/lib/utils/permissionUtils.d.ts +22 -0
  301. package/lib/utils/permissionUtils.d.ts.map +1 -0
  302. package/lib/utils/permissionUtils.js +72 -0
  303. package/lib/utils/requestCache.d.ts +8 -0
  304. package/lib/utils/requestCache.d.ts.map +1 -0
  305. package/lib/utils/requestCache.js +53 -0
  306. package/lib/utils/session.d.ts +14 -0
  307. package/lib/utils/session.d.ts.map +1 -0
  308. package/lib/utils/session.js +107 -0
  309. package/lib/utils/useConversationTyping.d.ts +26 -0
  310. package/lib/utils/useConversationTyping.d.ts.map +1 -0
  311. package/lib/utils/useConversationTyping.js +117 -0
  312. package/lib/utils/useMetropolis.d.ts +67 -12
  313. package/lib/utils/useMetropolis.d.ts.map +1 -0
  314. package/lib/utils/useMetropolis.js +311 -2
  315. package/lib/utils/useTranslations.d.ts +16 -0
  316. package/lib/utils/useTranslations.d.ts.map +1 -0
  317. package/lib/utils/useTranslations.js +105 -0
  318. package/lib/utils/validatorFactory.d.ts +3 -2
  319. package/lib/utils/validatorFactory.d.ts.map +1 -0
  320. package/lib/utils/validatorFactory.js +95 -2
  321. package/package.json +58 -17
  322. package/temp_signin.ts +73 -0
  323. package/lib/actions/profileActions/profileActions.d.ts +0 -50
  324. package/lib/actions/profileActions/profileActions.js +0 -2
  325. package/lib/adapters/profileAdapter/profileAdapter.d.ts +0 -32
  326. package/lib/adapters/profileAdapter/profileAdapter.js +0 -2
  327. package/lib/stores/profileStore.d.ts +0 -24
  328. package/lib/stores/profileStore.js +0 -2
  329. /package/{FACTORY_PATTERN_GUIDE.md → factoryPatternGuide.md} +0 -0
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Project details UX for books/chapters/outlines using MetropolisJS.
3
+ * Shows how to list project outline, books, and chapters, and add/edit items.
4
+ */
5
+ import {useEffect, useMemo, useState} from 'react';
6
+ import {Metropolis, useMetropolis} from '../src/index.js';
7
+
8
+ import type {FC} from 'react';
9
+ import type {PostType} from '../src/adapters/postAdapter/postAdapter.js';
10
+
11
+ type ChapterMap = Record<string, PostType[]>;
12
+
13
+ const ProjectShell: FC<{projectId: string}> = ({projectId}) => (
14
+ <Metropolis
15
+ config={{
16
+ development: {
17
+ environment: 'development',
18
+ app: {
19
+ api: {
20
+ url: 'http://localhost:3000/app',
21
+ public: 'http://localhost:3000/public'
22
+ }
23
+ }
24
+ }
25
+ }}
26
+ >
27
+ <ProjectDetails projectId={projectId} />
28
+ </Metropolis>
29
+ );
30
+
31
+ const ProjectDetails: FC<{projectId: string}> = ({projectId}) => {
32
+ const {postActions} = useMetropolis(['post']);
33
+ const [loading, setLoading] = useState(false);
34
+ const [books, setBooks] = useState<PostType[]>([]);
35
+ const [chapters, setChapters] = useState<ChapterMap>({});
36
+ const [outline, setOutline] = useState<PostType[]>([]);
37
+ const [bookName, setBookName] = useState('');
38
+ const [chapterName, setChapterName] = useState('');
39
+ const [chapterNumber, setChapterNumber] = useState<number | undefined>();
40
+ const [outlineText, setOutlineText] = useState('');
41
+ const [selectedBookId, setSelectedBookId] = useState<string | undefined>();
42
+
43
+ const selectedBook = useMemo(
44
+ () => books.find((book) => (book.bookId || book.postId) === selectedBookId),
45
+ [books, selectedBookId]
46
+ );
47
+
48
+ const loadData = async () => {
49
+ if(!projectId) {
50
+ return;
51
+ }
52
+ setLoading(true);
53
+ try {
54
+ const [projectOutline, projectBooks] = await Promise.all([
55
+ postActions.getProjectOutline(projectId, 0, 100, ['content', 'postId']),
56
+ postActions.getBooksByProject(projectId, 0, 100, ['bookId', 'bookName', 'postId', 'name'])
57
+ ]);
58
+ setOutline(projectOutline || []);
59
+ setBooks(projectBooks || []);
60
+ const chapterLists = await Promise.all(
61
+ (projectBooks || []).map((book) =>
62
+ postActions.getChaptersByBook(book.bookId || book.postId!, 0, 200, ['chapterNumber', 'name', 'postId'])
63
+ )
64
+ );
65
+ const chapterMap: ChapterMap = {};
66
+ projectBooks.forEach((book, index) => {
67
+ const bookId = book.bookId || book.postId || '';
68
+ chapterMap[bookId] = chapterLists[index] || [];
69
+ });
70
+ setChapters(chapterMap);
71
+ setSelectedBookId((current) => current || projectBooks[0]?.bookId || projectBooks[0]?.postId);
72
+ } finally {
73
+ setLoading(false);
74
+ }
75
+ };
76
+
77
+ useEffect(() => {
78
+ loadData();
79
+ // eslint-disable-next-line react-hooks/exhaustive-deps
80
+ }, [projectId]);
81
+
82
+ const handleAddBook = async () => {
83
+ if(!bookName.trim()) return;
84
+ await postActions.addBook(projectId, {name: bookName, bookName});
85
+ setBookName('');
86
+ await loadData();
87
+ };
88
+
89
+ const handleAddChapter = async () => {
90
+ if(!selectedBookId || !chapterName.trim()) return;
91
+ await postActions.addChapter(selectedBookId, {name: chapterName, chapterNumber});
92
+ setChapterName('');
93
+ setChapterNumber(undefined);
94
+ await loadData();
95
+ };
96
+
97
+ const handleAddOutline = async () => {
98
+ if(!outlineText.trim()) return;
99
+ await postActions.addProjectOutline(projectId, {content: outlineText});
100
+ setOutlineText('');
101
+ await loadData();
102
+ };
103
+
104
+ return (
105
+ <div style={styles.page}>
106
+ <header style={styles.hero}>
107
+ <div>
108
+ <p style={styles.eyebrow}>Project</p>
109
+ <h1 style={styles.title}>Project Details</h1>
110
+ <p style={styles.subtitle}>Books, chapters, and outlines managed through MetropolisJS.</p>
111
+ </div>
112
+ <div style={styles.status}>{loading ? 'Loading…' : 'Live'}</div>
113
+ </header>
114
+
115
+ <section style={styles.section}>
116
+ <div style={styles.card}>
117
+ <div style={styles.cardHeader}>
118
+ <h2 style={styles.cardTitle}>Project Outline</h2>
119
+ <button style={styles.buttonGhost} onClick={handleAddOutline}>Save outline</button>
120
+ </div>
121
+ <textarea
122
+ style={styles.textarea}
123
+ placeholder="Outline notes for the entire project…"
124
+ value={outlineText}
125
+ onChange={(e) => setOutlineText(e.target.value)}
126
+ />
127
+ <ul style={styles.list}>
128
+ {outline.map((item) => (
129
+ <li key={item.postId} style={styles.listItem}>
130
+ <div style={styles.listTitle}>{item.content || item.name}</div>
131
+ <div style={styles.listMeta}>#{item.postId}</div>
132
+ </li>
133
+ ))}
134
+ {!outline.length && <li style={styles.empty}>No outline yet.</li>}
135
+ </ul>
136
+ </div>
137
+
138
+ <div style={styles.card}>
139
+ <div style={styles.cardHeader}>
140
+ <h2 style={styles.cardTitle}>Add Book</h2>
141
+ </div>
142
+ <input
143
+ style={styles.input}
144
+ placeholder="Book title"
145
+ value={bookName}
146
+ onChange={(e) => setBookName(e.target.value)}
147
+ />
148
+ <button style={styles.buttonPrimary} onClick={handleAddBook}>Add book</button>
149
+ </div>
150
+ </section>
151
+
152
+ <section style={styles.section}>
153
+ <div style={styles.cardWide}>
154
+ <div style={styles.cardHeader}>
155
+ <h2 style={styles.cardTitle}>Books</h2>
156
+ <span style={styles.badge}>{books.length} total</span>
157
+ </div>
158
+ <div style={styles.columns}>
159
+ <div style={styles.column}>
160
+ <ul style={styles.list}>
161
+ {books.map((book) => {
162
+ const id = book.bookId || book.postId || '';
163
+ const isActive = id === selectedBookId;
164
+ return (
165
+ <li
166
+ key={id}
167
+ style={{...styles.listItem, ...(isActive ? styles.activeItem : {})}}
168
+ onClick={() => setSelectedBookId(id)}
169
+ >
170
+ <div style={styles.listTitle}>{book.bookName || book.name}</div>
171
+ <div style={styles.listMeta}>Book #{id}</div>
172
+ </li>
173
+ );
174
+ })}
175
+ {!books.length && <li style={styles.empty}>No books yet.</li>}
176
+ </ul>
177
+ </div>
178
+ <div style={styles.column}>
179
+ <div style={styles.cardHeader}>
180
+ <div>
181
+ <div style={styles.eyebrow}>Selected book</div>
182
+ <div style={styles.listTitle}>{selectedBook?.bookName || selectedBook?.name || 'Choose a book'}</div>
183
+ </div>
184
+ <div style={styles.badgeMuted}>{chapters[selectedBookId || '']?.length || 0} chapters</div>
185
+ </div>
186
+ <div style={styles.inlineForm}>
187
+ <input
188
+ style={styles.input}
189
+ placeholder="Chapter title"
190
+ value={chapterName}
191
+ onChange={(e) => setChapterName(e.target.value)}
192
+ />
193
+ <input
194
+ style={styles.input}
195
+ placeholder="Chapter #"
196
+ value={chapterNumber ?? ''}
197
+ onChange={(e) => setChapterNumber(e.target.value ? Number(e.target.value) : undefined)}
198
+ type="number"
199
+ min={1}
200
+ />
201
+ <button style={styles.buttonPrimary} onClick={handleAddChapter} disabled={!selectedBookId}>
202
+ Add chapter
203
+ </button>
204
+ </div>
205
+ <ul style={styles.list}>
206
+ {(chapters[selectedBookId || ''] || []).map((chapter) => (
207
+ <li key={chapter.postId} style={styles.listItem}>
208
+ <div style={styles.listTitle}>
209
+ <span style={styles.badgeMuted}>#{chapter.chapterNumber || '?'}</span> {chapter.name}
210
+ </div>
211
+ <div style={styles.listMeta}>Chapter ID {chapter.postId}</div>
212
+ </li>
213
+ ))}
214
+ {selectedBookId && !(chapters[selectedBookId] || []).length && (
215
+ <li style={styles.empty}>No chapters for this book yet.</li>
216
+ )}
217
+ </ul>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </section>
222
+ </div>
223
+ );
224
+ };
225
+
226
+ const styles: Record<string, React.CSSProperties> = {
227
+ page: {
228
+ fontFamily: '"Space Grotesk", "Helvetica Neue", Arial, sans-serif',
229
+ background: 'linear-gradient(135deg, #0f172a 0%, #111827 60%, #0b1021 100%)',
230
+ color: '#e5e7eb',
231
+ minHeight: '100vh',
232
+ padding: '32px',
233
+ lineHeight: 1.6
234
+ },
235
+ hero: {
236
+ display: 'flex',
237
+ alignItems: 'center',
238
+ justifyContent: 'space-between',
239
+ marginBottom: '24px'
240
+ },
241
+ title: {
242
+ fontSize: '28px',
243
+ margin: '4px 0 8px'
244
+ },
245
+ subtitle: {
246
+ color: '#94a3b8',
247
+ margin: 0
248
+ },
249
+ status: {
250
+ padding: '8px 12px',
251
+ background: '#22c55e',
252
+ color: '#0f172a',
253
+ borderRadius: '999px',
254
+ fontWeight: 700,
255
+ letterSpacing: '0.02em'
256
+ },
257
+ section: {
258
+ display: 'grid',
259
+ gap: '16px',
260
+ gridTemplateColumns: '2fr 1fr',
261
+ marginBottom: '16px'
262
+ },
263
+ card: {
264
+ background: 'rgba(255,255,255,0.03)',
265
+ border: '1px solid rgba(255,255,255,0.06)',
266
+ borderRadius: '16px',
267
+ padding: '16px',
268
+ backdropFilter: 'blur(4px)'
269
+ },
270
+ cardWide: {
271
+ background: 'rgba(255,255,255,0.03)',
272
+ border: '1px solid rgba(255,255,255,0.06)',
273
+ borderRadius: '16px',
274
+ padding: '16px',
275
+ backdropFilter: 'blur(4px)',
276
+ gridColumn: '1 / -1'
277
+ },
278
+ cardHeader: {
279
+ display: 'flex',
280
+ alignItems: 'center',
281
+ justifyContent: 'space-between',
282
+ marginBottom: '12px'
283
+ },
284
+ cardTitle: {
285
+ margin: 0,
286
+ fontSize: '18px',
287
+ letterSpacing: '0.01em'
288
+ },
289
+ buttonPrimary: {
290
+ background: 'linear-gradient(120deg, #22d3ee, #2563eb)',
291
+ color: '#0b1021',
292
+ border: 'none',
293
+ borderRadius: '12px',
294
+ padding: '10px 14px',
295
+ fontWeight: 700,
296
+ cursor: 'pointer'
297
+ },
298
+ buttonGhost: {
299
+ background: 'transparent',
300
+ color: '#e5e7eb',
301
+ border: '1px solid rgba(255,255,255,0.2)',
302
+ borderRadius: '12px',
303
+ padding: '8px 12px',
304
+ fontWeight: 600,
305
+ cursor: 'pointer'
306
+ },
307
+ input: {
308
+ width: '100%',
309
+ marginBottom: '8px',
310
+ padding: '10px 12px',
311
+ borderRadius: '10px',
312
+ border: '1px solid rgba(255,255,255,0.08)',
313
+ background: 'rgba(255,255,255,0.03)',
314
+ color: '#e5e7eb'
315
+ },
316
+ textarea: {
317
+ width: '100%',
318
+ minHeight: '120px',
319
+ padding: '12px',
320
+ borderRadius: '10px',
321
+ border: '1px solid rgba(255,255,255,0.08)',
322
+ background: 'rgba(255,255,255,0.03)',
323
+ color: '#e5e7eb'
324
+ },
325
+ list: {
326
+ listStyle: 'none',
327
+ padding: 0,
328
+ margin: '12px 0 0',
329
+ display: 'flex',
330
+ flexDirection: 'column',
331
+ gap: '8px'
332
+ },
333
+ listItem: {
334
+ padding: '10px 12px',
335
+ borderRadius: '10px',
336
+ border: '1px solid rgba(255,255,255,0.08)',
337
+ background: 'rgba(255,255,255,0.02)',
338
+ cursor: 'pointer'
339
+ },
340
+ activeItem: {
341
+ borderColor: '#22d3ee',
342
+ boxShadow: '0 8px 24px rgba(34, 211, 238, 0.15)'
343
+ },
344
+ listTitle: {
345
+ fontWeight: 700,
346
+ marginBottom: 4
347
+ },
348
+ listMeta: {
349
+ color: '#94a3b8',
350
+ fontSize: '12px'
351
+ },
352
+ empty: {
353
+ color: '#94a3b8',
354
+ fontStyle: 'italic'
355
+ },
356
+ badge: {
357
+ background: '#22d3ee',
358
+ color: '#0b1021',
359
+ borderRadius: '999px',
360
+ padding: '4px 10px',
361
+ fontWeight: 700,
362
+ fontSize: '12px'
363
+ },
364
+ badgeMuted: {
365
+ background: 'rgba(255,255,255,0.08)',
366
+ color: '#e5e7eb',
367
+ borderRadius: '999px',
368
+ padding: '4px 10px',
369
+ fontWeight: 700,
370
+ fontSize: '12px'
371
+ },
372
+ columns: {
373
+ display: 'grid',
374
+ gridTemplateColumns: '1fr 1fr',
375
+ gap: '12px'
376
+ },
377
+ column: {
378
+ display: 'flex',
379
+ flexDirection: 'column',
380
+ gap: '8px'
381
+ },
382
+ inlineForm: {
383
+ display: 'grid',
384
+ gridTemplateColumns: '2fr 1fr auto',
385
+ gap: '8px',
386
+ alignItems: 'center'
387
+ },
388
+ eyebrow: {
389
+ textTransform: 'uppercase',
390
+ letterSpacing: '0.08em',
391
+ fontSize: '12px',
392
+ color: '#94a3b8',
393
+ margin: 0
394
+ }
395
+ };
396
+
397
+ export default ProjectShell;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Copyright (c) 2025-Present, Nitrogen Labs, Inc.
3
+ * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
4
+ */
5
+
6
+ import {useState} from 'react';
7
+ import {Metropolis} from '../src/index';
8
+ import {i18n} from '../src/utils/i18n';
9
+
10
+ import type {FC} from 'react';
11
+
12
+ // GothamJS would import t from i18next like this:
13
+ // import {t as i18n} from 'i18next';
14
+
15
+ // Mock GothamJS component that uses the same i18next instance
16
+ const GothamJSComponent: FC = () => {
17
+ // In a real GothamJS component, you would import {t as i18n} from 'i18next'
18
+ // This gives you the same function name as MetropolisJS for consistency
19
+ // For this example, we'll simulate it by using the same i18n function
20
+ // that MetropolisJS uses, which internally uses i18next
21
+
22
+ return (
23
+ <div className="gotham-component">
24
+ <h2>{i18n('gotham_title')}</h2>
25
+ <p>{i18n('gotham_description')}</p>
26
+ <button>{i18n('gotham_action')}</button>
27
+ <span>{i18n('shared_text')}</span>
28
+ </div>
29
+ );
30
+ };
31
+
32
+ // Shared translations that both MetropolisJS and GothamJS can use
33
+ const sharedTranslations = {
34
+ // MetropolisJS specific translations
35
+ 'welcome': 'Welcome to MetropolisJS!',
36
+ 'save': 'Save',
37
+ 'cancel': 'Cancel',
38
+ 'loading': 'Loading...',
39
+
40
+ // GothamJS specific translations
41
+ 'gotham_title': 'GothamJS Component',
42
+ 'gotham_description': 'This component uses the same i18next instance',
43
+ 'gotham_action': 'Gotham Action',
44
+
45
+ // Shared translations that both libraries can use
46
+ 'shared_text': 'This text is shared between MetropolisJS and GothamJS',
47
+ 'common_button': 'Common Button',
48
+ 'error_message': 'An error occurred',
49
+ 'success_message': 'Operation completed successfully',
50
+
51
+ // Interpolation examples
52
+ 'hello_user': 'Hello {{name}}!',
53
+ 'items_count': 'You have {{count}} items',
54
+ 'welcome_back': 'Welcome back, {{name}}! You have {{count}} new messages.'
55
+ };
56
+
57
+ // Example showing how both libraries work together
58
+ const ProjectX: FC = () => {
59
+ return (
60
+ <Metropolis
61
+ translations={sharedTranslations}
62
+ config={{
63
+ development: {
64
+ environment: 'development',
65
+ app: {
66
+ api: {
67
+ url: 'https://api.projectx.com',
68
+ public: 'https://api.projectx.com/public'
69
+ },
70
+ urls: {
71
+ websocket: 'wss://ws.projectx.com'
72
+ }
73
+ }
74
+ }
75
+ }}
76
+ >
77
+ <div className="project-x">
78
+ {/* MetropolisJS components */}
79
+ <div className="metropolis-section">
80
+ <h1>{i18n('welcome')}</h1>
81
+ <p>{i18n('hello_user', {name: 'John'})}</p>
82
+ <button>{i18n('save')}</button>
83
+ <button>{i18n('cancel')}</button>
84
+ <p>{i18n('shared_text')}</p>
85
+ </div>
86
+
87
+ {/* GothamJS components - using the same translations */}
88
+ <GothamJSComponent />
89
+
90
+ {/* More shared usage */}
91
+ <div className="shared-section">
92
+ <button>{i18n('common_button')}</button>
93
+ <p>{i18n('items_count', {count: 5})}</p>
94
+ <p>{i18n('welcome_back', {name: 'Alice', count: 3})}</p>
95
+ </div>
96
+ </div>
97
+ </Metropolis>
98
+ );
99
+ };
100
+
101
+ // Example of how GothamJS would typically be used in a real project
102
+ const RealWorldExample: FC = () => {
103
+ // In a real project, GothamJS would be imported like this:
104
+ // import {GothamComponent} from '@nlabs/gothamjs';
105
+ // import {t} from 'i18next';
106
+
107
+ return (
108
+ <Metropolis
109
+ translations={{
110
+ 'metropolis_title': 'MetropolisJS Dashboard',
111
+ 'gotham_widget_title': 'GothamJS Widget',
112
+ 'shared_status': 'Status: {{status}}',
113
+ 'shared_user_info': 'User: {{username}} ({{role}})',
114
+ 'shared_actions_edit': 'Edit',
115
+ 'shared_actions_delete': 'Delete',
116
+ 'shared_actions_view': 'View',
117
+ 'shared_actions_create': 'Create'
118
+ }}
119
+ >
120
+ <div>
121
+ {/* MetropolisJS components */}
122
+ <header>
123
+ <h1>{i18n('metropolis_title')}</h1>
124
+ </header>
125
+
126
+ {/* GothamJS components would use the same translations */}
127
+ <div className="gotham-widget">
128
+ {/* In real GothamJS:
129
+ <GothamWidget
130
+ title={t('gotham_widget_title')}
131
+ status={t('shared_status', {status: 'active'})}
132
+ userInfo={t('shared_user_info', {username: 'john', role: 'admin'})}
133
+ actions={{
134
+ edit: t('shared_actions.edit'),
135
+ delete: t('shared_actions.delete'),
136
+ view: t('shared_actions.view')
137
+ }}
138
+ />
139
+ */}
140
+ </div>
141
+ </div>
142
+ </Metropolis>
143
+ );
144
+ };
145
+
146
+ // Example showing dynamic translation updates
147
+ const DynamicTranslationExample: FC = () => {
148
+ const [currentLanguage, setCurrentLanguage] = useState('en');
149
+
150
+ const translations = {
151
+ en: {
152
+ 'welcome': 'Welcome to ProjectX!',
153
+ 'gotham_title': 'GothamJS Component',
154
+ 'shared_text': 'Shared text in English'
155
+ },
156
+ es: {
157
+ 'welcome': '¡Bienvenido a ProjectX!',
158
+ 'gotham_title': 'Componente GothamJS',
159
+ 'shared_text': 'Texto compartido en español'
160
+ },
161
+ fr: {
162
+ 'welcome': 'Bienvenue sur ProjectX!',
163
+ 'gotham_title': 'Composant GothamJS',
164
+ 'shared_text': 'Texte partagé en français'
165
+ }
166
+ };
167
+
168
+ const handleLanguageChange = (lang: string) => {
169
+ setCurrentLanguage(lang);
170
+ // Both MetropolisJS and GothamJS would automatically
171
+ // use the new language since they share the same i18next instance
172
+ };
173
+
174
+ return (
175
+ <Metropolis translations={translations[currentLanguage]}>
176
+ <div>
177
+ <div className="language-selector">
178
+ <button onClick={() => handleLanguageChange('en')}>English</button>
179
+ <button onClick={() => handleLanguageChange('es')}>Español</button>
180
+ <button onClick={() => handleLanguageChange('fr')}>Français</button>
181
+ </div>
182
+
183
+ {/* MetropolisJS component */}
184
+ <div className="metropolis">
185
+ <h1>{i18n('welcome')}</h1>
186
+ </div>
187
+
188
+ {/* GothamJS component - automatically uses the same language */}
189
+ <div className="gotham">
190
+ <h2>{i18n('gotham_title')}</h2>
191
+ <p>{i18n('shared_text')}</p>
192
+ </div>
193
+ </div>
194
+ </Metropolis>
195
+ );
196
+ };
197
+
198
+ export {
199
+ DynamicTranslationExample,
200
+ GothamJSComponent, ProjectX,
201
+ RealWorldExample, sharedTranslations
202
+ };
203
+
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Example of handling signup errors in MetropolisJS
3
+ */
4
+ import {FluxFramework} from '@nlabs/arkhamjs';
5
+ import type {userActions} from '../src/actions/userActions/userActions';
6
+ import {USER_CONSTANTS} from '../src/stores/userStore';
7
+ import {createAction} from '../src/utils/actionFactory';
8
+
9
+ // Create a flux instance and user actions
10
+ const flux = new FluxFramework();
11
+ const userActions = createAction('user', flux) as unknown as userActions;
12
+
13
+ /**
14
+ * Example 1: Basic error handling with try/catch
15
+ */
16
+ const handleAddWithTryCatch = async (userData: any) => {
17
+ try {
18
+ const user = await userActions.addUser(userData);
19
+ console.log('Add successful:', user);
20
+ return {success: true, user};
21
+ } catch (error) {
22
+ console.error('Add failed:', error.message);
23
+ return {success: false, error: error.message};
24
+ }
25
+ };
26
+
27
+ /**
28
+ * Example 2: Using Flux store to handle errors
29
+ */
30
+ const handleSignUpWithFlux = async (userData: any) => {
31
+ // Set up a listener for signup success
32
+ const onSignUpSuccess = (payload: any) => {
33
+ console.log('Signup successful:', payload.user);
34
+ flux.removeListener(USER_CONSTANTS.SIGN_UP_SUCCESS, onSignUpSuccess);
35
+ flux.removeListener(USER_CONSTANTS.SIGN_UP_ERROR, onSignUpError);
36
+ };
37
+
38
+ // Set up a listener for signup error
39
+ const onSignUpError = (payload: any) => {
40
+ console.error('Signup failed:', payload.error.message);
41
+ flux.removeListener(USER_CONSTANTS.SIGN_UP_SUCCESS, onSignUpSuccess);
42
+ flux.removeListener(USER_CONSTANTS.SIGN_UP_ERROR, onSignUpError);
43
+ };
44
+
45
+ // Add listeners
46
+ flux.addListener(USER_CONSTANTS.SIGN_UP_SUCCESS, onSignUpSuccess);
47
+ flux.addListener(USER_CONSTANTS.SIGN_UP_ERROR, onSignUpError);
48
+
49
+ // Attempt signup
50
+ try {
51
+ await userActions.signUp(userData);
52
+ } catch (error) {
53
+ // Error is already handled by the flux listener
54
+ // This catch is just to prevent the error from propagating
55
+ }
56
+ };
57
+
58
+ /**
59
+ * Example 3: Custom validation before signup
60
+ */
61
+ const handleSignUpWithValidation = async (userData: any) => {
62
+ // Perform custom validation
63
+ const errors: string[] = [];
64
+
65
+ if (!userData.email || !userData.email.includes('@')) {
66
+ errors.push('Invalid email format');
67
+ }
68
+
69
+ if (!userData.password || userData.password.length < 8) {
70
+ errors.push('Password must be at least 8 characters');
71
+ }
72
+
73
+ if (!userData.username || userData.username.length < 3) {
74
+ errors.push('Username must be at least 3 characters');
75
+ }
76
+
77
+ // If validation fails, return errors
78
+ if (errors.length > 0) {
79
+ return {success: false, errors};
80
+ }
81
+
82
+ // If validation passes, attempt signup
83
+ try {
84
+ const user = await userActions.signUp(userData);
85
+ return {success: true, user};
86
+ } catch (error) {
87
+ // Handle specific error types
88
+ if (error.message.includes('already exists')) {
89
+ return {success: false, errors: ['Username or email already in use']};
90
+ }
91
+
92
+ return {success: false, errors: [error.message]};
93
+ }
94
+ };
95
+
96
+ // Export examples
97
+ export {
98
+ handleSignUpWithFlux, handleSignUpWithTryCatch, handleSignUpWithValidation
99
+ };
100
+