create-carlonicora-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/LICENSE +675 -0
  2. package/README.md +104 -0
  3. package/bin/cli.js +3 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +92 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/git.d.ts +7 -0
  9. package/dist/git.d.ts.map +1 -0
  10. package/dist/git.js +80 -0
  11. package/dist/git.js.map +1 -0
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +5 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/prompts.d.ts +5 -0
  17. package/dist/prompts.d.ts.map +1 -0
  18. package/dist/prompts.js +30 -0
  19. package/dist/prompts.js.map +1 -0
  20. package/dist/replacer.d.ts +9 -0
  21. package/dist/replacer.d.ts.map +1 -0
  22. package/dist/replacer.js +11 -0
  23. package/dist/replacer.js.map +1 -0
  24. package/dist/scaffold.d.ts +3 -0
  25. package/dist/scaffold.d.ts.map +1 -0
  26. package/dist/scaffold.js +79 -0
  27. package/dist/scaffold.js.map +1 -0
  28. package/dist/types/index.d.ts +16 -0
  29. package/dist/types/index.d.ts.map +1 -0
  30. package/dist/types/index.js +2 -0
  31. package/dist/types/index.js.map +1 -0
  32. package/dist/utils/files.d.ts +6 -0
  33. package/dist/utils/files.d.ts.map +1 -0
  34. package/dist/utils/files.js +103 -0
  35. package/dist/utils/files.js.map +1 -0
  36. package/dist/utils/logger.d.ts +12 -0
  37. package/dist/utils/logger.d.ts.map +1 -0
  38. package/dist/utils/logger.js +35 -0
  39. package/dist/utils/logger.js.map +1 -0
  40. package/dist/utils/validation.d.ts +6 -0
  41. package/dist/utils/validation.d.ts.map +1 -0
  42. package/dist/utils/validation.js +63 -0
  43. package/dist/utils/validation.js.map +1 -0
  44. package/package.json +52 -0
  45. package/template/.env.example +159 -0
  46. package/template/.github/workflows/check-library-updates.yml +71 -0
  47. package/template/.github/workflows/dev.yml +63 -0
  48. package/template/.github/workflows/pull-request.yml +55 -0
  49. package/template/.gitmodules +6 -0
  50. package/template/.husky/pre-commit +1 -0
  51. package/template/.husky/pre-push +1 -0
  52. package/template/.prettierignore +1 -0
  53. package/template/.prettierrc +13 -0
  54. package/template/.releaserc +134 -0
  55. package/template/.vscode/settings.json +16 -0
  56. package/template/CHANGELOG.md +0 -0
  57. package/template/CLAUDE.md +34 -0
  58. package/template/DOCKER.md +1591 -0
  59. package/template/Dockerfile +228 -0
  60. package/template/README.md +1 -0
  61. package/template/apps/api/.prettierrc +12 -0
  62. package/template/apps/api/eslint.config.mjs +54 -0
  63. package/template/apps/api/jest.config.js +29 -0
  64. package/template/apps/api/nest-cli.json +15 -0
  65. package/template/apps/api/package.json +155 -0
  66. package/template/apps/api/src/config/config.ts +17 -0
  67. package/template/apps/api/src/config/enums/job.name.ts +6 -0
  68. package/template/apps/api/src/config/enums/queue.id.ts +3 -0
  69. package/template/apps/api/src/config/interfaces/config.interface.ts +3 -0
  70. package/template/apps/api/src/features/features.modules.ts +6 -0
  71. package/template/apps/api/src/i18n/en/notifications.json +3 -0
  72. package/template/apps/api/src/main.ts +23 -0
  73. package/template/apps/api/src/neo4j.migrations/20250901_001.ts +33 -0
  74. package/template/apps/api/src/neo4j.migrations/20250901_002.ts +90 -0
  75. package/template/apps/api/src/neo4j.migrations/20250901_003.ts +57 -0
  76. package/template/apps/api/src/neo4j.migrations/20250901_004.ts +32 -0
  77. package/template/apps/api/src/neo4j.migrations/queries/migration.queries.ts +49 -0
  78. package/template/apps/api/src/types/langchain.d.ts +56 -0
  79. package/template/apps/api/tsconfig.build.json +4 -0
  80. package/template/apps/api/tsconfig.json +38 -0
  81. package/template/apps/web/.swcrc +26 -0
  82. package/template/apps/web/components.json +21 -0
  83. package/template/apps/web/eslint.config.mjs +33 -0
  84. package/template/apps/web/global.d.ts +7 -0
  85. package/template/apps/web/messages/en.json +249 -0
  86. package/template/apps/web/next.config.js +50 -0
  87. package/template/apps/web/package.json +146 -0
  88. package/template/apps/web/playwright.config.ts +86 -0
  89. package/template/apps/web/postcss.config.mjs +5 -0
  90. package/template/apps/web/public/sw.js +32 -0
  91. package/template/apps/web/src/app/[locale]/(admin)/administration/companies/[id]/page.tsx +46 -0
  92. package/template/apps/web/src/app/[locale]/(admin)/administration/companies/page.tsx +23 -0
  93. package/template/apps/web/src/app/[locale]/(admin)/layout.tsx +49 -0
  94. package/template/apps/web/src/app/[locale]/(auth)/activation/[code]/page.tsx +13 -0
  95. package/template/apps/web/src/app/[locale]/(auth)/auth/page.tsx +11 -0
  96. package/template/apps/web/src/app/[locale]/(auth)/invitation/[code]/page.tsx +7 -0
  97. package/template/apps/web/src/app/[locale]/(auth)/layout.tsx +9 -0
  98. package/template/apps/web/src/app/[locale]/(auth)/login/page.tsx +6 -0
  99. package/template/apps/web/src/app/[locale]/(auth)/logout/page.tsx +5 -0
  100. package/template/apps/web/src/app/[locale]/(auth)/register/page.tsx +6 -0
  101. package/template/apps/web/src/app/[locale]/(auth)/reset/[code]/page.tsx +7 -0
  102. package/template/apps/web/src/app/[locale]/(main)/(foundations)/notifications/page.tsx +9 -0
  103. package/template/apps/web/src/app/[locale]/(main)/(foundations)/roles/[id]/page.tsx +23 -0
  104. package/template/apps/web/src/app/[locale]/(main)/(foundations)/roles/page.tsx +12 -0
  105. package/template/apps/web/src/app/[locale]/(main)/(foundations)/users/[id]/error.tsx +14 -0
  106. package/template/apps/web/src/app/[locale]/(main)/(foundations)/users/[id]/loading.tsx +21 -0
  107. package/template/apps/web/src/app/[locale]/(main)/(foundations)/users/[id]/page.tsx +46 -0
  108. package/template/apps/web/src/app/[locale]/(main)/(foundations)/users/page.tsx +17 -0
  109. package/template/apps/web/src/app/[locale]/(main)/error.tsx +62 -0
  110. package/template/apps/web/src/app/[locale]/(main)/layout.tsx +40 -0
  111. package/template/apps/web/src/app/[locale]/(main)/page.tsx +41 -0
  112. package/template/apps/web/src/app/[locale]/layout.tsx +54 -0
  113. package/template/apps/web/src/app/globals.css +256 -0
  114. package/template/apps/web/src/config/BootstrapProvider.tsx +13 -0
  115. package/template/apps/web/src/config/Bootstrapper.ts +77 -0
  116. package/template/apps/web/src/config/env.ts +51 -0
  117. package/template/apps/web/src/config/middleware-env.ts +14 -0
  118. package/template/apps/web/src/enums/feature.ids.ts +3 -0
  119. package/template/apps/web/src/features/common/components/containers/IndexContainer.tsx +11 -0
  120. package/template/apps/web/src/features/common/components/details/LayoutDetails.tsx +33 -0
  121. package/template/apps/web/src/features/common/components/navigations/CommonSidebar.tsx +233 -0
  122. package/template/apps/web/src/features/common/components/navigations/CreationDropDown.tsx +117 -0
  123. package/template/apps/web/src/features/common/components/navigations/UserSidebarFooter.tsx +115 -0
  124. package/template/apps/web/src/features/common/components/navigations/VersionDisplay.tsx +18 -0
  125. package/template/apps/web/src/features/common/contexts/ErrorContext.tsx +62 -0
  126. package/template/apps/web/src/i18n/request.ts +13 -0
  127. package/template/apps/web/src/i18n/routing.ts +9 -0
  128. package/template/apps/web/src/i18n/useDateFnsLocale.ts +15 -0
  129. package/template/apps/web/src/proxy.ts +107 -0
  130. package/template/apps/web/src/server-actions/auth-cookies.ts +134 -0
  131. package/template/apps/web/src/types/modules.d.ts +10 -0
  132. package/template/apps/web/src/utils/metadata.ts +50 -0
  133. package/template/apps/web/src/utils/revalidation.ts +7 -0
  134. package/template/apps/web/tsconfig.json +51 -0
  135. package/template/docker-compose.yml +211 -0
  136. package/template/package.json +72 -0
  137. package/template/packages/nestjs-neo4jsonapi/.gitkeep +0 -0
  138. package/template/packages/nextjs-jsonapi/.gitkeep +0 -0
  139. package/template/packages/shared/package.json +23 -0
  140. package/template/packages/shared/src/const/roles.id.ts +5 -0
  141. package/template/packages/shared/src/const/system.roles.id.ts +4 -0
  142. package/template/packages/shared/src/index.ts +1 -0
  143. package/template/packages/shared/tsconfig.json +10 -0
  144. package/template/packages/shared/tsup.config.ts +16 -0
  145. package/template/pnpm-workspace.yaml +3 -0
  146. package/template/tsconfig.base.json +62 -0
  147. package/template/tsconfig.json +12 -0
  148. package/template/turbo.json +88 -0
  149. package/template/versions.production.json +4 -0
@@ -0,0 +1,233 @@
1
+ "use client";
2
+
3
+ import CreationDropDown from "@/features/common/components/navigations/CreationDropDown";
4
+ import { useRouter } from "@/i18n/routing";
5
+ import { recentPagesAtom } from "@carlonicora/nextjs-jsonapi/atoms";
6
+ import { RecentPagesNavigator } from "@carlonicora/nextjs-jsonapi/components";
7
+ import { useCurrentUserContext, useNotificationContext } from "@carlonicora/nextjs-jsonapi/contexts";
8
+ import { usePageUrlGenerator } from "@carlonicora/nextjs-jsonapi/hooks";
9
+ import {
10
+ Link,
11
+ Sidebar,
12
+ SidebarContent,
13
+ SidebarFooter,
14
+ SidebarGroup,
15
+ SidebarGroupContent,
16
+ SidebarGroupLabel,
17
+ SidebarHeader,
18
+ SidebarMenu,
19
+ SidebarMenuButton,
20
+ Tooltip,
21
+ TooltipContent,
22
+ TooltipTrigger,
23
+ useSidebar,
24
+ } from "@carlonicora/nextjs-jsonapi/shadcnui";
25
+
26
+ import { UserSidebarFooter } from "@/features/common/components/navigations/UserSidebarFooter";
27
+ import { useAtomValue } from "jotai";
28
+ import { HistoryIcon, HomeIcon } from "lucide-react";
29
+ import { useTranslations } from "next-intl";
30
+ import Image from "next/image";
31
+ import { Fragment, ReactNode, useMemo, useState } from "react";
32
+
33
+ export type NavigationItem = {
34
+ title: string;
35
+ component?: React.ReactNode;
36
+ url: string;
37
+ onClick?: () => void;
38
+ icon: ReactNode;
39
+ testId?: string;
40
+ };
41
+
42
+ export default function CommonSidebar() {
43
+ const { state } = useSidebar();
44
+ const { currentUser, company, hasPermissionToPath, hasAccesToFeature, hasPermissionToModule, hasRole } =
45
+ useCurrentUserContext();
46
+ const generateUrl = usePageUrlGenerator();
47
+ const t = useTranslations();
48
+ const { notifications } = useNotificationContext();
49
+ const [notificationModalOpen, setNotificationModalOpen] = useState(false);
50
+ const recentPages = useAtomValue(recentPagesAtom);
51
+ const router = useRouter();
52
+
53
+ const unreadCount = useMemo(() => {
54
+ return notifications.filter((notif) => !notif.isRead).length;
55
+ }, [notifications]);
56
+
57
+ const navigationMap = useMemo(() => {
58
+ const navMap = new Map<string, { hasTitle: boolean; items: NavigationItem[] }>([
59
+ ["/", { hasTitle: false, items: [] }],
60
+ ["expertise", { hasTitle: true, items: [] }],
61
+ // ["knowledge", { hasTitle: true, items: [] }],
62
+ ]);
63
+
64
+ navMap.get("/")?.items.push({
65
+ title: t(`generic.home`),
66
+ url: generateUrl({ page: `/` }),
67
+ icon: <HomeIcon />,
68
+ testId: "sidebar-home-link",
69
+ });
70
+
71
+ if (company) {
72
+ if (recentPages.length > 0) {
73
+ navMap.get("/")?.items.push({
74
+ title: t(`generic.recent_pages`),
75
+ component: <RecentPagesNavigator />,
76
+ url: "#",
77
+ icon: <HistoryIcon />,
78
+ testId: "sidebar-recent-pages",
79
+ });
80
+ }
81
+ }
82
+
83
+ return navMap;
84
+ }, [currentUser, company, recentPages, t, generateUrl, hasRole]);
85
+
86
+ return (
87
+ <Sidebar data-testid="sidebar-container" collapsible="icon">
88
+ <SidebarHeader>
89
+ <Link
90
+ href={generateUrl({ page: `/` })}
91
+ className="mb-4 flex max-h-32 w-full items-center justify-center text-2xl font-semibold"
92
+ >
93
+ {state === "expanded" ? (
94
+ <Image
95
+ src={`/{{name}}-logo.webp`}
96
+ className="max-h-32 object-contain p-4"
97
+ height={300}
98
+ width={300}
99
+ alt={"Phlow"}
100
+ priority
101
+ />
102
+ ) : (
103
+ <Image
104
+ src={`/{{name}}-logo.webp`}
105
+ className="max-h-10 object-contain"
106
+ height={300}
107
+ width={300}
108
+ alt={"Phlow"}
109
+ priority
110
+ />
111
+ )}
112
+ </Link>
113
+ </SidebarHeader>
114
+ <SidebarContent>
115
+ <SidebarGroup className={`py-0 ${state === "collapsed" ? "pb-4" : "pb-1"}`}>
116
+ {state === "expanded" ? (
117
+ <SidebarGroupContent className="flex flex-col gap-2">
118
+ {/* <SidebarMenu>
119
+ <Button
120
+ onClick={() => router.push(generateUrl({ page: Modules.Conversation }))}
121
+ variant={"link"}
122
+ className="relative h-12 w-full cursor-pointer p-0"
123
+ data-testid="sidebar-query-knowledge-button"
124
+ >
125
+ <Input
126
+ placeholder={t(`generic.query_knowledge`)}
127
+ type="text"
128
+ className="border-primary text-muted m-0 w-full cursor-pointer"
129
+ />
130
+ <div className="text-muted-foreground absolute top-3.5 right-10 flex flex-row gap-x-1">
131
+ <span className="flex h-5 w-5 items-center justify-center rounded-md border text-xs">⌘</span>
132
+ <span className="flex h-5 w-5 items-center justify-center rounded-md border text-xs">K</span>
133
+ </div>
134
+ <div className="text-muted-foreground absolute top-3 right-2 flex flex-row gap-x-2">
135
+ <span className="bg-accent flex h-6 w-6 items-center justify-center rounded-md border text-xs">
136
+ <SparklesIcon className="text-muted h-5 w-5" />
137
+ </span>
138
+ </div>
139
+ </Button>
140
+ </SidebarMenu> */}
141
+ <CreationDropDown />
142
+ </SidebarGroupContent>
143
+ ) : (
144
+ <SidebarGroupContent className="flex flex-col gap-2">
145
+ {/* <SidebarMenu>
146
+ <Tooltip>
147
+ <TooltipTrigger asChild>
148
+ <Button
149
+ onClick={() => router.push(generateUrl({ page: Modules.Conversation }))}
150
+ className="bg-accent relative cursor-pointer"
151
+ data-testid="sidebar-query-knowledge-button"
152
+ >
153
+ <SparklesIcon className="text-muted h-5 w-5" />
154
+ </Button>
155
+ </TooltipTrigger>
156
+ <TooltipContent side="right">{t(`generic.query_knowledge`)}</TooltipContent>
157
+ </Tooltip>
158
+ </SidebarMenu> */}
159
+ <CreationDropDown />
160
+ </SidebarGroupContent>
161
+ )}
162
+ </SidebarGroup>
163
+ {Array.from(navigationMap.entries())
164
+ .filter(([groupLabel, items]) => items.items.length > 0)
165
+ .map(([groupLabel, items]) => (
166
+ <SidebarGroup key={groupLabel} className={`py-0 ${state === "collapsed" ? "pb-4" : "pb-1"}`}>
167
+ {groupLabel !== "/" && state !== "collapsed" && items.hasTitle && (
168
+ <SidebarGroupLabel className="min-h-10 font-semibold">
169
+ {t(`generic.sidebar`, { type: groupLabel })}
170
+ </SidebarGroupLabel>
171
+ )}
172
+ <SidebarMenu className="gap-0">
173
+ {items.items.map((item) => {
174
+ if (item.url && !hasPermissionToPath(item.url)) return null;
175
+
176
+ const isDropdown = item.url === "#" && item.component;
177
+
178
+ return (
179
+ <Fragment key={item.title}>
180
+ {state === "collapsed" ? (
181
+ <Tooltip>
182
+ <TooltipTrigger asChild>
183
+ {isDropdown ? (
184
+ <SidebarMenuButton className="text-muted-foreground" data-testid={item.testId}>
185
+ {item.component}
186
+ </SidebarMenuButton>
187
+ ) : (
188
+ <SidebarMenuButton
189
+ asChild
190
+ className="text-muted-foreground cursor-pointer"
191
+ data-testid={item.testId}
192
+ onClick={item.onClick}
193
+ >
194
+ <Link href={item.url ? item.url : "#"}>
195
+ {item.icon}
196
+ {item.component ? item.component : <span>{item.title}</span>}
197
+ </Link>
198
+ </SidebarMenuButton>
199
+ )}
200
+ </TooltipTrigger>
201
+ <TooltipContent side="right">{item.title}</TooltipContent>
202
+ </Tooltip>
203
+ ) : (
204
+ <SidebarMenuButton asChild={!isDropdown} data-testid={item.testId} onClick={item.onClick}>
205
+ {isDropdown ? (
206
+ <div className="text-muted-foreground flex w-full items-center gap-2">
207
+ {item.icon}
208
+ {item.component}
209
+ </div>
210
+ ) : (
211
+ <Link href={item.url ? item.url : "#"} className="text-muted-foreground cursor-pointer">
212
+ {item.icon}
213
+ {item.component ? item.component : <span>{item.title}</span>}
214
+ </Link>
215
+ )}
216
+ </SidebarMenuButton>
217
+ )}
218
+ </Fragment>
219
+ );
220
+ })}
221
+ </SidebarMenu>
222
+ </SidebarGroup>
223
+ ))}
224
+ </SidebarContent>
225
+ <SidebarFooter className="border-t">
226
+ <UserSidebarFooter
227
+ notificationModalOpen={notificationModalOpen}
228
+ setNotificationModalOpen={setNotificationModalOpen}
229
+ />
230
+ </SidebarFooter>
231
+ </Sidebar>
232
+ );
233
+ }
@@ -0,0 +1,117 @@
1
+ "use client";
2
+
3
+ import { useCurrentUserContext } from "@carlonicora/nextjs-jsonapi/contexts";
4
+ import { UserInterface } from "@carlonicora/nextjs-jsonapi/features";
5
+ import {
6
+ Button,
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuLabel,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ useSidebar,
13
+ } from "@carlonicora/nextjs-jsonapi/shadcnui";
14
+ import { PlusCircleIcon } from "lucide-react";
15
+ import { useTranslations } from "next-intl";
16
+ import { useState } from "react";
17
+
18
+ export default function CreationDropDown() {
19
+ const { state } = useSidebar();
20
+ const t = useTranslations();
21
+ const { hasPermissionToModule } = useCurrentUserContext<UserInterface>();
22
+
23
+ const [menuOpen, setMenuOpen] = useState<boolean>(false);
24
+
25
+ // const [newArticleOpen, setNewArticleOpen] = useState<boolean>(false);
26
+ // const [newHyperlinkOpen, setNewHyperlinkOpen] = useState<boolean>(false);
27
+ // const [newDocumentOpen, setNewDocumentOpen] = useState<boolean>(false);
28
+ // const [newGlossaryOpen, setNewGlossaryOpen] = useState<boolean>(false);
29
+ // const [newDiscussionOpen, setNewDiscussionOpen] = useState<boolean>(false);
30
+
31
+ // const handleArticleClick = () => {
32
+ // setMenuOpen(false);
33
+ // requestAnimationFrame(() => {
34
+ // setNewArticleOpen(true);
35
+ // });
36
+ // };
37
+
38
+ // const handleHyperlinkClick = () => {
39
+ // setMenuOpen(false);
40
+ // requestAnimationFrame(() => {
41
+ // setNewHyperlinkOpen(true);
42
+ // });
43
+ // };
44
+
45
+ // const handleDocumentClick = () => {
46
+ // setMenuOpen(false);
47
+ // requestAnimationFrame(() => {
48
+ // setNewDocumentOpen(true);
49
+ // });
50
+ // };
51
+
52
+ // const handleGlossaryClick = () => {
53
+ // setMenuOpen(false);
54
+ // requestAnimationFrame(() => {
55
+ // setNewGlossaryOpen(true);
56
+ // });
57
+ // };
58
+
59
+ // const handleDiscussionClick = () => {
60
+ // setMenuOpen(false);
61
+ // requestAnimationFrame(() => {
62
+ // setNewDiscussionOpen(true);
63
+ // });
64
+ // };
65
+
66
+ return (
67
+ <>
68
+ <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
69
+ <DropdownMenuTrigger asChild suppressHydrationWarning>
70
+ <Button variant="outline" className="bg-accent text-accent-foreground">
71
+ <PlusCircleIcon />
72
+ {state === "collapsed" ? <></> : <span>{t(`generic.create`)}</span>}
73
+ </Button>
74
+ </DropdownMenuTrigger>
75
+ <DropdownMenuContent align="start" className="w-96">
76
+ <DropdownMenuLabel>{t(`generic.create_new`)}</DropdownMenuLabel>
77
+ <DropdownMenuSeparator />
78
+ {/* <DropdownMenuItem onClick={handleArticleClick} className="flex items-center gap-2 font-normal">
79
+ {getIconByModule({ module: Modules.Article })}
80
+ {t(`types.articles`, { count: 1 })}
81
+ </DropdownMenuItem>
82
+ <DropdownMenuItem onClick={handleHyperlinkClick} className="flex items-center gap-2 font-normal">
83
+ {getIconByModule({ module: Modules.Hyperlink })}
84
+ {t(`types.hyperlinks`, { count: 1 })}
85
+ </DropdownMenuItem>
86
+ <DropdownMenuItem onClick={handleDocumentClick} className="flex items-center gap-2 font-normal">
87
+ {getIconByModule({ module: Modules.Document })}
88
+ {t(`types.documents`, { count: 1 })}
89
+ </DropdownMenuItem>
90
+ <DropdownMenuItem onClick={handleGlossaryClick} className="flex items-center gap-2 font-normal">
91
+ {getIconByModule({ module: Modules.Glossary })}
92
+ {t(`types.glossaries`, { count: 1 })}
93
+ </DropdownMenuItem>
94
+ <DropdownMenuItem onClick={handleDiscussionClick} className="flex items-center gap-2 font-normal">
95
+ {getIconByModule({ module: Modules.Discussion })}
96
+ {t(`types.discussions`, { count: 1 })}
97
+ </DropdownMenuItem> */}
98
+ </DropdownMenuContent>
99
+ </DropdownMenu>
100
+ {/* {hasPermissionToModule({ module: Modules.Article, action: Action.Create }) && (
101
+ <ArticleEditor dialogOpen={newArticleOpen} onDialogOpenChange={setNewArticleOpen} />
102
+ )}
103
+ {hasPermissionToModule({ module: Modules.Hyperlink, action: Action.Create }) && (
104
+ <HyperlinkEditor dialogOpen={newHyperlinkOpen} onDialogOpenChange={setNewHyperlinkOpen} />
105
+ )}
106
+ {hasPermissionToModule({ module: Modules.Document, action: Action.Create }) && (
107
+ <DocumentEditor dialogOpen={newDocumentOpen} onDialogOpenChange={setNewDocumentOpen} />
108
+ )}
109
+ {hasPermissionToModule({ module: Modules.Glossary, action: Action.Create }) && (
110
+ <GlossaryEditor dialogOpen={newGlossaryOpen} onDialogOpenChange={setNewGlossaryOpen} />
111
+ )}
112
+ {hasPermissionToModule({ module: Modules.Discussion, action: Action.Create }) && (
113
+ <DiscussionEditor dialogOpen={newDiscussionOpen} onDialogOpenChange={setNewDiscussionOpen} />
114
+ )} */}
115
+ </>
116
+ );
117
+ }
@@ -0,0 +1,115 @@
1
+ "use client";
2
+
3
+ import VersionDisplay from "@/features/common/components/navigations/VersionDisplay";
4
+ import { Modules } from "@carlonicora/nextjs-jsonapi";
5
+ import { ModeToggleSwitch, NotificationModal, UserAvatar } from "@carlonicora/nextjs-jsonapi/components";
6
+ import { useCurrentUserContext } from "@carlonicora/nextjs-jsonapi/contexts";
7
+ import { UserInterface } from "@carlonicora/nextjs-jsonapi/features";
8
+ import { usePageUrlGenerator } from "@carlonicora/nextjs-jsonapi/hooks";
9
+ import {
10
+ DropdownMenu,
11
+ DropdownMenuContent,
12
+ DropdownMenuGroup,
13
+ DropdownMenuItem,
14
+ DropdownMenuLabel,
15
+ DropdownMenuSeparator,
16
+ DropdownMenuTrigger,
17
+ Link,
18
+ SidebarMenu,
19
+ SidebarMenuButton,
20
+ SidebarMenuItem,
21
+ useSidebar,
22
+ } from "@carlonicora/nextjs-jsonapi/shadcnui";
23
+ import { RoleId } from "@{{name}}/shared";
24
+ import { ChevronsUpDown, LogOut, SettingsIcon, UserIcon } from "lucide-react";
25
+ import { useTranslations } from "next-intl";
26
+
27
+ type UserSidebarFooterProps = {
28
+ notificationModalOpen: boolean;
29
+ setNotificationModalOpen: (open: boolean) => void;
30
+ };
31
+
32
+ export function UserSidebarFooter({ notificationModalOpen, setNotificationModalOpen }: UserSidebarFooterProps) {
33
+ const { currentUser, hasRole } = useCurrentUserContext<UserInterface>();
34
+ const { isMobile } = useSidebar();
35
+ const generateUrl = usePageUrlGenerator();
36
+ const t = useTranslations();
37
+
38
+ const logOut = async () => {
39
+ window.location.href = generateUrl({ page: `/logout` });
40
+ };
41
+
42
+ return (
43
+ <SidebarMenu>
44
+ {currentUser && !hasRole(RoleId.Administrator) && (
45
+ <SidebarMenuItem>
46
+ <NotificationModal isOpen={notificationModalOpen} setIsOpen={setNotificationModalOpen} />
47
+ </SidebarMenuItem>
48
+ )}
49
+ {hasRole(RoleId.CompanyAdministrator) && (
50
+ <Link href={generateUrl({ page: `/settings` })}>
51
+ <SidebarMenuItem>
52
+ <SidebarMenuButton className="text-muted-foreground">
53
+ <SettingsIcon />
54
+ {t(`generic.settings`)}
55
+ </SidebarMenuButton>
56
+ </SidebarMenuItem>
57
+ </Link>
58
+ )}
59
+ <SidebarMenuItem className="-ml-0.5">
60
+ {currentUser && (
61
+ <DropdownMenu>
62
+ <DropdownMenuTrigger asChild>
63
+ <SidebarMenuButton className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
64
+ <UserAvatar user={currentUser} className="h-5 w-5" />
65
+ <div className="grid flex-1 text-left text-sm leading-tight">
66
+ <span className="truncate font-semibold">{currentUser.name}</span>
67
+ <span className="truncate text-xs">{currentUser.email}</span>
68
+ </div>
69
+ <ChevronsUpDown className="ml-auto size-4" />
70
+ </SidebarMenuButton>
71
+ </DropdownMenuTrigger>
72
+ <DropdownMenuContent
73
+ className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
74
+ side={isMobile ? "bottom" : "right"}
75
+ align="end"
76
+ sideOffset={4}
77
+ >
78
+ <DropdownMenuLabel className="p-0 font-normal">
79
+ <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
80
+ <div className="grid flex-1 text-left text-sm leading-tight">
81
+ <span className="truncate font-semibold">{currentUser.name}</span>
82
+ <span className="truncate text-xs">{currentUser.email}</span>
83
+ </div>
84
+ </div>
85
+ </DropdownMenuLabel>
86
+ <DropdownMenuSeparator />
87
+ <DropdownMenuLabel>
88
+ <VersionDisplay />
89
+ </DropdownMenuLabel>
90
+ <DropdownMenuSeparator />
91
+ <DropdownMenuItem onSelect={(event) => event.preventDefault()}>
92
+ <ModeToggleSwitch />
93
+ {t(`generic.theme`)}
94
+ </DropdownMenuItem>
95
+ <DropdownMenuSeparator />
96
+ <DropdownMenuGroup>
97
+ <Link href={generateUrl({ page: Modules.User, id: currentUser.id })}>
98
+ <DropdownMenuItem>
99
+ <UserIcon />
100
+ {t(`generic.my_profile`)}
101
+ </DropdownMenuItem>
102
+ </Link>
103
+ </DropdownMenuGroup>
104
+ <DropdownMenuSeparator />
105
+ <DropdownMenuItem onClick={logOut}>
106
+ <LogOut />
107
+ {t(`foundations.auth.buttons.logout`)}
108
+ </DropdownMenuItem>
109
+ </DropdownMenuContent>
110
+ </DropdownMenu>
111
+ )}
112
+ </SidebarMenuItem>
113
+ </SidebarMenu>
114
+ );
115
+ }
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import packageInfo from "../../../../../../../package.json";
4
+
5
+ export const getAppVersion = () => {
6
+ return packageInfo.version;
7
+ };
8
+
9
+ export default function VersionDisplay() {
10
+ return (
11
+ <div className="text-muted-foreground flex w-full flex-col text-xs">
12
+ <div className="flex w-full flex-row justify-between">
13
+ <div className="flex w-full">Phlow Version</div>
14
+ <div className="flex">{getAppVersion()}</div>
15
+ </div>
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import { ErrorDetails } from "@carlonicora/nextjs-jsonapi/components";
4
+ import { Logout } from "@carlonicora/nextjs-jsonapi/components";
5
+ import { useMessages } from "next-intl";
6
+ import { createContext, ReactNode, useContext, useEffect, useState } from "react";
7
+
8
+ interface ErrorContextType {
9
+ error: { status: number; message: string } | null;
10
+ showError: (status: number, message: string) => void;
11
+ clearError: () => void;
12
+ }
13
+
14
+ const ErrorContext = createContext<ErrorContextType | undefined>(undefined);
15
+
16
+ export function useErrorHandler() {
17
+ const context = useContext(ErrorContext);
18
+ if (!context) {
19
+ throw new Error("useErrorHandler must be used within an ErrorProvider");
20
+ }
21
+ return context;
22
+ }
23
+
24
+ export function ErrorProvider({ children }: { children: ReactNode }) {
25
+ const [error, setError] = useState<{ status: number; message: string } | null>(null);
26
+ const messages = useMessages() as any;
27
+
28
+ const showError = (status: number, message: string) => {
29
+ setError({ status, message });
30
+ };
31
+
32
+ const clearError = () => {
33
+ setError(null);
34
+ };
35
+
36
+ useEffect(() => {
37
+ const { setGlobalErrorHandler } = require("@/data/abstracts/AbstractService");
38
+ setGlobalErrorHandler(showError);
39
+
40
+ return () => {
41
+ setGlobalErrorHandler(null);
42
+ };
43
+ }, []);
44
+
45
+ if (error) {
46
+ const errorMessages = messages?.errors?.[error.status.toString()];
47
+
48
+ if (error.status === 401) return <Logout />;
49
+
50
+ return (
51
+ <div className="flex min-h-screen w-full flex-col items-center justify-center">
52
+ <ErrorDetails
53
+ code={error.status}
54
+ title={errorMessages?.title || `Error ${error.status}`}
55
+ message={errorMessages?.message || error.message}
56
+ />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ return <ErrorContext.Provider value={{ error, showError, clearError }}>{children}</ErrorContext.Provider>;
62
+ }
@@ -0,0 +1,13 @@
1
+ import { hasLocale } from "next-intl";
2
+ import { getRequestConfig } from "next-intl/server";
3
+ import { routing } from "./routing";
4
+
5
+ export default getRequestConfig(async ({ requestLocale }) => {
6
+ const requested = await requestLocale;
7
+ const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;
8
+
9
+ return {
10
+ locale,
11
+ messages: (await import(`../../messages/${locale}.json`)).default,
12
+ };
13
+ });
@@ -0,0 +1,9 @@
1
+ import { createNavigation } from "next-intl/navigation";
2
+ import { defineRouting } from "next-intl/routing";
3
+
4
+ export const routing = defineRouting({
5
+ locales: ["en"],
6
+ defaultLocale: "en",
7
+ });
8
+
9
+ export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import { useLocale } from "next-intl";
4
+ import { enUS, it } from "date-fns/locale";
5
+ import type { Locale } from "date-fns";
6
+
7
+ const dateFnsLocales: Record<string, Locale> = {
8
+ en: enUS,
9
+ it: it,
10
+ };
11
+
12
+ export function useDateFnsLocale(): Locale {
13
+ const locale = useLocale();
14
+ return dateFnsLocales[locale] ?? enUS;
15
+ }