@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,909 @@
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
+ * Connection and Relationship Management Examples for MetropolisJS
6
+ *
7
+ * This file demonstrates how to manage relationships between collections using:
8
+ * - Connection edges (followers, members, admins, etc.)
9
+ * - Reactions (likes, pins, views, RSVPs, etc.)
10
+ * - Tags (categorization and labeling)
11
+ * - File attachments (images, videos, documents)
12
+ * - Conversation associations (linking items to discussions)
13
+ */
14
+
15
+ import React, {useEffect, useState} from 'react';
16
+ import {useFlux} from '@nlabs/arkhamjs-utils-react';
17
+ import {
18
+ createConnectionActions,
19
+ useReactionActions,
20
+ useTagActions,
21
+ useImageActions,
22
+ usePostActions,
23
+ COLLECTIONS,
24
+ CONNECTION_TYPES,
25
+ REACTION_TYPES,
26
+ type ConnectionEdge,
27
+ type ReactionType,
28
+ type TagType
29
+ } from '@nlabs/metropolisjs';
30
+
31
+ // =============================================================================
32
+ // Connection Management Examples
33
+ // =============================================================================
34
+
35
+ export const FollowSystemExample = () => {
36
+ const flux = useFlux();
37
+ const connectionActions = createConnectionActions(flux);
38
+ const [followers, setFollowers] = useState<ConnectionEdge[]>([]);
39
+ const [following, setFollowing] = useState<ConnectionEdge[]>([]);
40
+ const [isLoading, setIsLoading] = useState(false);
41
+ const [error, setError] = useState<string | null>(null);
42
+
43
+ const currentUserId = 'user-123';
44
+
45
+ useEffect(() => {
46
+ loadConnections();
47
+ }, []);
48
+
49
+ const loadConnections = async () => {
50
+ try {
51
+ setIsLoading(true);
52
+ setError(null);
53
+
54
+ const followerList = await connectionActions.getConnections(
55
+ COLLECTIONS.USERS,
56
+ currentUserId,
57
+ COLLECTIONS.USERS,
58
+ {connectionType: CONNECTION_TYPES.FOLLOWER}
59
+ );
60
+
61
+ const followingList = await connectionActions.getConnections(
62
+ COLLECTIONS.USERS,
63
+ currentUserId,
64
+ COLLECTIONS.USERS,
65
+ {connectionType: CONNECTION_TYPES.FOLLOWING}
66
+ );
67
+
68
+ setFollowers(followerList);
69
+ setFollowing(followingList);
70
+ } catch(err: any) {
71
+ setError(err.message || 'Failed to load connections');
72
+ console.error('Error loading connections:', err);
73
+ } finally {
74
+ setIsLoading(false);
75
+ }
76
+ };
77
+
78
+ const handleFollowUser = async (targetUserId: string) => {
79
+ try {
80
+ setError(null);
81
+
82
+ await connectionActions.addConnection(
83
+ COLLECTIONS.USERS,
84
+ currentUserId,
85
+ COLLECTIONS.USERS,
86
+ targetUserId,
87
+ CONNECTION_TYPES.FOLLOWING
88
+ );
89
+
90
+ await loadConnections();
91
+ } catch(err: any) {
92
+ setError(err.message || 'Failed to follow user');
93
+ console.error('Error following user:', err);
94
+ }
95
+ };
96
+
97
+ const handleUnfollowUser = async (targetUserId: string) => {
98
+ try {
99
+ setError(null);
100
+
101
+ await connectionActions.removeConnection(
102
+ COLLECTIONS.USERS,
103
+ currentUserId,
104
+ COLLECTIONS.USERS,
105
+ targetUserId
106
+ );
107
+
108
+ await loadConnections();
109
+ } catch(err: any) {
110
+ setError(err.message || 'Failed to unfollow user');
111
+ console.error('Error unfollowing user:', err);
112
+ }
113
+ };
114
+
115
+ if(isLoading) return <div>Loading connections...</div>;
116
+
117
+ return (
118
+ <div className="follow-system">
119
+ <h2>Follow System</h2>
120
+ {error && <div className="error">{error}</div>}
121
+
122
+ <div className="stats">
123
+ <div>
124
+ <strong>Followers:</strong> {followers.length}
125
+ </div>
126
+ <div>
127
+ <strong>Following:</strong> {following.length}
128
+ </div>
129
+ </div>
130
+
131
+ <div className="follow-button-example">
132
+ <button onClick={() => handleFollowUser('user-456')}>
133
+ Follow User
134
+ </button>
135
+ <button onClick={() => handleUnfollowUser('user-456')}>
136
+ Unfollow User
137
+ </button>
138
+ </div>
139
+
140
+ <div className="followers-list">
141
+ <h3>Followers</h3>
142
+ <ul>
143
+ {followers.map(connection => (
144
+ <li key={connection._from}>
145
+ User: {connection.fromId}
146
+ </li>
147
+ ))}
148
+ </ul>
149
+ </div>
150
+
151
+ <div className="following-list">
152
+ <h3>Following</h3>
153
+ <ul>
154
+ {following.map(connection => (
155
+ <li key={connection._to}>
156
+ User: {connection.toId}
157
+ </li>
158
+ ))}
159
+ </ul>
160
+ </div>
161
+ </div>
162
+ );
163
+ };
164
+
165
+ export const GroupMembershipExample = () => {
166
+ const flux = useFlux();
167
+ const connectionActions = createConnectionActions(flux);
168
+ const [members, setMembers] = useState<ConnectionEdge[]>([]);
169
+ const [admins, setAdmins] = useState<ConnectionEdge[]>([]);
170
+ const [error, setError] = useState<string | null>(null);
171
+
172
+ const groupId = 'group-123';
173
+ const currentUserId = 'user-123';
174
+
175
+ useEffect(() => {
176
+ loadGroupMembers();
177
+ }, []);
178
+
179
+ const loadGroupMembers = async () => {
180
+ try {
181
+ setError(null);
182
+
183
+ const allConnections = await connectionActions.getConnections(
184
+ COLLECTIONS.GROUPS,
185
+ groupId,
186
+ COLLECTIONS.USERS
187
+ );
188
+
189
+ const memberList = allConnections.filter(c => c.connectionType === CONNECTION_TYPES.MEMBER);
190
+ const adminList = allConnections.filter(c => c.connectionType === CONNECTION_TYPES.ADMIN);
191
+
192
+ setMembers(memberList);
193
+ setAdmins(adminList);
194
+ } catch(err: any) {
195
+ setError(err.message || 'Failed to load group members');
196
+ console.error('Error loading group members:', err);
197
+ }
198
+ };
199
+
200
+ const handleJoinGroup = async () => {
201
+ try {
202
+ setError(null);
203
+
204
+ await connectionActions.addConnection(
205
+ COLLECTIONS.GROUPS,
206
+ groupId,
207
+ COLLECTIONS.USERS,
208
+ currentUserId,
209
+ CONNECTION_TYPES.MEMBER
210
+ );
211
+
212
+ await loadGroupMembers();
213
+ } catch(err: any) {
214
+ setError(err.message || 'Failed to join group');
215
+ console.error('Error joining group:', err);
216
+ }
217
+ };
218
+
219
+ const handleLeaveGroup = async () => {
220
+ try {
221
+ setError(null);
222
+
223
+ await connectionActions.removeConnection(
224
+ COLLECTIONS.GROUPS,
225
+ groupId,
226
+ COLLECTIONS.USERS,
227
+ currentUserId
228
+ );
229
+
230
+ await loadGroupMembers();
231
+ } catch(err: any) {
232
+ setError(err.message || 'Failed to leave group');
233
+ console.error('Error leaving group:', err);
234
+ }
235
+ };
236
+
237
+ const handlePromoteToAdmin = async (userId: string) => {
238
+ try {
239
+ setError(null);
240
+
241
+ await connectionActions.removeConnection(
242
+ COLLECTIONS.GROUPS,
243
+ groupId,
244
+ COLLECTIONS.USERS,
245
+ userId
246
+ );
247
+
248
+ await connectionActions.addConnection(
249
+ COLLECTIONS.GROUPS,
250
+ groupId,
251
+ COLLECTIONS.USERS,
252
+ userId,
253
+ CONNECTION_TYPES.ADMIN
254
+ );
255
+
256
+ await loadGroupMembers();
257
+ } catch(err: any) {
258
+ setError(err.message || 'Failed to promote user');
259
+ console.error('Error promoting user:', err);
260
+ }
261
+ };
262
+
263
+ return (
264
+ <div className="group-membership">
265
+ <h2>Group Membership</h2>
266
+ {error && <div className="error">{error}</div>}
267
+
268
+ <div className="stats">
269
+ <div><strong>Members:</strong> {members.length}</div>
270
+ <div><strong>Admins:</strong> {admins.length}</div>
271
+ </div>
272
+
273
+ <div className="actions">
274
+ <button onClick={handleJoinGroup}>Join Group</button>
275
+ <button onClick={handleLeaveGroup}>Leave Group</button>
276
+ </div>
277
+
278
+ <div className="member-lists">
279
+ <div>
280
+ <h3>Admins</h3>
281
+ <ul>
282
+ {admins.map(admin => (
283
+ <li key={admin.toId}>{admin.toId}</li>
284
+ ))}
285
+ </ul>
286
+ </div>
287
+
288
+ <div>
289
+ <h3>Members</h3>
290
+ <ul>
291
+ {members.map(member => (
292
+ <li key={member.toId}>
293
+ {member.toId}
294
+ <button onClick={() => handlePromoteToAdmin(member.toId)}>
295
+ Promote to Admin
296
+ </button>
297
+ </li>
298
+ ))}
299
+ </ul>
300
+ </div>
301
+ </div>
302
+ </div>
303
+ );
304
+ };
305
+
306
+ export const BlockingExample = () => {
307
+ const flux = useFlux();
308
+ const connectionActions = createConnectionActions(flux);
309
+ const [blockedUsers, setBlockedUsers] = useState<ConnectionEdge[]>([]);
310
+ const [error, setError] = useState<string | null>(null);
311
+
312
+ const currentUserId = 'user-123';
313
+
314
+ useEffect(() => {
315
+ loadBlockedUsers();
316
+ }, []);
317
+
318
+ const loadBlockedUsers = async () => {
319
+ try {
320
+ setError(null);
321
+
322
+ const blocked = await connectionActions.getConnections(
323
+ COLLECTIONS.USERS,
324
+ currentUserId,
325
+ COLLECTIONS.USERS,
326
+ {connectionType: CONNECTION_TYPES.BLOCKED}
327
+ );
328
+
329
+ setBlockedUsers(blocked);
330
+ } catch(err: any) {
331
+ setError(err.message || 'Failed to load blocked users');
332
+ console.error('Error loading blocked users:', err);
333
+ }
334
+ };
335
+
336
+ const handleBlockUser = async (targetUserId: string) => {
337
+ try {
338
+ setError(null);
339
+
340
+ await connectionActions.addConnection(
341
+ COLLECTIONS.USERS,
342
+ currentUserId,
343
+ COLLECTIONS.USERS,
344
+ targetUserId,
345
+ CONNECTION_TYPES.BLOCKED,
346
+ {blockedAt: Date.now(), reason: 'User preference'}
347
+ );
348
+
349
+ await loadBlockedUsers();
350
+ } catch(err: any) {
351
+ setError(err.message || 'Failed to block user');
352
+ console.error('Error blocking user:', err);
353
+ }
354
+ };
355
+
356
+ const handleUnblockUser = async (targetUserId: string) => {
357
+ try {
358
+ setError(null);
359
+
360
+ await connectionActions.removeConnection(
361
+ COLLECTIONS.USERS,
362
+ currentUserId,
363
+ COLLECTIONS.USERS,
364
+ targetUserId
365
+ );
366
+
367
+ await loadBlockedUsers();
368
+ } catch(err: any) {
369
+ setError(err.message || 'Failed to unblock user');
370
+ console.error('Error unblocking user:', err);
371
+ }
372
+ };
373
+
374
+ return (
375
+ <div className="blocking-example">
376
+ <h2>Blocked Users</h2>
377
+ {error && <div className="error">{error}</div>}
378
+
379
+ <p>You have blocked {blockedUsers.length} user(s)</p>
380
+
381
+ <ul>
382
+ {blockedUsers.map(blocked => (
383
+ <li key={blocked.toId}>
384
+ User: {blocked.toId}
385
+ {blocked.metadata?.reason && <span> (Reason: {blocked.metadata.reason})</span>}
386
+ <button onClick={() => handleUnblockUser(blocked.toId)}>
387
+ Unblock
388
+ </button>
389
+ </li>
390
+ ))}
391
+ </ul>
392
+
393
+ <div className="block-form">
394
+ <button onClick={() => handleBlockUser('user-789')}>
395
+ Block Example User
396
+ </button>
397
+ </div>
398
+ </div>
399
+ );
400
+ };
401
+
402
+ // =============================================================================
403
+ // Reaction Management Examples
404
+ // =============================================================================
405
+
406
+ export const LikeSystemExample = ({postId}: {postId: string}) => {
407
+ const reactionActions = useReactionActions();
408
+ const [likeCount, setLikeCount] = useState(0);
409
+ const [hasLiked, setHasLiked] = useState(false);
410
+ const [error, setError] = useState<string | null>(null);
411
+
412
+ useEffect(() => {
413
+ loadReactionState();
414
+ }, [postId]);
415
+
416
+ const loadReactionState = async () => {
417
+ try {
418
+ setError(null);
419
+
420
+ const count = await reactionActions.getReactionCount(
421
+ postId,
422
+ COLLECTIONS.POSTS,
423
+ REACTION_TYPES.LIKE
424
+ );
425
+
426
+ const liked = await reactionActions.hasReaction(
427
+ postId,
428
+ COLLECTIONS.POSTS,
429
+ REACTION_TYPES.LIKE,
430
+ 'inbound'
431
+ );
432
+
433
+ setLikeCount(count);
434
+ setHasLiked(liked);
435
+ } catch(err: any) {
436
+ setError(err.message || 'Failed to load reaction state');
437
+ console.error('Error loading reaction state:', err);
438
+ }
439
+ };
440
+
441
+ const handleToggleLike = async () => {
442
+ try {
443
+ setError(null);
444
+
445
+ if(hasLiked) {
446
+ await reactionActions.deleteReaction(
447
+ postId,
448
+ COLLECTIONS.POSTS,
449
+ REACTION_TYPES.LIKE
450
+ );
451
+ } else {
452
+ await reactionActions.addReaction(
453
+ postId,
454
+ COLLECTIONS.POSTS,
455
+ {
456
+ name: REACTION_TYPES.LIKE,
457
+ value: 1
458
+ }
459
+ );
460
+ }
461
+
462
+ await loadReactionState();
463
+ } catch(err: any) {
464
+ setError(err.message || 'Failed to toggle like');
465
+ console.error('Error toggling like:', err);
466
+ }
467
+ };
468
+
469
+ return (
470
+ <div className="like-system">
471
+ {error && <div className="error">{error}</div>}
472
+ <button
473
+ onClick={handleToggleLike}
474
+ className={hasLiked ? 'liked' : ''}
475
+ >
476
+ {hasLiked ? '❤️ Liked' : '🤍 Like'} ({reactionActions.abbreviateCount(likeCount)})
477
+ </button>
478
+ </div>
479
+ );
480
+ };
481
+
482
+ export const PinSystemExample = ({postId}: {postId: string}) => {
483
+ const reactionActions = useReactionActions();
484
+ const [isPinned, setIsPinned] = useState(false);
485
+ const [error, setError] = useState<string | null>(null);
486
+
487
+ useEffect(() => {
488
+ checkPinStatus();
489
+ }, [postId]);
490
+
491
+ const checkPinStatus = async () => {
492
+ try {
493
+ setError(null);
494
+
495
+ const pinned = await reactionActions.hasReaction(
496
+ postId,
497
+ COLLECTIONS.POSTS,
498
+ REACTION_TYPES.PIN,
499
+ 'inbound'
500
+ );
501
+
502
+ setIsPinned(pinned);
503
+ } catch(err: any) {
504
+ setError(err.message || 'Failed to check pin status');
505
+ console.error('Error checking pin status:', err);
506
+ }
507
+ };
508
+
509
+ const handleTogglePin = async () => {
510
+ try {
511
+ setError(null);
512
+
513
+ if(isPinned) {
514
+ await reactionActions.deleteReaction(
515
+ postId,
516
+ COLLECTIONS.POSTS,
517
+ REACTION_TYPES.PIN
518
+ );
519
+ } else {
520
+ await reactionActions.addReaction(
521
+ postId,
522
+ COLLECTIONS.POSTS,
523
+ {
524
+ name: REACTION_TYPES.PIN,
525
+ value: 1
526
+ }
527
+ );
528
+ }
529
+
530
+ await checkPinStatus();
531
+ } catch(err: any) {
532
+ setError(err.message || 'Failed to toggle pin');
533
+ console.error('Error toggling pin:', err);
534
+ }
535
+ };
536
+
537
+ return (
538
+ <div className="pin-system">
539
+ {error && <div className="error">{error}</div>}
540
+ <button onClick={handleTogglePin}>
541
+ {isPinned ? '📌 Unpin' : '📍 Pin'}
542
+ </button>
543
+ </div>
544
+ );
545
+ };
546
+
547
+ export const RSVPSystemExample = ({eventId}: {eventId: string}) => {
548
+ const reactionActions = useReactionActions();
549
+ const [rsvpStatus, setRsvpStatus] = useState<'none' | 'going' | 'maybe' | 'not-going'>('none');
550
+ const [goingCount, setGoingCount] = useState(0);
551
+ const [error, setError] = useState<string | null>(null);
552
+
553
+ useEffect(() => {
554
+ loadRSVPState();
555
+ }, [eventId]);
556
+
557
+ const loadRSVPState = async () => {
558
+ try {
559
+ setError(null);
560
+
561
+ const hasRsvp = await reactionActions.hasReaction(
562
+ eventId,
563
+ COLLECTIONS.POSTS,
564
+ REACTION_TYPES.RSVP,
565
+ 'inbound'
566
+ );
567
+
568
+ const count = await reactionActions.getReactionCount(
569
+ eventId,
570
+ COLLECTIONS.POSTS,
571
+ REACTION_TYPES.RSVP
572
+ );
573
+
574
+ setGoingCount(count);
575
+ } catch(err: any) {
576
+ setError(err.message || 'Failed to load RSVP state');
577
+ console.error('Error loading RSVP state:', err);
578
+ }
579
+ };
580
+
581
+ const handleRSVP = async (status: 'going' | 'maybe' | 'not-going') => {
582
+ try {
583
+ setError(null);
584
+
585
+ if(rsvpStatus !== 'none') {
586
+ await reactionActions.deleteReaction(
587
+ eventId,
588
+ COLLECTIONS.POSTS,
589
+ REACTION_TYPES.RSVP
590
+ );
591
+ }
592
+
593
+ if(status !== 'not-going') {
594
+ await reactionActions.addReaction(
595
+ eventId,
596
+ COLLECTIONS.POSTS,
597
+ {
598
+ name: REACTION_TYPES.RSVP,
599
+ value: status === 'going' ? 1 : 0.5
600
+ }
601
+ );
602
+ }
603
+
604
+ setRsvpStatus(status);
605
+ await loadRSVPState();
606
+ } catch(err: any) {
607
+ setError(err.message || 'Failed to RSVP');
608
+ console.error('Error RSVP:', err);
609
+ }
610
+ };
611
+
612
+ return (
613
+ <div className="rsvp-system">
614
+ <h3>RSVP to Event</h3>
615
+ {error && <div className="error">{error}</div>}
616
+
617
+ <div className="rsvp-count">
618
+ {goingCount} people going
619
+ </div>
620
+
621
+ <div className="rsvp-buttons">
622
+ <button
623
+ onClick={() => handleRSVP('going')}
624
+ className={rsvpStatus === 'going' ? 'active' : ''}
625
+ >
626
+ ✅ Going
627
+ </button>
628
+ <button
629
+ onClick={() => handleRSVP('maybe')}
630
+ className={rsvpStatus === 'maybe' ? 'active' : ''}
631
+ >
632
+ 🤔 Maybe
633
+ </button>
634
+ <button
635
+ onClick={() => handleRSVP('not-going')}
636
+ className={rsvpStatus === 'not-going' ? 'active' : ''}
637
+ >
638
+ ❌ Can't Go
639
+ </button>
640
+ </div>
641
+ </div>
642
+ );
643
+ };
644
+
645
+ export const ViewCountExample = ({postId}: {postId: string}) => {
646
+ const reactionActions = useReactionActions();
647
+ const [viewCount, setViewCount] = useState(0);
648
+ const [error, setError] = useState<string | null>(null);
649
+
650
+ useEffect(() => {
651
+ trackView();
652
+ }, [postId]);
653
+
654
+ const trackView = async () => {
655
+ try {
656
+ setError(null);
657
+
658
+ await reactionActions.addReaction(
659
+ postId,
660
+ COLLECTIONS.POSTS,
661
+ {
662
+ name: REACTION_TYPES.VIEW,
663
+ value: 1
664
+ }
665
+ );
666
+
667
+ const count = await reactionActions.getReactionCount(
668
+ postId,
669
+ COLLECTIONS.POSTS,
670
+ REACTION_TYPES.VIEW
671
+ );
672
+
673
+ setViewCount(count);
674
+ } catch(err: any) {
675
+ setError(err.message || 'Failed to track view');
676
+ console.error('Error tracking view:', err);
677
+ }
678
+ };
679
+
680
+ return (
681
+ <div className="view-count">
682
+ {error && <div className="error">{error}</div>}
683
+ <span>👁️ {reactionActions.abbreviateCount(viewCount)} views</span>
684
+ </div>
685
+ );
686
+ };
687
+
688
+ // =============================================================================
689
+ // Tag Management Examples
690
+ // =============================================================================
691
+
692
+ export const TaggingExample = ({postId}: {postId: string}) => {
693
+ const tagActions = useTagActions();
694
+ const [tags, setTags] = useState<TagType[]>([]);
695
+ const [newTag, setNewTag] = useState('');
696
+ const [error, setError] = useState<string | null>(null);
697
+
698
+ useEffect(() => {
699
+ loadTags();
700
+ }, [postId]);
701
+
702
+ const loadTags = async () => {
703
+ try {
704
+ setError(null);
705
+
706
+ const loadedTags = await tagActions.getTags(['tagId', 'name', 'category']);
707
+ const postTags = loadedTags.filter((tag: any) => tag.itemId === postId);
708
+ setTags(postTags);
709
+ } catch(err: any) {
710
+ setError(err.message || 'Failed to load tags');
711
+ console.error('Error loading tags:', err);
712
+ }
713
+ };
714
+
715
+ const handleAddTag = async () => {
716
+ try {
717
+ setError(null);
718
+
719
+ await tagActions.addTag({
720
+ itemId: postId,
721
+ itemType: COLLECTIONS.POSTS,
722
+ name: newTag,
723
+ category: 'general'
724
+ }, ['tagId', 'name', 'category']);
725
+
726
+ setNewTag('');
727
+ await loadTags();
728
+ } catch(err: any) {
729
+ setError(err.message || 'Failed to add tag');
730
+ console.error('Error adding tag:', err);
731
+ }
732
+ };
733
+
734
+ const handleRemoveTag = async (tagId: string) => {
735
+ try {
736
+ setError(null);
737
+
738
+ await tagActions.deleteTag(tagId);
739
+ await loadTags();
740
+ } catch(err: any) {
741
+ setError(err.message || 'Failed to remove tag');
742
+ console.error('Error removing tag:', err);
743
+ }
744
+ };
745
+
746
+ return (
747
+ <div className="tagging-system">
748
+ <h3>Tags</h3>
749
+ {error && <div className="error">{error}</div>}
750
+
751
+ <div className="tags">
752
+ {tags.map(tag => (
753
+ <span key={tag.tagId} className="tag">
754
+ #{tag.name}
755
+ <button onClick={() => handleRemoveTag(tag.tagId!)}>×</button>
756
+ </span>
757
+ ))}
758
+ </div>
759
+
760
+ <div className="add-tag">
761
+ <input
762
+ type="text"
763
+ value={newTag}
764
+ onChange={(e) => setNewTag(e.target.value)}
765
+ placeholder="Add tag..."
766
+ />
767
+ <button onClick={handleAddTag} disabled={!newTag}>
768
+ Add Tag
769
+ </button>
770
+ </div>
771
+ </div>
772
+ );
773
+ };
774
+
775
+ // =============================================================================
776
+ // File Attachment Examples
777
+ // =============================================================================
778
+
779
+ export const ImageAttachmentExample = ({postId}: {postId: string}) => {
780
+ const imageActions = useImageActions();
781
+ const postActions = usePostActions();
782
+ const [images, setImages] = useState<any[]>([]);
783
+ const [isUploading, setIsUploading] = useState(false);
784
+ const [error, setError] = useState<string | null>(null);
785
+
786
+ useEffect(() => {
787
+ loadImages();
788
+ }, [postId]);
789
+
790
+ const loadImages = async () => {
791
+ try {
792
+ setError(null);
793
+
794
+ const post = await postActions.itemById(postId, ['postId', 'files']);
795
+ const imageFiles = post.files?.filter((f: any) => f.type === 'image') || [];
796
+ setImages(imageFiles);
797
+ } catch(err: any) {
798
+ setError(err.message || 'Failed to load images');
799
+ console.error('Error loading images:', err);
800
+ }
801
+ };
802
+
803
+ const handleUploadImage = async (file: File) => {
804
+ try {
805
+ setIsUploading(true);
806
+ setError(null);
807
+
808
+ const uploadedImage = await imageActions.add({
809
+ name: file.name,
810
+ type: 'image',
811
+ fileType: file.type,
812
+ size: file.size
813
+ });
814
+
815
+ await postActions.update({
816
+ postId,
817
+ files: [...images, uploadedImage]
818
+ });
819
+
820
+ await loadImages();
821
+ } catch(err: any) {
822
+ setError(err.message || 'Failed to upload image');
823
+ console.error('Error uploading image:', err);
824
+ } finally {
825
+ setIsUploading(false);
826
+ }
827
+ };
828
+
829
+ const handleRemoveImage = async (imageId: string) => {
830
+ try {
831
+ setError(null);
832
+
833
+ await imageActions.delete(imageId);
834
+
835
+ const updatedImages = images.filter(img => img.imageId !== imageId);
836
+ await postActions.update({
837
+ postId,
838
+ files: updatedImages
839
+ });
840
+
841
+ await loadImages();
842
+ } catch(err: any) {
843
+ setError(err.message || 'Failed to remove image');
844
+ console.error('Error removing image:', err);
845
+ }
846
+ };
847
+
848
+ return (
849
+ <div className="image-attachment">
850
+ <h3>Attached Images</h3>
851
+ {error && <div className="error">{error}</div>}
852
+
853
+ <div className="image-grid">
854
+ {images.map(image => (
855
+ <div key={image.imageId} className="image-item">
856
+ <img src={image.url} alt={image.name} />
857
+ <button onClick={() => handleRemoveImage(image.imageId)}>
858
+ Remove
859
+ </button>
860
+ </div>
861
+ ))}
862
+ </div>
863
+
864
+ <div className="upload-section">
865
+ <input
866
+ type="file"
867
+ accept="image/*"
868
+ onChange={(e) => {
869
+ const file = e.target.files?.[0];
870
+ if(file) handleUploadImage(file);
871
+ }}
872
+ disabled={isUploading}
873
+ />
874
+ {isUploading && <span>Uploading...</span>}
875
+ </div>
876
+ </div>
877
+ );
878
+ };
879
+
880
+ // =============================================================================
881
+ // Complete Connection Example
882
+ // =============================================================================
883
+
884
+ export const CompleteConnectionExample = ({itemId, itemType}: {itemId: string; itemType: string}) => {
885
+ return (
886
+ <div className="complete-connection-example">
887
+ <h2>All Connection Types</h2>
888
+
889
+ <section>
890
+ <h3>Reactions</h3>
891
+ <LikeSystemExample postId={itemId} />
892
+ <PinSystemExample postId={itemId} />
893
+ <ViewCountExample postId={itemId} />
894
+ </section>
895
+
896
+ <section>
897
+ <h3>Tags</h3>
898
+ <TaggingExample postId={itemId} />
899
+ </section>
900
+
901
+ <section>
902
+ <h3>Attachments</h3>
903
+ <ImageAttachmentExample postId={itemId} />
904
+ </section>
905
+ </div>
906
+ );
907
+ };
908
+
909
+ export default CompleteConnectionExample;