@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,889 @@
1
+ /**
2
+ * Copyright (c) 2019-Present, Nitrogen Labs, Inc.
3
+ * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
4
+ *
5
+ * Extensibility and Custom Field Examples for MetropolisJS
6
+ *
7
+ * This file demonstrates how to use extensible fields and custom adapters
8
+ * to extend MetropolisJS functionality with:
9
+ * - Custom data validation and transformation
10
+ * - Type-safe extensible fields
11
+ * - Custom business logic in adapters
12
+ * - Advanced validation patterns
13
+ * - Metadata and custom field management
14
+ */
15
+
16
+ import React, {useEffect, useState} from 'react';
17
+ import {useFlux} from '@nlabs/arkhamjs-utils-react';
18
+ import {
19
+ createPostActions,
20
+ createEventActions,
21
+ createGroupActions,
22
+ createUserActions,
23
+ type PostType,
24
+ type EventType,
25
+ type GroupType,
26
+ type PostActionsOptions,
27
+ type EventActionsOptions,
28
+ type GroupActionsOptions,
29
+ type BaseAdapterOptions
30
+ } from '@nlabs/metropolisjs';
31
+
32
+ // =============================================================================
33
+ // Custom Type Extensions
34
+ // =============================================================================
35
+
36
+ interface ExtendedPostType extends PostType {
37
+ metadata?: {
38
+ isSponsored?: boolean;
39
+ sponsorName?: string;
40
+ priority?: 'low' | 'medium' | 'high';
41
+ views?: number;
42
+ engagement?: {
43
+ likes?: number;
44
+ comments?: number;
45
+ shares?: number;
46
+ };
47
+ };
48
+ customFields?: {
49
+ campaign?: string;
50
+ targetAudience?: string[];
51
+ expiresAt?: number;
52
+ };
53
+ }
54
+
55
+ interface ExtendedEventType extends EventType {
56
+ metadata?: {
57
+ venue?: {
58
+ name: string;
59
+ address: string;
60
+ capacity: number;
61
+ };
62
+ ticketing?: {
63
+ price: number;
64
+ currency: string;
65
+ available: number;
66
+ };
67
+ categories?: string[];
68
+ };
69
+ customFields?: {
70
+ ageRestriction?: number;
71
+ dresscode?: string;
72
+ accessibility?: string[];
73
+ };
74
+ }
75
+
76
+ interface ExtendedGroupType extends GroupType {
77
+ metadata?: {
78
+ settings?: {
79
+ requiresApproval?: boolean;
80
+ allowInvites?: boolean;
81
+ isVerified?: boolean;
82
+ };
83
+ stats?: {
84
+ totalPosts?: number;
85
+ activeMembers?: number;
86
+ created?: number;
87
+ };
88
+ };
89
+ customFields?: {
90
+ industryTags?: string[];
91
+ companySize?: string;
92
+ website?: string;
93
+ };
94
+ }
95
+
96
+ // =============================================================================
97
+ // Custom Adapter Examples
98
+ // =============================================================================
99
+
100
+ export const SponsoredPostAdapterExample = () => {
101
+ const flux = useFlux();
102
+
103
+ const customPostAdapter = (input: unknown, options?: BaseAdapterOptions): ExtendedPostType => {
104
+ const post = input as ExtendedPostType;
105
+
106
+ if(post.metadata?.isSponsored) {
107
+ if(!post.metadata.sponsorName) {
108
+ throw new Error('Sponsored posts must include sponsorName in metadata');
109
+ }
110
+
111
+ if(!post.customFields?.campaign) {
112
+ throw new Error('Sponsored posts must include a campaign ID');
113
+ }
114
+
115
+ if(!post.metadata.priority) {
116
+ post.metadata.priority = 'medium';
117
+ }
118
+ }
119
+
120
+ if(post.customFields?.expiresAt) {
121
+ if(post.customFields.expiresAt < Date.now()) {
122
+ throw new Error('Expiration date must be in the future');
123
+ }
124
+ }
125
+
126
+ return post;
127
+ };
128
+
129
+ const postActions = createPostActions(flux, {
130
+ postAdapter: customPostAdapter
131
+ });
132
+
133
+ const [formData, setFormData] = useState<ExtendedPostType>({
134
+ content: '',
135
+ metadata: {
136
+ isSponsored: false,
137
+ sponsorName: '',
138
+ priority: 'medium'
139
+ },
140
+ customFields: {
141
+ campaign: '',
142
+ targetAudience: [],
143
+ expiresAt: undefined
144
+ }
145
+ });
146
+
147
+ const [error, setError] = useState<string | null>(null);
148
+ const [isSubmitting, setIsSubmitting] = useState(false);
149
+
150
+ const handleSubmit = async () => {
151
+ try {
152
+ setIsSubmitting(true);
153
+ setError(null);
154
+
155
+ const newPost = await postActions.add(formData, [
156
+ 'postId',
157
+ 'content',
158
+ 'metadata',
159
+ 'customFields',
160
+ 'createdAt'
161
+ ]);
162
+
163
+ console.log('Sponsored post created:', newPost);
164
+ setFormData({
165
+ content: '',
166
+ metadata: {isSponsored: false, sponsorName: '', priority: 'medium'},
167
+ customFields: {campaign: '', targetAudience: [], expiresAt: undefined}
168
+ });
169
+ } catch(err: any) {
170
+ setError(err.message || 'Failed to create sponsored post');
171
+ console.error('Error creating sponsored post:', err);
172
+ } finally {
173
+ setIsSubmitting(false);
174
+ }
175
+ };
176
+
177
+ return (
178
+ <div className="sponsored-post-creator">
179
+ <h2>Create Sponsored Post</h2>
180
+ {error && <div className="error">{error}</div>}
181
+
182
+ <textarea
183
+ placeholder="Post content"
184
+ value={formData.content}
185
+ onChange={(e) => setFormData({...formData, content: e.target.value})}
186
+ rows={4}
187
+ />
188
+
189
+ <label>
190
+ <input
191
+ type="checkbox"
192
+ checked={formData.metadata?.isSponsored}
193
+ onChange={(e) => setFormData({
194
+ ...formData,
195
+ metadata: {...formData.metadata, isSponsored: e.target.checked}
196
+ })}
197
+ />
198
+ This is a sponsored post
199
+ </label>
200
+
201
+ {formData.metadata?.isSponsored && (
202
+ <>
203
+ <input
204
+ type="text"
205
+ placeholder="Sponsor name (required)"
206
+ value={formData.metadata.sponsorName}
207
+ onChange={(e) => setFormData({
208
+ ...formData,
209
+ metadata: {...formData.metadata, sponsorName: e.target.value}
210
+ })}
211
+ />
212
+
213
+ <input
214
+ type="text"
215
+ placeholder="Campaign ID (required)"
216
+ value={formData.customFields?.campaign}
217
+ onChange={(e) => setFormData({
218
+ ...formData,
219
+ customFields: {...formData.customFields, campaign: e.target.value}
220
+ })}
221
+ />
222
+
223
+ <select
224
+ value={formData.metadata.priority}
225
+ onChange={(e) => setFormData({
226
+ ...formData,
227
+ metadata: {...formData.metadata, priority: e.target.value as any}
228
+ })}
229
+ >
230
+ <option value="low">Low Priority</option>
231
+ <option value="medium">Medium Priority</option>
232
+ <option value="high">High Priority</option>
233
+ </select>
234
+
235
+ <input
236
+ type="datetime-local"
237
+ placeholder="Expires at"
238
+ onChange={(e) => setFormData({
239
+ ...formData,
240
+ customFields: {
241
+ ...formData.customFields,
242
+ expiresAt: new Date(e.target.value).getTime()
243
+ }
244
+ })}
245
+ />
246
+ </>
247
+ )}
248
+
249
+ <button onClick={handleSubmit} disabled={isSubmitting || !formData.content}>
250
+ {isSubmitting ? 'Creating...' : 'Create Post'}
251
+ </button>
252
+ </div>
253
+ );
254
+ };
255
+
256
+ // =============================================================================
257
+ // Event Ticketing Adapter Example
258
+ // =============================================================================
259
+
260
+ export const EventTicketingAdapterExample = () => {
261
+ const flux = useFlux();
262
+
263
+ const customEventAdapter = (input: unknown, options?: BaseAdapterOptions): ExtendedEventType => {
264
+ const event = input as ExtendedEventType;
265
+
266
+ if(event.metadata?.ticketing) {
267
+ const {price, available} = event.metadata.ticketing;
268
+
269
+ if(price < 0) {
270
+ throw new Error('Ticket price cannot be negative');
271
+ }
272
+
273
+ if(available < 0) {
274
+ throw new Error('Available tickets cannot be negative');
275
+ }
276
+
277
+ if(event.metadata.venue?.capacity && available > event.metadata.venue.capacity) {
278
+ throw new Error('Available tickets cannot exceed venue capacity');
279
+ }
280
+
281
+ if(!event.metadata.ticketing.currency) {
282
+ event.metadata.ticketing.currency = 'USD';
283
+ }
284
+ }
285
+
286
+ if(event.customFields?.ageRestriction) {
287
+ if(event.customFields.ageRestriction < 0 || event.customFields.ageRestriction > 100) {
288
+ throw new Error('Age restriction must be between 0 and 100');
289
+ }
290
+ }
291
+
292
+ if(event.startDate && event.endDate && event.startDate > event.endDate) {
293
+ throw new Error('Event start date must be before end date');
294
+ }
295
+
296
+ return event;
297
+ };
298
+
299
+ const eventActions = createEventActions(flux, {
300
+ eventAdapter: customEventAdapter
301
+ });
302
+
303
+ const [formData, setFormData] = useState<ExtendedEventType>({
304
+ name: '',
305
+ content: '',
306
+ startDate: undefined,
307
+ endDate: undefined,
308
+ metadata: {
309
+ venue: {
310
+ name: '',
311
+ address: '',
312
+ capacity: 0
313
+ },
314
+ ticketing: {
315
+ price: 0,
316
+ currency: 'USD',
317
+ available: 0
318
+ },
319
+ categories: []
320
+ },
321
+ customFields: {
322
+ ageRestriction: undefined,
323
+ dresscode: '',
324
+ accessibility: []
325
+ }
326
+ });
327
+
328
+ const [error, setError] = useState<string | null>(null);
329
+ const [isSubmitting, setIsSubmitting] = useState(false);
330
+
331
+ const handleSubmit = async () => {
332
+ try {
333
+ setIsSubmitting(true);
334
+ setError(null);
335
+
336
+ const newEvent = await eventActions.addEvent(formData, [
337
+ 'eventId',
338
+ 'name',
339
+ 'content',
340
+ 'startDate',
341
+ 'endDate',
342
+ 'metadata',
343
+ 'customFields',
344
+ 'createdAt'
345
+ ]);
346
+
347
+ console.log('Ticketed event created:', newEvent);
348
+ } catch(err: any) {
349
+ setError(err.message || 'Failed to create event');
350
+ console.error('Error creating event:', err);
351
+ } finally {
352
+ setIsSubmitting(false);
353
+ }
354
+ };
355
+
356
+ return (
357
+ <div className="ticketed-event-creator">
358
+ <h2>Create Ticketed Event</h2>
359
+ {error && <div className="error">{error}</div>}
360
+
361
+ <input
362
+ type="text"
363
+ placeholder="Event name"
364
+ value={formData.name}
365
+ onChange={(e) => setFormData({...formData, name: e.target.value})}
366
+ />
367
+
368
+ <textarea
369
+ placeholder="Event description"
370
+ value={formData.content}
371
+ onChange={(e) => setFormData({...formData, content: e.target.value})}
372
+ rows={4}
373
+ />
374
+
375
+ <div className="dates">
376
+ <input
377
+ type="datetime-local"
378
+ onChange={(e) => setFormData({
379
+ ...formData,
380
+ startDate: new Date(e.target.value).getTime()
381
+ })}
382
+ />
383
+ <input
384
+ type="datetime-local"
385
+ onChange={(e) => setFormData({
386
+ ...formData,
387
+ endDate: new Date(e.target.value).getTime()
388
+ })}
389
+ />
390
+ </div>
391
+
392
+ <h3>Venue Information</h3>
393
+ <input
394
+ type="text"
395
+ placeholder="Venue name"
396
+ value={formData.metadata?.venue?.name}
397
+ onChange={(e) => setFormData({
398
+ ...formData,
399
+ metadata: {
400
+ ...formData.metadata,
401
+ venue: {...formData.metadata?.venue, name: e.target.value}
402
+ }
403
+ })}
404
+ />
405
+
406
+ <input
407
+ type="text"
408
+ placeholder="Venue address"
409
+ value={formData.metadata?.venue?.address}
410
+ onChange={(e) => setFormData({
411
+ ...formData,
412
+ metadata: {
413
+ ...formData.metadata,
414
+ venue: {...formData.metadata?.venue, address: e.target.value}
415
+ }
416
+ })}
417
+ />
418
+
419
+ <input
420
+ type="number"
421
+ placeholder="Venue capacity"
422
+ value={formData.metadata?.venue?.capacity}
423
+ onChange={(e) => setFormData({
424
+ ...formData,
425
+ metadata: {
426
+ ...formData.metadata,
427
+ venue: {...formData.metadata?.venue, capacity: parseInt(e.target.value)}
428
+ }
429
+ })}
430
+ />
431
+
432
+ <h3>Ticketing</h3>
433
+ <input
434
+ type="number"
435
+ placeholder="Ticket price"
436
+ value={formData.metadata?.ticketing?.price}
437
+ onChange={(e) => setFormData({
438
+ ...formData,
439
+ metadata: {
440
+ ...formData.metadata,
441
+ ticketing: {...formData.metadata?.ticketing, price: parseFloat(e.target.value)}
442
+ }
443
+ })}
444
+ step="0.01"
445
+ />
446
+
447
+ <select
448
+ value={formData.metadata?.ticketing?.currency}
449
+ onChange={(e) => setFormData({
450
+ ...formData,
451
+ metadata: {
452
+ ...formData.metadata,
453
+ ticketing: {...formData.metadata?.ticketing, currency: e.target.value}
454
+ }
455
+ })}
456
+ >
457
+ <option value="USD">USD</option>
458
+ <option value="EUR">EUR</option>
459
+ <option value="GBP">GBP</option>
460
+ <option value="JPY">JPY</option>
461
+ </select>
462
+
463
+ <input
464
+ type="number"
465
+ placeholder="Available tickets"
466
+ value={formData.metadata?.ticketing?.available}
467
+ onChange={(e) => setFormData({
468
+ ...formData,
469
+ metadata: {
470
+ ...formData.metadata,
471
+ ticketing: {...formData.metadata?.ticketing, available: parseInt(e.target.value)}
472
+ }
473
+ })}
474
+ />
475
+
476
+ <h3>Additional Information</h3>
477
+ <input
478
+ type="number"
479
+ placeholder="Age restriction (optional)"
480
+ value={formData.customFields?.ageRestriction || ''}
481
+ onChange={(e) => setFormData({
482
+ ...formData,
483
+ customFields: {...formData.customFields, ageRestriction: parseInt(e.target.value)}
484
+ })}
485
+ />
486
+
487
+ <input
488
+ type="text"
489
+ placeholder="Dress code (optional)"
490
+ value={formData.customFields?.dresscode}
491
+ onChange={(e) => setFormData({
492
+ ...formData,
493
+ customFields: {...formData.customFields, dresscode: e.target.value}
494
+ })}
495
+ />
496
+
497
+ <button onClick={handleSubmit} disabled={isSubmitting || !formData.name}>
498
+ {isSubmitting ? 'Creating...' : 'Create Event'}
499
+ </button>
500
+ </div>
501
+ );
502
+ };
503
+
504
+ // =============================================================================
505
+ // Group Settings Adapter Example
506
+ // =============================================================================
507
+
508
+ export const GroupSettingsAdapterExample = () => {
509
+ const flux = useFlux();
510
+
511
+ const customGroupAdapter = (input: unknown, options?: BaseAdapterOptions): ExtendedGroupType => {
512
+ const group = input as ExtendedGroupType;
513
+
514
+ if(group.metadata?.settings) {
515
+ const {requiresApproval, allowInvites, isVerified} = group.metadata.settings;
516
+
517
+ if(isVerified && group.privacy !== 'public') {
518
+ throw new Error('Verified groups must be public');
519
+ }
520
+
521
+ if(!allowInvites && requiresApproval) {
522
+ console.warn('Group does not allow invites but requires approval - may limit growth');
523
+ }
524
+ }
525
+
526
+ if(group.customFields?.industryTags) {
527
+ if(group.customFields.industryTags.length > 10) {
528
+ throw new Error('Cannot have more than 10 industry tags');
529
+ }
530
+ }
531
+
532
+ if(group.customFields?.website) {
533
+ try {
534
+ new URL(group.customFields.website);
535
+ } catch {
536
+ throw new Error('Invalid website URL format');
537
+ }
538
+ }
539
+
540
+ if(!group.metadata) {
541
+ group.metadata = {
542
+ settings: {
543
+ requiresApproval: false,
544
+ allowInvites: true,
545
+ isVerified: false
546
+ },
547
+ stats: {
548
+ totalPosts: 0,
549
+ activeMembers: 0,
550
+ created: Date.now()
551
+ }
552
+ };
553
+ }
554
+
555
+ return group;
556
+ };
557
+
558
+ const groupActions = createGroupActions(flux, {
559
+ groupAdapter: customGroupAdapter
560
+ });
561
+
562
+ const [formData, setFormData] = useState<ExtendedGroupType>({
563
+ name: '',
564
+ description: '',
565
+ privacy: 'public',
566
+ metadata: {
567
+ settings: {
568
+ requiresApproval: false,
569
+ allowInvites: true,
570
+ isVerified: false
571
+ }
572
+ },
573
+ customFields: {
574
+ industryTags: [],
575
+ companySize: '',
576
+ website: ''
577
+ }
578
+ });
579
+
580
+ const [error, setError] = useState<string | null>(null);
581
+ const [isSubmitting, setIsSubmitting] = useState(false);
582
+
583
+ const handleSubmit = async () => {
584
+ try {
585
+ setIsSubmitting(true);
586
+ setError(null);
587
+
588
+ const newGroup = await groupActions.add(formData, [
589
+ 'groupId',
590
+ 'name',
591
+ 'description',
592
+ 'privacy',
593
+ 'metadata',
594
+ 'customFields',
595
+ 'createdAt'
596
+ ]);
597
+
598
+ console.log('Group with custom settings created:', newGroup);
599
+ } catch(err: any) {
600
+ setError(err.message || 'Failed to create group');
601
+ console.error('Error creating group:', err);
602
+ } finally {
603
+ setIsSubmitting(false);
604
+ }
605
+ };
606
+
607
+ return (
608
+ <div className="group-settings-creator">
609
+ <h2>Create Professional Group</h2>
610
+ {error && <div className="error">{error}</div>}
611
+
612
+ <input
613
+ type="text"
614
+ placeholder="Group name"
615
+ value={formData.name}
616
+ onChange={(e) => setFormData({...formData, name: e.target.value})}
617
+ />
618
+
619
+ <textarea
620
+ placeholder="Group description"
621
+ value={formData.description}
622
+ onChange={(e) => setFormData({...formData, description: e.target.value})}
623
+ rows={3}
624
+ />
625
+
626
+ <select
627
+ value={formData.privacy}
628
+ onChange={(e) => setFormData({...formData, privacy: e.target.value})}
629
+ >
630
+ <option value="public">Public</option>
631
+ <option value="private">Private</option>
632
+ </select>
633
+
634
+ <h3>Group Settings</h3>
635
+ <label>
636
+ <input
637
+ type="checkbox"
638
+ checked={formData.metadata?.settings?.requiresApproval}
639
+ onChange={(e) => setFormData({
640
+ ...formData,
641
+ metadata: {
642
+ ...formData.metadata,
643
+ settings: {...formData.metadata?.settings, requiresApproval: e.target.checked}
644
+ }
645
+ })}
646
+ />
647
+ Requires approval to join
648
+ </label>
649
+
650
+ <label>
651
+ <input
652
+ type="checkbox"
653
+ checked={formData.metadata?.settings?.allowInvites}
654
+ onChange={(e) => setFormData({
655
+ ...formData,
656
+ metadata: {
657
+ ...formData.metadata,
658
+ settings: {...formData.metadata?.settings, allowInvites: e.target.checked}
659
+ }
660
+ })}
661
+ />
662
+ Allow member invites
663
+ </label>
664
+
665
+ <label>
666
+ <input
667
+ type="checkbox"
668
+ checked={formData.metadata?.settings?.isVerified}
669
+ onChange={(e) => setFormData({
670
+ ...formData,
671
+ metadata: {
672
+ ...formData.metadata,
673
+ settings: {...formData.metadata?.settings, isVerified: e.target.checked}
674
+ }
675
+ })}
676
+ disabled={formData.privacy !== 'public'}
677
+ />
678
+ Verified group (public only)
679
+ </label>
680
+
681
+ <h3>Professional Information</h3>
682
+ <input
683
+ type="text"
684
+ placeholder="Industry tags (comma-separated)"
685
+ onChange={(e) => setFormData({
686
+ ...formData,
687
+ customFields: {
688
+ ...formData.customFields,
689
+ industryTags: e.target.value.split(',').map(t => t.trim()).filter(t => t)
690
+ }
691
+ })}
692
+ />
693
+
694
+ <select
695
+ value={formData.customFields?.companySize}
696
+ onChange={(e) => setFormData({
697
+ ...formData,
698
+ customFields: {...formData.customFields, companySize: e.target.value}
699
+ })}
700
+ >
701
+ <option value="">Select company size</option>
702
+ <option value="1-10">1-10 employees</option>
703
+ <option value="11-50">11-50 employees</option>
704
+ <option value="51-200">51-200 employees</option>
705
+ <option value="201-500">201-500 employees</option>
706
+ <option value="501+">501+ employees</option>
707
+ </select>
708
+
709
+ <input
710
+ type="url"
711
+ placeholder="Website URL (optional)"
712
+ value={formData.customFields?.website}
713
+ onChange={(e) => setFormData({
714
+ ...formData,
715
+ customFields: {...formData.customFields, website: e.target.value}
716
+ })}
717
+ />
718
+
719
+ <button onClick={handleSubmit} disabled={isSubmitting || !formData.name}>
720
+ {isSubmitting ? 'Creating...' : 'Create Group'}
721
+ </button>
722
+ </div>
723
+ );
724
+ };
725
+
726
+ // =============================================================================
727
+ // Dynamic Adapter Switching Example
728
+ // =============================================================================
729
+
730
+ export const DynamicAdapterExample = () => {
731
+ const flux = useFlux();
732
+
733
+ const [adapterMode, setAdapterMode] = useState<'strict' | 'lenient'>('strict');
734
+
735
+ const strictAdapter = (input: unknown): PostType => {
736
+ const post = input as PostType;
737
+
738
+ if(!post.content || post.content.trim().length === 0) {
739
+ throw new Error('Content is required and cannot be empty');
740
+ }
741
+
742
+ if(post.content.length > 5000) {
743
+ throw new Error('Content cannot exceed 5000 characters');
744
+ }
745
+
746
+ if(post.content.includes('<script>')) {
747
+ throw new Error('Content contains disallowed HTML');
748
+ }
749
+
750
+ return post;
751
+ };
752
+
753
+ const lenientAdapter = (input: unknown): PostType => {
754
+ const post = input as PostType;
755
+
756
+ if(!post.content) {
757
+ post.content = '[No content]';
758
+ }
759
+
760
+ if(post.content.length > 5000) {
761
+ post.content = post.content.substring(0, 5000) + '...';
762
+ }
763
+
764
+ return post;
765
+ };
766
+
767
+ const postActions = createPostActions(flux, {
768
+ postAdapter: adapterMode === 'strict' ? strictAdapter : lenientAdapter
769
+ });
770
+
771
+ const [content, setContent] = useState('');
772
+ const [error, setError] = useState<string | null>(null);
773
+ const [isSubmitting, setIsSubmitting] = useState(false);
774
+
775
+ const handleSubmit = async () => {
776
+ try {
777
+ setIsSubmitting(true);
778
+ setError(null);
779
+
780
+ const newPost = await postActions.add({content}, ['postId', 'content', 'createdAt']);
781
+
782
+ console.log('Post created with', adapterMode, 'validation:', newPost);
783
+ setContent('');
784
+ } catch(err: any) {
785
+ setError(err.message || 'Failed to create post');
786
+ console.error('Error creating post:', err);
787
+ } finally {
788
+ setIsSubmitting(false);
789
+ }
790
+ };
791
+
792
+ return (
793
+ <div className="dynamic-adapter-example">
794
+ <h2>Dynamic Adapter Switching</h2>
795
+
796
+ <div className="mode-selector">
797
+ <label>
798
+ <input
799
+ type="radio"
800
+ value="strict"
801
+ checked={adapterMode === 'strict'}
802
+ onChange={() => setAdapterMode('strict')}
803
+ />
804
+ Strict Mode (Throws errors)
805
+ </label>
806
+ <label>
807
+ <input
808
+ type="radio"
809
+ value="lenient"
810
+ checked={adapterMode === 'lenient'}
811
+ onChange={() => setAdapterMode('lenient')}
812
+ />
813
+ Lenient Mode (Auto-corrects)
814
+ </label>
815
+ </div>
816
+
817
+ {error && <div className="error">{error}</div>}
818
+
819
+ <textarea
820
+ placeholder="Post content"
821
+ value={content}
822
+ onChange={(e) => setContent(e.target.value)}
823
+ rows={4}
824
+ />
825
+
826
+ <div className="info">
827
+ <small>
828
+ Current mode: {adapterMode === 'strict' ? 'Strict validation' : 'Auto-correction enabled'}
829
+ </small>
830
+ <small>Characters: {content.length}</small>
831
+ </div>
832
+
833
+ <button onClick={handleSubmit} disabled={isSubmitting}>
834
+ {isSubmitting ? 'Creating...' : 'Create Post'}
835
+ </button>
836
+ </div>
837
+ );
838
+ };
839
+
840
+ // =============================================================================
841
+ // Complete Extensibility Example
842
+ // =============================================================================
843
+
844
+ export const CompleteExtensibilityExample = () => {
845
+ const [activeExample, setActiveExample] = useState<'sponsored' | 'ticketing' | 'settings' | 'dynamic'>('sponsored');
846
+
847
+ return (
848
+ <div className="extensibility-examples">
849
+ <h1>MetropolisJS Extensibility Examples</h1>
850
+
851
+ <nav>
852
+ <button onClick={() => setActiveExample('sponsored')}>Sponsored Posts</button>
853
+ <button onClick={() => setActiveExample('ticketing')}>Event Ticketing</button>
854
+ <button onClick={() => setActiveExample('settings')}>Group Settings</button>
855
+ <button onClick={() => setActiveExample('dynamic')}>Dynamic Adapters</button>
856
+ </nav>
857
+
858
+ <main>
859
+ {activeExample === 'sponsored' && <SponsoredPostAdapterExample />}
860
+ {activeExample === 'ticketing' && <EventTicketingAdapterExample />}
861
+ {activeExample === 'settings' && <GroupSettingsAdapterExample />}
862
+ {activeExample === 'dynamic' && <DynamicAdapterExample />}
863
+ </main>
864
+
865
+ <section className="documentation">
866
+ <h2>Key Concepts</h2>
867
+ <ul>
868
+ <li>
869
+ <strong>Custom Adapters:</strong> Transform and validate data before API calls
870
+ </li>
871
+ <li>
872
+ <strong>Extensible Fields:</strong> Use metadata and customFields for domain-specific data
873
+ </li>
874
+ <li>
875
+ <strong>Type Safety:</strong> Extend base types for compile-time validation
876
+ </li>
877
+ <li>
878
+ <strong>Business Logic:</strong> Implement complex validation rules in adapters
879
+ </li>
880
+ <li>
881
+ <strong>Dynamic Behavior:</strong> Switch adapters at runtime for different modes
882
+ </li>
883
+ </ul>
884
+ </section>
885
+ </div>
886
+ );
887
+ };
888
+
889
+ export default CompleteExtensibilityExample;