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