@nlabs/metropolisjs 0.1.1 → 0.2.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.
- package/.github/copilot-instructions.md +167 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +112 -0
- package/GRAPHQL_VALIDATION_RESULTS.md +158 -0
- package/PERMISSION_GRAPHQL_INTEGRATION.md +293 -0
- package/PERMISSION_IMPLEMENTATION_SUMMARY.md +243 -0
- package/README.md +1343 -0
- package/SIGNIN_GRAPHQL_VALIDATION.md +125 -0
- package/VAULT_GRAPHQL_API_FINDINGS.md +312 -0
- package/docs/ACTIONS.md +63 -0
- package/docs/COLLECTIONS.md +1038 -0
- package/docs/CONNECTIONS.md +1367 -0
- package/docs/CRUD_INTEGRATION.md +702 -0
- package/examples/connections-usage.tsx +909 -0
- package/examples/content-actions-usage.ts +144 -0
- package/examples/crud-usage.tsx +847 -0
- package/examples/extensibility-usage.tsx +889 -0
- package/examples/factory-pattern-usage.ts +283 -0
- package/examples/i18n-import-examples.ts +86 -0
- package/examples/i18n-namespace-usage.ts +118 -0
- package/examples/i18next-singleton-demo.ts +187 -0
- package/examples/permission-system-usage.tsx +415 -0
- package/examples/project-details.tsx +397 -0
- package/examples/shared-i18n-usage.tsx +203 -0
- package/examples/signup-error-handling.ts +100 -0
- package/examples/translation-types.ts +86 -0
- package/examples/translation-usage.tsx +336 -0
- package/factoryPatternGuide.md +244 -0
- package/index.js +1 -0
- package/jest.config.mjs +28 -0
- package/lex.config.mjs +26 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts +22 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServer.d.ts.map +1 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServer.js +197 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts +3 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.d.ts.map +1 -0
- package/lib/__tests__/e2e/helpers/testGraphQLServerSimple.js +52 -0
- package/lib/actions/appActions/appActions.d.ts +29 -0
- package/lib/actions/appActions/appActions.d.ts.map +1 -0
- package/lib/actions/appActions/appActions.js +159 -0
- package/lib/actions/connectionActions/connectionActions.d.ts +19 -0
- package/lib/actions/connectionActions/connectionActions.d.ts.map +1 -0
- package/lib/actions/connectionActions/connectionActions.js +131 -0
- package/lib/actions/contentActions/contentActions.d.ts +34 -0
- package/lib/actions/contentActions/contentActions.d.ts.map +1 -0
- package/lib/actions/contentActions/contentActions.js +314 -0
- package/lib/actions/conversationActions/conversationActions.d.ts +30 -0
- package/lib/actions/conversationActions/conversationActions.d.ts.map +1 -0
- package/lib/actions/conversationActions/conversationActions.js +225 -0
- package/lib/actions/eventActions/eventActions.d.ts +65 -0
- package/lib/actions/eventActions/eventActions.d.ts.map +1 -0
- package/lib/actions/eventActions/eventActions.js +436 -0
- package/lib/actions/groupActions/groupActions.d.ts +30 -0
- package/lib/actions/groupActions/groupActions.d.ts.map +1 -0
- package/lib/actions/groupActions/groupActions.js +229 -0
- package/lib/actions/imageActions/imageActions.d.ts +62 -0
- package/lib/actions/imageActions/imageActions.d.ts.map +1 -0
- package/lib/actions/imageActions/imageActions.js +593 -0
- package/lib/actions/index.d.ts +21 -0
- package/lib/actions/index.d.ts.map +1 -0
- package/lib/actions/index.js +22 -0
- package/lib/actions/locationActions/locationActions.d.ts +60 -0
- package/lib/actions/locationActions/locationActions.d.ts.map +1 -0
- package/lib/actions/locationActions/locationActions.js +388 -0
- package/lib/actions/messageActions/messageActions.d.ts +38 -0
- package/lib/actions/messageActions/messageActions.d.ts.map +1 -0
- package/lib/actions/messageActions/messageActions.js +235 -0
- package/lib/actions/permissionActions/permissionActions.d.ts +34 -0
- package/lib/actions/permissionActions/permissionActions.d.ts.map +1 -0
- package/lib/actions/permissionActions/permissionActions.js +330 -0
- package/lib/actions/personaActions/personaActions.d.ts +65 -0
- package/lib/actions/personaActions/personaActions.d.ts.map +1 -0
- package/lib/actions/personaActions/personaActions.js +572 -0
- package/lib/actions/postActions/postActions.d.ts +36 -0
- package/lib/actions/postActions/postActions.d.ts.map +1 -0
- package/lib/actions/postActions/postActions.js +447 -0
- package/lib/actions/reactionActions/reactionActions.d.ts +38 -0
- package/lib/actions/reactionActions/reactionActions.d.ts.map +1 -0
- package/lib/actions/reactionActions/reactionActions.js +413 -0
- package/lib/actions/sseActions/sseActions.d.ts +38 -0
- package/lib/actions/sseActions/sseActions.d.ts.map +1 -0
- package/lib/actions/sseActions/sseActions.js +148 -0
- package/lib/actions/subscriptionActions/subscriptionActions.d.ts +24 -0
- package/lib/actions/subscriptionActions/subscriptionActions.d.ts.map +1 -0
- package/lib/actions/subscriptionActions/subscriptionActions.js +329 -0
- package/lib/actions/tagActions/tagActions.d.ts +37 -0
- package/lib/actions/tagActions/tagActions.d.ts.map +1 -0
- package/lib/actions/tagActions/tagActions.js +347 -0
- package/lib/actions/translationActions/translationActions.d.ts +38 -0
- package/lib/actions/translationActions/translationActions.d.ts.map +1 -0
- package/lib/actions/translationActions/translationActions.js +189 -0
- package/lib/actions/userActions/userActions.d.ts +78 -0
- package/lib/actions/userActions/userActions.d.ts.map +1 -0
- package/lib/actions/userActions/userActions.js +844 -0
- package/lib/actions/videoActions/videoActions.d.ts +41 -0
- package/lib/actions/videoActions/videoActions.d.ts.map +1 -0
- package/lib/actions/videoActions/videoActions.js +532 -0
- package/lib/actions/websocketActions/websocketActions.d.ts +30 -0
- package/lib/actions/websocketActions/websocketActions.d.ts.map +1 -0
- package/lib/actions/websocketActions/websocketActions.js +377 -0
- package/lib/adapters/appAdapter/appAdapter.d.ts +8 -0
- package/lib/adapters/appAdapter/appAdapter.d.ts.map +1 -0
- package/lib/adapters/appAdapter/appAdapter.js +90 -0
- package/lib/adapters/arangoAdapter/arangoAdapter.d.ts +21 -0
- package/lib/adapters/arangoAdapter/arangoAdapter.d.ts.map +1 -0
- package/lib/adapters/arangoAdapter/arangoAdapter.js +75 -0
- package/lib/adapters/contentAdapter/contentAdapter.d.ts +32 -0
- package/lib/adapters/contentAdapter/contentAdapter.d.ts.map +1 -0
- package/lib/adapters/contentAdapter/contentAdapter.js +107 -0
- package/lib/adapters/conversationAdapter/conversationAdapter.d.ts +24 -0
- package/lib/adapters/conversationAdapter/conversationAdapter.d.ts.map +1 -0
- package/lib/adapters/conversationAdapter/conversationAdapter.js +75 -0
- package/lib/adapters/eventAdapter/eventAdapter.d.ts +46 -0
- package/lib/adapters/eventAdapter/eventAdapter.d.ts.map +1 -0
- package/lib/adapters/eventAdapter/eventAdapter.js +162 -0
- package/lib/adapters/fileAdapter/fileAdapter.d.ts +30 -0
- package/lib/adapters/fileAdapter/fileAdapter.d.ts.map +1 -0
- package/lib/adapters/fileAdapter/fileAdapter.js +118 -0
- package/lib/adapters/groupAdapter/groupAdapter.d.ts +28 -0
- package/lib/adapters/groupAdapter/groupAdapter.d.ts.map +1 -0
- package/lib/adapters/groupAdapter/groupAdapter.js +66 -0
- package/lib/adapters/imageAdapter/imageAdapter.d.ts +38 -0
- package/lib/adapters/imageAdapter/imageAdapter.d.ts.map +1 -0
- package/lib/adapters/imageAdapter/imageAdapter.js +134 -0
- package/lib/adapters/index.d.ts +34 -0
- package/lib/adapters/index.d.ts.map +1 -0
- package/lib/adapters/index.js +19 -0
- package/lib/adapters/locationAdapter/locationAdapter.d.ts +28 -0
- package/lib/adapters/locationAdapter/locationAdapter.d.ts.map +1 -0
- package/lib/adapters/locationAdapter/locationAdapter.js +95 -0
- package/lib/adapters/messageAdapter/messageAdapter.d.ts +31 -0
- package/lib/adapters/messageAdapter/messageAdapter.d.ts.map +1 -0
- package/lib/adapters/messageAdapter/messageAdapter.js +82 -0
- package/lib/adapters/permissionAdapter/permissionAdapter.d.ts +41 -0
- package/lib/adapters/permissionAdapter/permissionAdapter.d.ts.map +1 -0
- package/lib/adapters/permissionAdapter/permissionAdapter.js +120 -0
- package/lib/adapters/personaAdapter/personaAdapter.d.ts +44 -0
- package/lib/adapters/personaAdapter/personaAdapter.d.ts.map +1 -0
- package/lib/adapters/personaAdapter/personaAdapter.js +161 -0
- package/lib/adapters/postAdapter/postAdapter.d.ts +33 -0
- package/lib/adapters/postAdapter/postAdapter.d.ts.map +1 -0
- package/lib/adapters/postAdapter/postAdapter.js +115 -0
- package/lib/adapters/reactionAdapter/reactionAdapter.d.ts +23 -0
- package/lib/adapters/reactionAdapter/reactionAdapter.d.ts.map +1 -0
- package/lib/adapters/reactionAdapter/reactionAdapter.js +81 -0
- package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts +17 -0
- package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts.map +1 -0
- package/lib/adapters/reaktorAdapter/reaktorAdapter.js +58 -0
- package/lib/adapters/sessionAdapter/sessionAdapter.d.ts +24 -0
- package/lib/adapters/sessionAdapter/sessionAdapter.d.ts.map +1 -0
- package/lib/adapters/sessionAdapter/sessionAdapter.js +80 -0
- package/lib/adapters/tagAdapter/tagAdapter.d.ts +21 -0
- package/lib/adapters/tagAdapter/tagAdapter.d.ts.map +1 -0
- package/lib/adapters/tagAdapter/tagAdapter.js +98 -0
- package/lib/adapters/translationAdapter/translationAdapter.d.ts +21 -0
- package/lib/adapters/translationAdapter/translationAdapter.d.ts.map +1 -0
- package/lib/adapters/translationAdapter/translationAdapter.js +55 -0
- package/lib/adapters/userAdapter/userAdapter.d.ts +67 -0
- package/lib/adapters/userAdapter/userAdapter.d.ts.map +1 -0
- package/lib/adapters/userAdapter/userAdapter.js +302 -0
- package/lib/adapters/videoAdapter/videoAdapter.d.ts +8 -0
- package/lib/adapters/videoAdapter/videoAdapter.d.ts.map +1 -0
- package/lib/adapters/videoAdapter/videoAdapter.js +194 -0
- package/lib/config/index.d.ts +44 -0
- package/lib/config/index.d.ts.map +1 -0
- package/lib/config/index.js +97 -0
- package/lib/constants/Collections.d.ts +44 -0
- package/lib/constants/Collections.d.ts.map +1 -0
- package/lib/constants/Collections.js +40 -0
- package/lib/constants/MetropolisConstants.d.ts +15 -0
- package/lib/constants/MetropolisConstants.d.ts.map +1 -0
- package/lib/constants/MetropolisConstants.js +15 -0
- package/lib/graphql/message.d.ts +3 -0
- package/lib/graphql/message.d.ts.map +1 -0
- package/lib/graphql/message.js +20 -0
- package/lib/graphql/notification.d.ts +3 -0
- package/lib/graphql/notification.d.ts.map +1 -0
- package/lib/graphql/notification.js +20 -0
- package/lib/graphql/session.d.ts +2 -0
- package/lib/graphql/session.d.ts.map +1 -0
- package/lib/graphql/session.js +17 -0
- package/lib/index.d.ts +52 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +248 -0
- package/lib/stores/appStore.d.ts +23 -0
- package/lib/stores/appStore.d.ts.map +1 -0
- package/lib/stores/appStore.js +23 -0
- package/lib/stores/contentStore.d.ts +36 -0
- package/lib/stores/contentStore.d.ts.map +1 -0
- package/lib/stores/contentStore.js +139 -0
- package/lib/stores/conversationStore.d.ts +38 -0
- package/lib/stores/conversationStore.d.ts.map +1 -0
- package/lib/stores/conversationStore.js +117 -0
- package/lib/stores/eventStore.d.ts +37 -0
- package/lib/stores/eventStore.d.ts.map +1 -0
- package/lib/stores/eventStore.js +47 -0
- package/lib/stores/groupStore.d.ts +37 -0
- package/lib/stores/groupStore.d.ts.map +1 -0
- package/lib/stores/groupStore.js +68 -0
- package/lib/stores/imageStore.d.ts +40 -0
- package/lib/stores/imageStore.d.ts.map +1 -0
- package/lib/stores/imageStore.js +53 -0
- package/lib/stores/index.d.ts +24 -0
- package/lib/stores/index.d.ts.map +1 -0
- package/lib/stores/index.js +24 -0
- package/lib/stores/locationStore.d.ts +34 -0
- package/lib/stores/locationStore.d.ts.map +1 -0
- package/lib/stores/locationStore.js +42 -0
- package/lib/stores/messageStore.d.ts +51 -0
- package/lib/stores/messageStore.d.ts.map +1 -0
- package/lib/stores/messageStore.js +137 -0
- package/lib/stores/notificationStore.d.ts +33 -0
- package/lib/stores/notificationStore.d.ts.map +1 -0
- package/lib/stores/notificationStore.js +55 -0
- package/lib/stores/permissionStore.d.ts +37 -0
- package/lib/stores/permissionStore.d.ts.map +1 -0
- package/lib/stores/permissionStore.js +117 -0
- package/lib/stores/personaStore.d.ts +26 -0
- package/lib/stores/personaStore.d.ts.map +1 -0
- package/lib/stores/personaStore.js +118 -0
- package/lib/stores/postStore.d.ts +34 -0
- package/lib/stores/postStore.d.ts.map +1 -0
- package/lib/stores/postStore.js +53 -0
- package/lib/stores/reactionStore.d.ts +25 -0
- package/lib/stores/reactionStore.d.ts.map +1 -0
- package/lib/stores/reactionStore.js +28 -0
- package/lib/stores/subscriptionStore.d.ts +41 -0
- package/lib/stores/subscriptionStore.d.ts.map +1 -0
- package/lib/stores/subscriptionStore.js +85 -0
- package/lib/stores/tagStore.d.ts +34 -0
- package/lib/stores/tagStore.d.ts.map +1 -0
- package/lib/stores/tagStore.js +87 -0
- package/lib/stores/translationStore.d.ts +42 -0
- package/lib/stores/translationStore.d.ts.map +1 -0
- package/lib/stores/translationStore.js +122 -0
- package/lib/stores/userStore.d.ts +74 -0
- package/lib/stores/userStore.d.ts.map +1 -0
- package/lib/stores/userStore.js +319 -0
- package/lib/stores/videoStore.d.ts +39 -0
- package/lib/stores/videoStore.d.ts.map +1 -0
- package/lib/stores/videoStore.js +132 -0
- package/lib/stores/websocketStore.d.ts +24 -0
- package/lib/stores/websocketStore.d.ts.map +1 -0
- package/lib/stores/websocketStore.js +51 -0
- package/lib/types/apps.types.d.ts +17 -0
- package/lib/types/apps.types.d.ts.map +1 -0
- package/lib/types/apps.types.js +6 -0
- package/lib/types/common.types.d.ts +31 -0
- package/lib/types/common.types.d.ts.map +1 -0
- package/lib/types/common.types.js +6 -0
- package/lib/types/conversations.types.d.ts +17 -0
- package/lib/types/conversations.types.d.ts.map +1 -0
- package/lib/types/conversations.types.js +6 -0
- package/lib/types/edges.types.d.ts +38 -0
- package/lib/types/edges.types.d.ts.map +1 -0
- package/lib/types/edges.types.js +6 -0
- package/lib/types/groups.types.d.ts +19 -0
- package/lib/types/groups.types.d.ts.map +1 -0
- package/lib/types/groups.types.js +6 -0
- package/lib/types/index.d.ts +12 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +12 -0
- package/lib/types/subscriptions.types.d.ts +32 -0
- package/lib/types/subscriptions.types.d.ts.map +1 -0
- package/lib/types/subscriptions.types.js +6 -0
- package/lib/types/videos.types.d.ts +61 -0
- package/lib/types/videos.types.d.ts.map +1 -0
- package/lib/types/videos.types.js +6 -0
- package/lib/utils/MetropolisProvider.d.ts +58 -0
- package/lib/utils/MetropolisProvider.d.ts.map +1 -0
- package/lib/utils/MetropolisProvider.js +31 -0
- package/lib/utils/actionFactory.d.ts +24 -0
- package/lib/utils/actionFactory.d.ts.map +1 -0
- package/lib/utils/actionFactory.js +95 -0
- package/lib/utils/api.d.ts +44 -0
- package/lib/utils/api.d.ts.map +1 -0
- package/lib/utils/api.js +253 -0
- package/lib/utils/app.d.ts +7 -0
- package/lib/utils/app.d.ts.map +1 -0
- package/lib/utils/app.js +15 -0
- package/lib/utils/baseActionFactory.d.ts +16 -0
- package/lib/utils/baseActionFactory.d.ts.map +1 -0
- package/lib/utils/baseActionFactory.js +72 -0
- package/lib/utils/configUtils.d.ts +11 -0
- package/lib/utils/configUtils.d.ts.map +1 -0
- package/lib/utils/configUtils.js +20 -0
- package/lib/utils/dateUtils.d.ts +3 -0
- package/lib/utils/dateUtils.d.ts.map +1 -0
- package/lib/utils/dateUtils.js +11 -0
- package/lib/utils/file.d.ts +3 -0
- package/lib/utils/file.d.ts.map +1 -0
- package/lib/utils/file.js +156 -0
- package/lib/utils/i18n.d.ts +7 -0
- package/lib/utils/i18n.d.ts.map +1 -0
- package/lib/utils/i18n.js +96 -0
- package/lib/utils/index.d.ts +20 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +26 -0
- package/lib/utils/location.d.ts +10 -0
- package/lib/utils/location.d.ts.map +1 -0
- package/lib/utils/location.js +82 -0
- package/lib/utils/permissionUtils.d.ts +22 -0
- package/lib/utils/permissionUtils.d.ts.map +1 -0
- package/lib/utils/permissionUtils.js +72 -0
- package/lib/utils/requestCache.d.ts +8 -0
- package/lib/utils/requestCache.d.ts.map +1 -0
- package/lib/utils/requestCache.js +53 -0
- package/lib/utils/session.d.ts +14 -0
- package/lib/utils/session.d.ts.map +1 -0
- package/lib/utils/session.js +107 -0
- package/lib/utils/useConversationTyping.d.ts +26 -0
- package/lib/utils/useConversationTyping.d.ts.map +1 -0
- package/lib/utils/useConversationTyping.js +117 -0
- package/lib/utils/useMetropolis.d.ts +67 -0
- package/lib/utils/useMetropolis.d.ts.map +1 -0
- package/lib/utils/useMetropolis.js +311 -0
- package/lib/utils/useTranslations.d.ts +16 -0
- package/lib/utils/useTranslations.d.ts.map +1 -0
- package/lib/utils/useTranslations.js +105 -0
- package/lib/utils/validatorFactory.d.ts +49 -0
- package/lib/utils/validatorFactory.d.ts.map +1 -0
- package/lib/utils/validatorFactory.js +95 -0
- package/package.json +75 -28
- package/temp_signin.ts +73 -0
- package/tsconfig.lint.json +25 -0
- package/tsconfig.test.json +14 -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;
|