@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.
- 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 +1123 -112
- 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/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/index.js +1 -0
- package/jest.config.mjs +28 -0
- package/lex.config.mjs +11 -2
- 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 +27 -7
- package/lib/actions/eventActions/eventActions.d.ts.map +1 -0
- package/lib/actions/eventActions/eventActions.js +436 -2
- 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 +16 -10
- package/lib/actions/imageActions/imageActions.d.ts.map +1 -0
- package/lib/actions/imageActions/imageActions.js +593 -2
- package/lib/actions/index.d.ts +21 -9
- package/lib/actions/index.d.ts.map +1 -0
- package/lib/actions/index.js +22 -2
- package/lib/actions/locationActions/locationActions.d.ts +11 -9
- package/lib/actions/locationActions/locationActions.d.ts.map +1 -0
- package/lib/actions/locationActions/locationActions.js +388 -2
- package/lib/actions/messageActions/messageActions.d.ts +16 -32
- package/lib/actions/messageActions/messageActions.d.ts.map +1 -0
- package/lib/actions/messageActions/messageActions.js +235 -2
- 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 +12 -32
- package/lib/actions/postActions/postActions.d.ts.map +1 -0
- package/lib/actions/postActions/postActions.js +447 -2
- package/lib/actions/reactionActions/reactionActions.d.ts +13 -27
- package/lib/actions/reactionActions/reactionActions.d.ts.map +1 -0
- package/lib/actions/reactionActions/reactionActions.js +413 -2
- package/lib/actions/sseActions/sseActions.d.ts +1 -0
- package/lib/actions/sseActions/sseActions.d.ts.map +1 -0
- package/lib/actions/sseActions/sseActions.js +148 -2
- 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 +13 -31
- package/lib/actions/tagActions/tagActions.d.ts.map +1 -0
- package/lib/actions/tagActions/tagActions.js +347 -2
- 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 +44 -95
- package/lib/actions/userActions/userActions.d.ts.map +1 -0
- package/lib/actions/userActions/userActions.js +870 -2
- 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 +20 -21
- package/lib/actions/websocketActions/websocketActions.d.ts.map +1 -0
- package/lib/actions/websocketActions/websocketActions.js +377 -2
- 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 +3 -2
- package/lib/adapters/arangoAdapter/arangoAdapter.d.ts.map +1 -0
- package/lib/adapters/arangoAdapter/arangoAdapter.js +75 -2
- package/lib/adapters/contentAdapter/contentAdapter.d.ts +3 -2
- package/lib/adapters/contentAdapter/contentAdapter.d.ts.map +1 -0
- package/lib/adapters/contentAdapter/contentAdapter.js +107 -2
- package/lib/adapters/conversationAdapter/conversationAdapter.d.ts +3 -2
- package/lib/adapters/conversationAdapter/conversationAdapter.d.ts.map +1 -0
- package/lib/adapters/conversationAdapter/conversationAdapter.js +75 -2
- package/lib/adapters/eventAdapter/eventAdapter.d.ts +5 -2
- package/lib/adapters/eventAdapter/eventAdapter.d.ts.map +1 -0
- package/lib/adapters/eventAdapter/eventAdapter.js +162 -2
- package/lib/adapters/fileAdapter/fileAdapter.d.ts +3 -2
- package/lib/adapters/fileAdapter/fileAdapter.d.ts.map +1 -0
- package/lib/adapters/fileAdapter/fileAdapter.js +118 -2
- 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 +9 -2
- package/lib/adapters/imageAdapter/imageAdapter.d.ts.map +1 -0
- package/lib/adapters/imageAdapter/imageAdapter.js +134 -2
- package/lib/adapters/index.d.ts +34 -16
- package/lib/adapters/index.d.ts.map +1 -0
- package/lib/adapters/index.js +19 -2
- package/lib/adapters/locationAdapter/locationAdapter.d.ts +3 -2
- package/lib/adapters/locationAdapter/locationAdapter.d.ts.map +1 -0
- package/lib/adapters/locationAdapter/locationAdapter.js +95 -2
- package/lib/adapters/messageAdapter/messageAdapter.d.ts +23 -19
- package/lib/adapters/messageAdapter/messageAdapter.d.ts.map +1 -0
- package/lib/adapters/messageAdapter/messageAdapter.js +82 -2
- 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 +28 -2
- package/lib/adapters/personaAdapter/personaAdapter.d.ts.map +1 -0
- package/lib/adapters/personaAdapter/personaAdapter.js +161 -2
- package/lib/adapters/postAdapter/postAdapter.d.ts +3 -2
- package/lib/adapters/postAdapter/postAdapter.d.ts.map +1 -0
- package/lib/adapters/postAdapter/postAdapter.js +115 -2
- package/lib/adapters/reactionAdapter/reactionAdapter.d.ts +5 -2
- package/lib/adapters/reactionAdapter/reactionAdapter.d.ts.map +1 -0
- package/lib/adapters/reactionAdapter/reactionAdapter.js +81 -2
- package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts +3 -2
- package/lib/adapters/reaktorAdapter/reaktorAdapter.d.ts.map +1 -0
- package/lib/adapters/reaktorAdapter/reaktorAdapter.js +58 -2
- package/lib/adapters/sessionAdapter/sessionAdapter.d.ts +3 -2
- package/lib/adapters/sessionAdapter/sessionAdapter.d.ts.map +1 -0
- package/lib/adapters/sessionAdapter/sessionAdapter.js +80 -2
- package/lib/adapters/tagAdapter/tagAdapter.d.ts +3 -2
- package/lib/adapters/tagAdapter/tagAdapter.d.ts.map +1 -0
- package/lib/adapters/tagAdapter/tagAdapter.js +98 -2
- 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 +18 -6
- package/lib/adapters/userAdapter/userAdapter.d.ts.map +1 -0
- package/lib/adapters/userAdapter/userAdapter.js +302 -2
- 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 +19 -6
- package/lib/config/index.d.ts.map +1 -0
- package/lib/config/index.js +97 -2
- 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 +1 -0
- package/lib/constants/MetropolisConstants.d.ts.map +1 -0
- package/lib/constants/MetropolisConstants.js +15 -2
- package/lib/graphql/message.d.ts +1 -0
- package/lib/graphql/message.d.ts.map +1 -0
- package/lib/graphql/message.js +6 -4
- package/lib/graphql/notification.d.ts +1 -0
- package/lib/graphql/notification.d.ts.map +1 -0
- package/lib/graphql/notification.js +6 -4
- package/lib/graphql/session.d.ts +1 -0
- package/lib/graphql/session.d.ts.map +1 -0
- package/lib/graphql/session.js +4 -3
- package/lib/index.d.ts +45 -22
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +248 -2
- package/lib/stores/appStore.d.ts +1 -0
- package/lib/stores/appStore.d.ts.map +1 -0
- package/lib/stores/appStore.js +23 -2
- 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 +4 -1
- package/lib/stores/eventStore.d.ts.map +1 -0
- package/lib/stores/eventStore.js +47 -2
- 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 +6 -1
- package/lib/stores/imageStore.d.ts.map +1 -0
- package/lib/stores/imageStore.js +53 -2
- package/lib/stores/index.d.ts +20 -11
- package/lib/stores/index.d.ts.map +1 -0
- package/lib/stores/index.js +24 -2
- package/lib/stores/locationStore.d.ts +2 -1
- package/lib/stores/locationStore.d.ts.map +1 -0
- package/lib/stores/locationStore.js +42 -2
- package/lib/stores/messageStore.d.ts +19 -4
- package/lib/stores/messageStore.d.ts.map +1 -0
- package/lib/stores/messageStore.js +137 -2
- 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 +2 -1
- package/lib/stores/postStore.d.ts.map +1 -0
- package/lib/stores/postStore.js +53 -2
- package/lib/stores/reactionStore.d.ts +1 -0
- package/lib/stores/reactionStore.d.ts.map +1 -0
- package/lib/stores/reactionStore.js +28 -2
- 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 +8 -5
- package/lib/stores/tagStore.d.ts.map +1 -0
- package/lib/stores/tagStore.js +87 -2
- 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 +5 -4
- package/lib/stores/userStore.d.ts.map +1 -0
- package/lib/stores/userStore.js +319 -2
- 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 +1 -0
- package/lib/stores/websocketStore.d.ts.map +1 -0
- package/lib/stores/websocketStore.js +51 -2
- 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 +39 -15
- package/lib/utils/MetropolisProvider.d.ts.map +1 -0
- package/lib/utils/MetropolisProvider.js +31 -2
- package/lib/utils/actionFactory.d.ts +21 -19
- package/lib/utils/actionFactory.d.ts.map +1 -0
- package/lib/utils/actionFactory.js +95 -2
- package/lib/utils/api.d.ts +5 -4
- package/lib/utils/api.d.ts.map +1 -0
- package/lib/utils/api.js +252 -6
- package/lib/utils/app.d.ts +1 -0
- package/lib/utils/app.d.ts.map +1 -0
- package/lib/utils/app.js +15 -2
- package/lib/utils/baseActionFactory.d.ts +5 -23
- package/lib/utils/baseActionFactory.d.ts.map +1 -0
- package/lib/utils/baseActionFactory.js +72 -2
- 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 +1 -0
- package/lib/utils/dateUtils.d.ts.map +1 -0
- package/lib/utils/dateUtils.js +11 -2
- package/lib/utils/file.d.ts +3 -1
- package/lib/utils/file.d.ts.map +1 -0
- package/lib/utils/file.js +156 -2
- 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 +4 -3
- package/lib/utils/location.d.ts.map +1 -0
- package/lib/utils/location.js +82 -2
- 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 -12
- package/lib/utils/useMetropolis.d.ts.map +1 -0
- package/lib/utils/useMetropolis.js +311 -2
- 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 +3 -2
- package/lib/utils/validatorFactory.d.ts.map +1 -0
- package/lib/utils/validatorFactory.js +95 -2
- package/package.json +58 -17
- package/temp_signin.ts +73 -0
- package/lib/actions/profileActions/profileActions.d.ts +0 -50
- package/lib/actions/profileActions/profileActions.js +0 -2
- package/lib/adapters/profileAdapter/profileAdapter.d.ts +0 -32
- package/lib/adapters/profileAdapter/profileAdapter.js +0 -2
- package/lib/stores/profileStore.d.ts +0 -24
- package/lib/stores/profileStore.js +0 -2
- /package/{FACTORY_PATTERN_GUIDE.md → factoryPatternGuide.md} +0 -0
|
@@ -0,0 +1,847 @@
|
|
|
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
|
+
* Comprehensive CRUD Operations Examples for MetropolisJS
|
|
6
|
+
*
|
|
7
|
+
* This file demonstrates complete Create, Read, Update, Delete operations
|
|
8
|
+
* for all major collections in MetropolisJS with React components showing
|
|
9
|
+
* real-world usage patterns, error handling, and state management.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, {useEffect, useState} from 'react';
|
|
13
|
+
import {useFlux} from '@nlabs/arkhamjs-utils-react';
|
|
14
|
+
import {
|
|
15
|
+
usePostActions,
|
|
16
|
+
useGroupActions,
|
|
17
|
+
useMessageActions,
|
|
18
|
+
createConversationActions,
|
|
19
|
+
useEventActions,
|
|
20
|
+
useUserActions,
|
|
21
|
+
type PostType,
|
|
22
|
+
type GroupType,
|
|
23
|
+
type MessageType,
|
|
24
|
+
type ConversationType,
|
|
25
|
+
type EventType
|
|
26
|
+
} from '@nlabs/metropolisjs';
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Posts CRUD Operations
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
export const PostCreatorExample = () => {
|
|
33
|
+
const flux = useFlux();
|
|
34
|
+
const postActions = usePostActions();
|
|
35
|
+
const [content, setContent] = useState('');
|
|
36
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
37
|
+
const [error, setError] = useState<string | null>(null);
|
|
38
|
+
|
|
39
|
+
const handleCreatePost = async () => {
|
|
40
|
+
try {
|
|
41
|
+
setIsSubmitting(true);
|
|
42
|
+
setError(null);
|
|
43
|
+
|
|
44
|
+
const newPost = await postActions.add({
|
|
45
|
+
content,
|
|
46
|
+
type: 'post',
|
|
47
|
+
privacy: 'public'
|
|
48
|
+
}, ['postId', 'content', 'userId', 'createdAt']);
|
|
49
|
+
|
|
50
|
+
console.log('Post created:', newPost);
|
|
51
|
+
setContent('');
|
|
52
|
+
} catch(err: any) {
|
|
53
|
+
setError(err.message || 'Failed to create post');
|
|
54
|
+
console.error('Error creating post:', err);
|
|
55
|
+
} finally {
|
|
56
|
+
setIsSubmitting(false);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="post-creator">
|
|
62
|
+
<h2>Create Post</h2>
|
|
63
|
+
<textarea
|
|
64
|
+
value={content}
|
|
65
|
+
onChange={(e) => setContent(e.target.value)}
|
|
66
|
+
placeholder="What's on your mind?"
|
|
67
|
+
rows={4}
|
|
68
|
+
disabled={isSubmitting}
|
|
69
|
+
/>
|
|
70
|
+
{error && <div className="error">{error}</div>}
|
|
71
|
+
<button
|
|
72
|
+
onClick={handleCreatePost}
|
|
73
|
+
disabled={!content || isSubmitting}
|
|
74
|
+
>
|
|
75
|
+
{isSubmitting ? 'Posting...' : 'Create Post'}
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const PostListExample = () => {
|
|
82
|
+
const postActions = usePostActions();
|
|
83
|
+
const [posts, setPosts] = useState<PostType[]>([]);
|
|
84
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
85
|
+
const [error, setError] = useState<string | null>(null);
|
|
86
|
+
const [page, setPage] = useState(0);
|
|
87
|
+
const pageSize = 10;
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const loadPosts = async () => {
|
|
91
|
+
try {
|
|
92
|
+
setIsLoading(true);
|
|
93
|
+
setError(null);
|
|
94
|
+
|
|
95
|
+
const from = page * pageSize;
|
|
96
|
+
const to = from + pageSize;
|
|
97
|
+
|
|
98
|
+
const loadedPosts = await postActions.listByLatest(
|
|
99
|
+
from,
|
|
100
|
+
to,
|
|
101
|
+
['postId', 'content', 'userId', 'createdAt', 'tags']
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
setPosts(loadedPosts);
|
|
105
|
+
} catch(err: any) {
|
|
106
|
+
setError(err.message || 'Failed to load posts');
|
|
107
|
+
console.error('Error loading posts:', err);
|
|
108
|
+
} finally {
|
|
109
|
+
setIsLoading(false);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
loadPosts();
|
|
114
|
+
}, [page, postActions]);
|
|
115
|
+
|
|
116
|
+
const handleNextPage = () => setPage(p => p + 1);
|
|
117
|
+
const handlePrevPage = () => setPage(p => Math.max(0, p - 1));
|
|
118
|
+
|
|
119
|
+
if(isLoading) return <div>Loading posts...</div>;
|
|
120
|
+
if(error) return <div className="error">Error: {error}</div>;
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div className="post-list">
|
|
124
|
+
<h2>Recent Posts</h2>
|
|
125
|
+
{posts.length === 0 ? (
|
|
126
|
+
<p>No posts found</p>
|
|
127
|
+
) : (
|
|
128
|
+
<ul>
|
|
129
|
+
{posts.map(post => (
|
|
130
|
+
<li key={post.postId}>
|
|
131
|
+
<p>{post.content}</p>
|
|
132
|
+
<small>Posted at: {post.createdAt}</small>
|
|
133
|
+
</li>
|
|
134
|
+
))}
|
|
135
|
+
</ul>
|
|
136
|
+
)}
|
|
137
|
+
<div className="pagination">
|
|
138
|
+
<button onClick={handlePrevPage} disabled={page === 0}>Previous</button>
|
|
139
|
+
<span>Page {page + 1}</span>
|
|
140
|
+
<button onClick={handleNextPage}>Next</button>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const PostDetailExample = ({postId}: {postId: string}) => {
|
|
147
|
+
const postActions = usePostActions();
|
|
148
|
+
const [post, setPost] = useState<PostType | null>(null);
|
|
149
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
150
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
151
|
+
const [editContent, setEditContent] = useState('');
|
|
152
|
+
const [error, setError] = useState<string | null>(null);
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
const loadPost = async () => {
|
|
156
|
+
try {
|
|
157
|
+
setIsLoading(true);
|
|
158
|
+
setError(null);
|
|
159
|
+
|
|
160
|
+
const loadedPost = await postActions.itemById(postId, [
|
|
161
|
+
'postId',
|
|
162
|
+
'content',
|
|
163
|
+
'userId',
|
|
164
|
+
'createdAt',
|
|
165
|
+
'updatedAt',
|
|
166
|
+
'tags',
|
|
167
|
+
'files'
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
setPost(loadedPost);
|
|
171
|
+
setEditContent(loadedPost.content || '');
|
|
172
|
+
} catch(err: any) {
|
|
173
|
+
setError(err.message || 'Failed to load post');
|
|
174
|
+
console.error('Error loading post:', err);
|
|
175
|
+
} finally {
|
|
176
|
+
setIsLoading(false);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
loadPost();
|
|
181
|
+
}, [postId, postActions]);
|
|
182
|
+
|
|
183
|
+
const handleUpdatePost = async () => {
|
|
184
|
+
if(!post) return;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
setError(null);
|
|
188
|
+
|
|
189
|
+
const updatedPost = await postActions.update({
|
|
190
|
+
postId: post.postId,
|
|
191
|
+
content: editContent
|
|
192
|
+
}, ['postId', 'content', 'updatedAt']);
|
|
193
|
+
|
|
194
|
+
setPost(updatedPost);
|
|
195
|
+
setIsEditing(false);
|
|
196
|
+
} catch(err: any) {
|
|
197
|
+
setError(err.message || 'Failed to update post');
|
|
198
|
+
console.error('Error updating post:', err);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handleDeletePost = async () => {
|
|
203
|
+
if(!post || !confirm('Are you sure you want to delete this post?')) return;
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
setError(null);
|
|
207
|
+
await postActions.delete(post.postId);
|
|
208
|
+
console.log('Post deleted successfully');
|
|
209
|
+
} catch(err: any) {
|
|
210
|
+
setError(err.message || 'Failed to delete post');
|
|
211
|
+
console.error('Error deleting post:', err);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if(isLoading) return <div>Loading post...</div>;
|
|
216
|
+
if(error) return <div className="error">Error: {error}</div>;
|
|
217
|
+
if(!post) return <div>Post not found</div>;
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<div className="post-detail">
|
|
221
|
+
<h2>Post Details</h2>
|
|
222
|
+
{isEditing ? (
|
|
223
|
+
<div>
|
|
224
|
+
<textarea
|
|
225
|
+
value={editContent}
|
|
226
|
+
onChange={(e) => setEditContent(e.target.value)}
|
|
227
|
+
rows={4}
|
|
228
|
+
/>
|
|
229
|
+
<button onClick={handleUpdatePost}>Save</button>
|
|
230
|
+
<button onClick={() => setIsEditing(false)}>Cancel</button>
|
|
231
|
+
</div>
|
|
232
|
+
) : (
|
|
233
|
+
<div>
|
|
234
|
+
<p>{post.content}</p>
|
|
235
|
+
<small>Created: {post.createdAt}</small>
|
|
236
|
+
{post.updatedAt && <small> | Updated: {post.updatedAt}</small>}
|
|
237
|
+
<div>
|
|
238
|
+
<button onClick={() => setIsEditing(true)}>Edit</button>
|
|
239
|
+
<button onClick={handleDeletePost}>Delete</button>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// =============================================================================
|
|
248
|
+
// Groups CRUD Operations
|
|
249
|
+
// =============================================================================
|
|
250
|
+
|
|
251
|
+
export const GroupManagerExample = () => {
|
|
252
|
+
const groupActions = useGroupActions();
|
|
253
|
+
const [groups, setGroups] = useState<GroupType[]>([]);
|
|
254
|
+
const [isCreating, setIsCreating] = useState(false);
|
|
255
|
+
const [newGroupName, setNewGroupName] = useState('');
|
|
256
|
+
const [newGroupDescription, setNewGroupDescription] = useState('');
|
|
257
|
+
const [error, setError] = useState<string | null>(null);
|
|
258
|
+
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
loadGroups();
|
|
261
|
+
}, []);
|
|
262
|
+
|
|
263
|
+
const loadGroups = async () => {
|
|
264
|
+
try {
|
|
265
|
+
setError(null);
|
|
266
|
+
const loadedGroups = await groupActions.listByLatest(0, 20, [
|
|
267
|
+
'groupId',
|
|
268
|
+
'name',
|
|
269
|
+
'description',
|
|
270
|
+
'privacy',
|
|
271
|
+
'createdAt'
|
|
272
|
+
]);
|
|
273
|
+
setGroups(loadedGroups);
|
|
274
|
+
} catch(err: any) {
|
|
275
|
+
setError(err.message || 'Failed to load groups');
|
|
276
|
+
console.error('Error loading groups:', err);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const handleCreateGroup = async () => {
|
|
281
|
+
try {
|
|
282
|
+
setError(null);
|
|
283
|
+
|
|
284
|
+
const newGroup = await groupActions.add({
|
|
285
|
+
name: newGroupName,
|
|
286
|
+
description: newGroupDescription,
|
|
287
|
+
privacy: 'public'
|
|
288
|
+
}, ['groupId', 'name', 'description', 'createdAt']);
|
|
289
|
+
|
|
290
|
+
setGroups([newGroup, ...groups]);
|
|
291
|
+
setNewGroupName('');
|
|
292
|
+
setNewGroupDescription('');
|
|
293
|
+
setIsCreating(false);
|
|
294
|
+
} catch(err: any) {
|
|
295
|
+
setError(err.message || 'Failed to create group');
|
|
296
|
+
console.error('Error creating group:', err);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const handleUpdateGroup = async (groupId: string, updates: Partial<GroupType>) => {
|
|
301
|
+
try {
|
|
302
|
+
setError(null);
|
|
303
|
+
|
|
304
|
+
const updatedGroup = await groupActions.update(
|
|
305
|
+
{groupId, ...updates},
|
|
306
|
+
['groupId', 'name', 'description', 'updatedAt']
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
setGroups(groups.map(g => g.groupId === groupId ? updatedGroup : g));
|
|
310
|
+
} catch(err: any) {
|
|
311
|
+
setError(err.message || 'Failed to update group');
|
|
312
|
+
console.error('Error updating group:', err);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const handleDeleteGroup = async (groupId: string) => {
|
|
317
|
+
if(!confirm('Are you sure you want to delete this group?')) return;
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
setError(null);
|
|
321
|
+
await groupActions.delete(groupId);
|
|
322
|
+
setGroups(groups.filter(g => g.groupId !== groupId));
|
|
323
|
+
} catch(err: any) {
|
|
324
|
+
setError(err.message || 'Failed to delete group');
|
|
325
|
+
console.error('Error deleting group:', err);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<div className="group-manager">
|
|
331
|
+
<h2>Group Management</h2>
|
|
332
|
+
{error && <div className="error">{error}</div>}
|
|
333
|
+
|
|
334
|
+
{isCreating ? (
|
|
335
|
+
<div className="create-group-form">
|
|
336
|
+
<input
|
|
337
|
+
type="text"
|
|
338
|
+
placeholder="Group name"
|
|
339
|
+
value={newGroupName}
|
|
340
|
+
onChange={(e) => setNewGroupName(e.target.value)}
|
|
341
|
+
/>
|
|
342
|
+
<textarea
|
|
343
|
+
placeholder="Description"
|
|
344
|
+
value={newGroupDescription}
|
|
345
|
+
onChange={(e) => setNewGroupDescription(e.target.value)}
|
|
346
|
+
rows={3}
|
|
347
|
+
/>
|
|
348
|
+
<button onClick={handleCreateGroup} disabled={!newGroupName}>
|
|
349
|
+
Create
|
|
350
|
+
</button>
|
|
351
|
+
<button onClick={() => setIsCreating(false)}>Cancel</button>
|
|
352
|
+
</div>
|
|
353
|
+
) : (
|
|
354
|
+
<button onClick={() => setIsCreating(true)}>Create New Group</button>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
<div className="group-list">
|
|
358
|
+
{groups.map(group => (
|
|
359
|
+
<div key={group.groupId} className="group-item">
|
|
360
|
+
<h3>{group.name}</h3>
|
|
361
|
+
<p>{group.description}</p>
|
|
362
|
+
<button onClick={() => handleUpdateGroup(group.groupId!, {
|
|
363
|
+
description: prompt('New description:', group.description) || group.description
|
|
364
|
+
})}>
|
|
365
|
+
Edit
|
|
366
|
+
</button>
|
|
367
|
+
<button onClick={() => handleDeleteGroup(group.groupId!)}>
|
|
368
|
+
Delete
|
|
369
|
+
</button>
|
|
370
|
+
</div>
|
|
371
|
+
))}
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
);
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// =============================================================================
|
|
378
|
+
// Conversations & Messages CRUD Operations
|
|
379
|
+
// =============================================================================
|
|
380
|
+
|
|
381
|
+
export const ConversationListExample = () => {
|
|
382
|
+
const flux = useFlux();
|
|
383
|
+
const conversationActions = createConversationActions(flux);
|
|
384
|
+
const [conversations, setConversations] = useState<ConversationType[]>([]);
|
|
385
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
386
|
+
const [error, setError] = useState<string | null>(null);
|
|
387
|
+
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
const loadConversations = async () => {
|
|
390
|
+
try {
|
|
391
|
+
setIsLoading(true);
|
|
392
|
+
setError(null);
|
|
393
|
+
|
|
394
|
+
const loadedConversations = await conversationActions.list(0, 50, [
|
|
395
|
+
'conversationId',
|
|
396
|
+
'name',
|
|
397
|
+
'participantIds',
|
|
398
|
+
'lastMessageAt',
|
|
399
|
+
'createdAt'
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
setConversations(loadedConversations);
|
|
403
|
+
} catch(err: any) {
|
|
404
|
+
setError(err.message || 'Failed to load conversations');
|
|
405
|
+
console.error('Error loading conversations:', err);
|
|
406
|
+
} finally {
|
|
407
|
+
setIsLoading(false);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
loadConversations();
|
|
412
|
+
}, [conversationActions]);
|
|
413
|
+
|
|
414
|
+
const handleCreateConversation = async (participantId: string) => {
|
|
415
|
+
try {
|
|
416
|
+
setError(null);
|
|
417
|
+
|
|
418
|
+
const newConversation = await conversationActions.add({
|
|
419
|
+
participantIds: [participantId],
|
|
420
|
+
type: 'direct'
|
|
421
|
+
}, ['conversationId', 'name', 'participantIds', 'createdAt']);
|
|
422
|
+
|
|
423
|
+
setConversations([newConversation, ...conversations]);
|
|
424
|
+
} catch(err: any) {
|
|
425
|
+
setError(err.message || 'Failed to create conversation');
|
|
426
|
+
console.error('Error creating conversation:', err);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const handleDeleteConversation = async (conversationId: string) => {
|
|
431
|
+
if(!confirm('Delete this conversation?')) return;
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
setError(null);
|
|
435
|
+
await conversationActions.delete(conversationId);
|
|
436
|
+
setConversations(conversations.filter(c => c.conversationId !== conversationId));
|
|
437
|
+
} catch(err: any) {
|
|
438
|
+
setError(err.message || 'Failed to delete conversation');
|
|
439
|
+
console.error('Error deleting conversation:', err);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
if(isLoading) return <div>Loading conversations...</div>;
|
|
444
|
+
if(error) return <div className="error">Error: {error}</div>;
|
|
445
|
+
|
|
446
|
+
return (
|
|
447
|
+
<div className="conversation-list">
|
|
448
|
+
<h2>Conversations</h2>
|
|
449
|
+
{conversations.length === 0 ? (
|
|
450
|
+
<p>No conversations yet</p>
|
|
451
|
+
) : (
|
|
452
|
+
<ul>
|
|
453
|
+
{conversations.map(conversation => (
|
|
454
|
+
<li key={conversation.conversationId}>
|
|
455
|
+
<div>
|
|
456
|
+
<h4>{conversation.name || 'Unnamed Conversation'}</h4>
|
|
457
|
+
<small>Last activity: {conversation.lastMessageAt}</small>
|
|
458
|
+
</div>
|
|
459
|
+
<button onClick={() => handleDeleteConversation(conversation.conversationId!)}>
|
|
460
|
+
Delete
|
|
461
|
+
</button>
|
|
462
|
+
</li>
|
|
463
|
+
))}
|
|
464
|
+
</ul>
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
467
|
+
);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
export const MessageThreadExample = ({conversationId}: {conversationId: string}) => {
|
|
471
|
+
const messageActions = useMessageActions();
|
|
472
|
+
const [messages, setMessages] = useState<MessageType[]>([]);
|
|
473
|
+
const [newMessage, setNewMessage] = useState('');
|
|
474
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
475
|
+
const [isSending, setIsSending] = useState(false);
|
|
476
|
+
const [error, setError] = useState<string | null>(null);
|
|
477
|
+
|
|
478
|
+
useEffect(() => {
|
|
479
|
+
const loadMessages = async () => {
|
|
480
|
+
try {
|
|
481
|
+
setIsLoading(true);
|
|
482
|
+
setError(null);
|
|
483
|
+
|
|
484
|
+
const loadedMessages = await messageActions.getMessages(conversationId, [
|
|
485
|
+
'messageId',
|
|
486
|
+
'content',
|
|
487
|
+
'senderId',
|
|
488
|
+
'conversationId',
|
|
489
|
+
'createdAt'
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
setMessages(loadedMessages);
|
|
493
|
+
} catch(err: any) {
|
|
494
|
+
setError(err.message || 'Failed to load messages');
|
|
495
|
+
console.error('Error loading messages:', err);
|
|
496
|
+
} finally {
|
|
497
|
+
setIsLoading(false);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
loadMessages();
|
|
502
|
+
}, [conversationId, messageActions]);
|
|
503
|
+
|
|
504
|
+
const handleSendMessage = async () => {
|
|
505
|
+
try {
|
|
506
|
+
setIsSending(true);
|
|
507
|
+
setError(null);
|
|
508
|
+
|
|
509
|
+
const message = await messageActions.sendMessage({
|
|
510
|
+
conversationId,
|
|
511
|
+
content: newMessage,
|
|
512
|
+
type: 'text'
|
|
513
|
+
}, ['messageId', 'content', 'senderId', 'createdAt']);
|
|
514
|
+
|
|
515
|
+
setMessages([...messages, message]);
|
|
516
|
+
setNewMessage('');
|
|
517
|
+
} catch(err: any) {
|
|
518
|
+
setError(err.message || 'Failed to send message');
|
|
519
|
+
console.error('Error sending message:', err);
|
|
520
|
+
} finally {
|
|
521
|
+
setIsSending(false);
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
if(isLoading) return <div>Loading messages...</div>;
|
|
526
|
+
if(error) return <div className="error">Error: {error}</div>;
|
|
527
|
+
|
|
528
|
+
return (
|
|
529
|
+
<div className="message-thread">
|
|
530
|
+
<h2>Messages</h2>
|
|
531
|
+
<div className="messages">
|
|
532
|
+
{messages.map(message => (
|
|
533
|
+
<div key={message.messageId} className="message">
|
|
534
|
+
<p>{message.content}</p>
|
|
535
|
+
<small>{message.createdAt}</small>
|
|
536
|
+
</div>
|
|
537
|
+
))}
|
|
538
|
+
</div>
|
|
539
|
+
<div className="message-input">
|
|
540
|
+
<input
|
|
541
|
+
type="text"
|
|
542
|
+
value={newMessage}
|
|
543
|
+
onChange={(e) => setNewMessage(e.target.value)}
|
|
544
|
+
placeholder="Type a message..."
|
|
545
|
+
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
|
|
546
|
+
disabled={isSending}
|
|
547
|
+
/>
|
|
548
|
+
<button onClick={handleSendMessage} disabled={!newMessage || isSending}>
|
|
549
|
+
{isSending ? 'Sending...' : 'Send'}
|
|
550
|
+
</button>
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
);
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// =============================================================================
|
|
557
|
+
// Events CRUD Operations
|
|
558
|
+
// =============================================================================
|
|
559
|
+
|
|
560
|
+
export const EventManagerExample = () => {
|
|
561
|
+
const eventActions = useEventActions();
|
|
562
|
+
const [events, setEvents] = useState<EventType[]>([]);
|
|
563
|
+
const [isCreating, setIsCreating] = useState(false);
|
|
564
|
+
const [formData, setFormData] = useState({
|
|
565
|
+
name: '',
|
|
566
|
+
content: '',
|
|
567
|
+
startDate: '',
|
|
568
|
+
endDate: '',
|
|
569
|
+
location: ''
|
|
570
|
+
});
|
|
571
|
+
const [error, setError] = useState<string | null>(null);
|
|
572
|
+
|
|
573
|
+
useEffect(() => {
|
|
574
|
+
loadEvents();
|
|
575
|
+
}, []);
|
|
576
|
+
|
|
577
|
+
const loadEvents = async () => {
|
|
578
|
+
try {
|
|
579
|
+
setError(null);
|
|
580
|
+
const latitude = 37.7749;
|
|
581
|
+
const longitude = -122.4194;
|
|
582
|
+
|
|
583
|
+
const loadedEvents = await eventActions.getEventsByTags(
|
|
584
|
+
[],
|
|
585
|
+
latitude,
|
|
586
|
+
longitude,
|
|
587
|
+
0,
|
|
588
|
+
20,
|
|
589
|
+
new Date().setHours(0, 0, 0, 0),
|
|
590
|
+
['eventId', 'name', 'content', 'startDate', 'endDate', 'location']
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
setEvents(loadedEvents);
|
|
594
|
+
} catch(err: any) {
|
|
595
|
+
setError(err.message || 'Failed to load events');
|
|
596
|
+
console.error('Error loading events:', err);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
const handleCreateEvent = async () => {
|
|
601
|
+
try {
|
|
602
|
+
setError(null);
|
|
603
|
+
|
|
604
|
+
const newEvent = await eventActions.addEvent({
|
|
605
|
+
name: formData.name,
|
|
606
|
+
content: formData.content,
|
|
607
|
+
startDate: new Date(formData.startDate).getTime(),
|
|
608
|
+
endDate: new Date(formData.endDate).getTime(),
|
|
609
|
+
location: formData.location,
|
|
610
|
+
type: 'event'
|
|
611
|
+
}, ['eventId', 'name', 'content', 'startDate', 'endDate', 'location']);
|
|
612
|
+
|
|
613
|
+
setEvents([newEvent, ...events]);
|
|
614
|
+
setFormData({name: '', content: '', startDate: '', endDate: '', location: ''});
|
|
615
|
+
setIsCreating(false);
|
|
616
|
+
} catch(err: any) {
|
|
617
|
+
setError(err.message || 'Failed to create event');
|
|
618
|
+
console.error('Error creating event:', err);
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
const handleUpdateEvent = async (eventId: string, updates: Partial<EventType>) => {
|
|
623
|
+
try {
|
|
624
|
+
setError(null);
|
|
625
|
+
|
|
626
|
+
const updatedEvent = await eventActions.updateEvent(
|
|
627
|
+
{eventId, ...updates},
|
|
628
|
+
['eventId', 'name', 'content', 'startDate', 'endDate', 'location']
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
setEvents(events.map(e => e.eventId === eventId ? updatedEvent : e));
|
|
632
|
+
} catch(err: any) {
|
|
633
|
+
setError(err.message || 'Failed to update event');
|
|
634
|
+
console.error('Error updating event:', err);
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const handleDeleteEvent = async (eventId: string) => {
|
|
639
|
+
if(!confirm('Delete this event?')) return;
|
|
640
|
+
|
|
641
|
+
try {
|
|
642
|
+
setError(null);
|
|
643
|
+
await eventActions.deleteEvent(eventId);
|
|
644
|
+
setEvents(events.filter(e => e.eventId !== eventId));
|
|
645
|
+
} catch(err: any) {
|
|
646
|
+
setError(err.message || 'Failed to delete event');
|
|
647
|
+
console.error('Error deleting event:', err);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
return (
|
|
652
|
+
<div className="event-manager">
|
|
653
|
+
<h2>Event Management</h2>
|
|
654
|
+
{error && <div className="error">{error}</div>}
|
|
655
|
+
|
|
656
|
+
{isCreating ? (
|
|
657
|
+
<div className="create-event-form">
|
|
658
|
+
<input
|
|
659
|
+
type="text"
|
|
660
|
+
placeholder="Event name"
|
|
661
|
+
value={formData.name}
|
|
662
|
+
onChange={(e) => setFormData({...formData, name: e.target.value})}
|
|
663
|
+
/>
|
|
664
|
+
<textarea
|
|
665
|
+
placeholder="Description"
|
|
666
|
+
value={formData.content}
|
|
667
|
+
onChange={(e) => setFormData({...formData, content: e.target.value})}
|
|
668
|
+
rows={3}
|
|
669
|
+
/>
|
|
670
|
+
<input
|
|
671
|
+
type="datetime-local"
|
|
672
|
+
value={formData.startDate}
|
|
673
|
+
onChange={(e) => setFormData({...formData, startDate: e.target.value})}
|
|
674
|
+
/>
|
|
675
|
+
<input
|
|
676
|
+
type="datetime-local"
|
|
677
|
+
value={formData.endDate}
|
|
678
|
+
onChange={(e) => setFormData({...formData, endDate: e.target.value})}
|
|
679
|
+
/>
|
|
680
|
+
<input
|
|
681
|
+
type="text"
|
|
682
|
+
placeholder="Location"
|
|
683
|
+
value={formData.location}
|
|
684
|
+
onChange={(e) => setFormData({...formData, location: e.target.value})}
|
|
685
|
+
/>
|
|
686
|
+
<button onClick={handleCreateEvent} disabled={!formData.name || !formData.startDate}>
|
|
687
|
+
Create Event
|
|
688
|
+
</button>
|
|
689
|
+
<button onClick={() => setIsCreating(false)}>Cancel</button>
|
|
690
|
+
</div>
|
|
691
|
+
) : (
|
|
692
|
+
<button onClick={() => setIsCreating(true)}>Create New Event</button>
|
|
693
|
+
)}
|
|
694
|
+
|
|
695
|
+
<div className="event-list">
|
|
696
|
+
{events.map(event => (
|
|
697
|
+
<div key={event.eventId} className="event-item">
|
|
698
|
+
<h3>{event.name}</h3>
|
|
699
|
+
<p>{event.content}</p>
|
|
700
|
+
<p>
|
|
701
|
+
{new Date(event.startDate!).toLocaleString()} - {new Date(event.endDate!).toLocaleString()}
|
|
702
|
+
</p>
|
|
703
|
+
<p>Location: {event.location}</p>
|
|
704
|
+
<button onClick={() => handleDeleteEvent(event.eventId!)}>Delete</button>
|
|
705
|
+
</div>
|
|
706
|
+
))}
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
);
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// =============================================================================
|
|
713
|
+
// User Management Example
|
|
714
|
+
// =============================================================================
|
|
715
|
+
|
|
716
|
+
export const UserProfileExample = ({userId}: {userId: string}) => {
|
|
717
|
+
const userActions = useUserActions();
|
|
718
|
+
const [profile, setProfile] = useState<any>(null);
|
|
719
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
720
|
+
const [editData, setEditData] = useState({
|
|
721
|
+
displayName: '',
|
|
722
|
+
bio: '',
|
|
723
|
+
location: ''
|
|
724
|
+
});
|
|
725
|
+
const [error, setError] = useState<string | null>(null);
|
|
726
|
+
|
|
727
|
+
useEffect(() => {
|
|
728
|
+
const loadProfile = async () => {
|
|
729
|
+
try {
|
|
730
|
+
setError(null);
|
|
731
|
+
const user = await userActions.itemById(userId, [
|
|
732
|
+
'userId',
|
|
733
|
+
'username',
|
|
734
|
+
'displayName',
|
|
735
|
+
'email',
|
|
736
|
+
'bio',
|
|
737
|
+
'location',
|
|
738
|
+
'avatarUrl'
|
|
739
|
+
]);
|
|
740
|
+
|
|
741
|
+
setProfile(user);
|
|
742
|
+
setEditData({
|
|
743
|
+
displayName: user.displayName || '',
|
|
744
|
+
bio: user.bio || '',
|
|
745
|
+
location: user.location || ''
|
|
746
|
+
});
|
|
747
|
+
} catch(err: any) {
|
|
748
|
+
setError(err.message || 'Failed to load profile');
|
|
749
|
+
console.error('Error loading profile:', err);
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
loadProfile();
|
|
754
|
+
}, [userId, userActions]);
|
|
755
|
+
|
|
756
|
+
const handleUpdateProfile = async () => {
|
|
757
|
+
try {
|
|
758
|
+
setError(null);
|
|
759
|
+
|
|
760
|
+
const updatedUser = await userActions.update({
|
|
761
|
+
userId,
|
|
762
|
+
...editData
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
setProfile(updatedUser);
|
|
766
|
+
setIsEditing(false);
|
|
767
|
+
} catch(err: any) {
|
|
768
|
+
setError(err.message || 'Failed to update profile');
|
|
769
|
+
console.error('Error updating profile:', err);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
if(!profile) return <div>Loading profile...</div>;
|
|
774
|
+
if(error) return <div className="error">Error: {error}</div>;
|
|
775
|
+
|
|
776
|
+
return (
|
|
777
|
+
<div className="user-profile">
|
|
778
|
+
<h2>User Profile</h2>
|
|
779
|
+
{isEditing ? (
|
|
780
|
+
<div>
|
|
781
|
+
<input
|
|
782
|
+
type="text"
|
|
783
|
+
placeholder="Display name"
|
|
784
|
+
value={editData.displayName}
|
|
785
|
+
onChange={(e) => setEditData({...editData, displayName: e.target.value})}
|
|
786
|
+
/>
|
|
787
|
+
<textarea
|
|
788
|
+
placeholder="Bio"
|
|
789
|
+
value={editData.bio}
|
|
790
|
+
onChange={(e) => setEditData({...editData, bio: e.target.value})}
|
|
791
|
+
rows={3}
|
|
792
|
+
/>
|
|
793
|
+
<input
|
|
794
|
+
type="text"
|
|
795
|
+
placeholder="Location"
|
|
796
|
+
value={editData.location}
|
|
797
|
+
onChange={(e) => setEditData({...editData, location: e.target.value})}
|
|
798
|
+
/>
|
|
799
|
+
<button onClick={handleUpdateProfile}>Save</button>
|
|
800
|
+
<button onClick={() => setIsEditing(false)}>Cancel</button>
|
|
801
|
+
</div>
|
|
802
|
+
) : (
|
|
803
|
+
<div>
|
|
804
|
+
<p><strong>Username:</strong> {profile.username}</p>
|
|
805
|
+
<p><strong>Display Name:</strong> {profile.displayName}</p>
|
|
806
|
+
<p><strong>Email:</strong> {profile.email}</p>
|
|
807
|
+
<p><strong>Bio:</strong> {profile.bio}</p>
|
|
808
|
+
<p><strong>Location:</strong> {profile.location}</p>
|
|
809
|
+
<button onClick={() => setIsEditing(true)}>Edit Profile</button>
|
|
810
|
+
</div>
|
|
811
|
+
)}
|
|
812
|
+
</div>
|
|
813
|
+
);
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
// =============================================================================
|
|
817
|
+
// Complete Application Example
|
|
818
|
+
// =============================================================================
|
|
819
|
+
|
|
820
|
+
export const CompleteCRUDExample = () => {
|
|
821
|
+
const [activeTab, setActiveTab] = useState<'posts' | 'groups' | 'events' | 'conversations'>('posts');
|
|
822
|
+
|
|
823
|
+
return (
|
|
824
|
+
<div className="crud-example-app">
|
|
825
|
+
<nav>
|
|
826
|
+
<button onClick={() => setActiveTab('posts')}>Posts</button>
|
|
827
|
+
<button onClick={() => setActiveTab('groups')}>Groups</button>
|
|
828
|
+
<button onClick={() => setActiveTab('events')}>Events</button>
|
|
829
|
+
<button onClick={() => setActiveTab('conversations')}>Conversations</button>
|
|
830
|
+
</nav>
|
|
831
|
+
|
|
832
|
+
<main>
|
|
833
|
+
{activeTab === 'posts' && (
|
|
834
|
+
<>
|
|
835
|
+
<PostCreatorExample />
|
|
836
|
+
<PostListExample />
|
|
837
|
+
</>
|
|
838
|
+
)}
|
|
839
|
+
{activeTab === 'groups' && <GroupManagerExample />}
|
|
840
|
+
{activeTab === 'events' && <EventManagerExample />}
|
|
841
|
+
{activeTab === 'conversations' && <ConversationListExample />}
|
|
842
|
+
</main>
|
|
843
|
+
</div>
|
|
844
|
+
);
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
export default CompleteCRUDExample;
|