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.
- package/LICENSE +675 -0
- package/README.md +104 -0
- package/bin/cli.js +3 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +92 -0
- package/dist/cli.js.map +1 -0
- package/dist/git.d.ts +7 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +80 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +5 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +30 -0
- package/dist/prompts.js.map +1 -0
- package/dist/replacer.d.ts +9 -0
- package/dist/replacer.d.ts.map +1 -0
- package/dist/replacer.js +11 -0
- package/dist/replacer.js.map +1 -0
- package/dist/scaffold.d.ts +3 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +79 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/types/index.d.ts +16 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/files.d.ts +6 -0
- package/dist/utils/files.d.ts.map +1 -0
- package/dist/utils/files.js +103 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +35 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/validation.d.ts +6 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +63 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +52 -0
- package/template/.env.example +159 -0
- package/template/.github/workflows/check-library-updates.yml +71 -0
- package/template/.github/workflows/dev.yml +63 -0
- package/template/.github/workflows/pull-request.yml +55 -0
- package/template/.gitmodules +6 -0
- package/template/.husky/pre-commit +1 -0
- package/template/.husky/pre-push +1 -0
- package/template/.prettierignore +1 -0
- package/template/.prettierrc +13 -0
- package/template/.releaserc +134 -0
- package/template/.vscode/settings.json +16 -0
- package/template/CHANGELOG.md +0 -0
- package/template/CLAUDE.md +34 -0
- package/template/DOCKER.md +1591 -0
- package/template/Dockerfile +228 -0
- package/template/README.md +1 -0
- package/template/apps/api/.prettierrc +12 -0
- package/template/apps/api/eslint.config.mjs +54 -0
- package/template/apps/api/jest.config.js +29 -0
- package/template/apps/api/nest-cli.json +15 -0
- package/template/apps/api/package.json +155 -0
- package/template/apps/api/src/config/config.ts +17 -0
- package/template/apps/api/src/config/enums/job.name.ts +6 -0
- package/template/apps/api/src/config/enums/queue.id.ts +3 -0
- package/template/apps/api/src/config/interfaces/config.interface.ts +3 -0
- package/template/apps/api/src/features/features.modules.ts +6 -0
- package/template/apps/api/src/i18n/en/notifications.json +3 -0
- package/template/apps/api/src/main.ts +23 -0
- package/template/apps/api/src/neo4j.migrations/20250901_001.ts +33 -0
- package/template/apps/api/src/neo4j.migrations/20250901_002.ts +90 -0
- package/template/apps/api/src/neo4j.migrations/20250901_003.ts +57 -0
- package/template/apps/api/src/neo4j.migrations/20250901_004.ts +32 -0
- package/template/apps/api/src/neo4j.migrations/queries/migration.queries.ts +49 -0
- package/template/apps/api/src/types/langchain.d.ts +56 -0
- package/template/apps/api/tsconfig.build.json +4 -0
- package/template/apps/api/tsconfig.json +38 -0
- package/template/apps/web/.swcrc +26 -0
- package/template/apps/web/components.json +21 -0
- package/template/apps/web/eslint.config.mjs +33 -0
- package/template/apps/web/global.d.ts +7 -0
- package/template/apps/web/messages/en.json +249 -0
- package/template/apps/web/next.config.js +50 -0
- package/template/apps/web/package.json +146 -0
- package/template/apps/web/playwright.config.ts +86 -0
- package/template/apps/web/postcss.config.mjs +5 -0
- package/template/apps/web/public/sw.js +32 -0
- package/template/apps/web/src/app/[locale]/(admin)/administration/companies/[id]/page.tsx +46 -0
- package/template/apps/web/src/app/[locale]/(admin)/administration/companies/page.tsx +23 -0
- package/template/apps/web/src/app/[locale]/(admin)/layout.tsx +49 -0
- package/template/apps/web/src/app/[locale]/(auth)/activation/[code]/page.tsx +13 -0
- package/template/apps/web/src/app/[locale]/(auth)/auth/page.tsx +11 -0
- package/template/apps/web/src/app/[locale]/(auth)/invitation/[code]/page.tsx +7 -0
- package/template/apps/web/src/app/[locale]/(auth)/layout.tsx +9 -0
- package/template/apps/web/src/app/[locale]/(auth)/login/page.tsx +6 -0
- package/template/apps/web/src/app/[locale]/(auth)/logout/page.tsx +5 -0
- package/template/apps/web/src/app/[locale]/(auth)/register/page.tsx +6 -0
- package/template/apps/web/src/app/[locale]/(auth)/reset/[code]/page.tsx +7 -0
- package/template/apps/web/src/app/[locale]/(main)/(foundations)/notifications/page.tsx +9 -0
- package/template/apps/web/src/app/[locale]/(main)/(foundations)/roles/[id]/page.tsx +23 -0
- package/template/apps/web/src/app/[locale]/(main)/(foundations)/roles/page.tsx +12 -0
- package/template/apps/web/src/app/[locale]/(main)/(foundations)/users/[id]/error.tsx +14 -0
- package/template/apps/web/src/app/[locale]/(main)/(foundations)/users/[id]/loading.tsx +21 -0
- package/template/apps/web/src/app/[locale]/(main)/(foundations)/users/[id]/page.tsx +46 -0
- package/template/apps/web/src/app/[locale]/(main)/(foundations)/users/page.tsx +17 -0
- package/template/apps/web/src/app/[locale]/(main)/error.tsx +62 -0
- package/template/apps/web/src/app/[locale]/(main)/layout.tsx +40 -0
- package/template/apps/web/src/app/[locale]/(main)/page.tsx +41 -0
- package/template/apps/web/src/app/[locale]/layout.tsx +54 -0
- package/template/apps/web/src/app/globals.css +256 -0
- package/template/apps/web/src/config/BootstrapProvider.tsx +13 -0
- package/template/apps/web/src/config/Bootstrapper.ts +77 -0
- package/template/apps/web/src/config/env.ts +51 -0
- package/template/apps/web/src/config/middleware-env.ts +14 -0
- package/template/apps/web/src/enums/feature.ids.ts +3 -0
- package/template/apps/web/src/features/common/components/containers/IndexContainer.tsx +11 -0
- package/template/apps/web/src/features/common/components/details/LayoutDetails.tsx +33 -0
- package/template/apps/web/src/features/common/components/navigations/CommonSidebar.tsx +233 -0
- package/template/apps/web/src/features/common/components/navigations/CreationDropDown.tsx +117 -0
- package/template/apps/web/src/features/common/components/navigations/UserSidebarFooter.tsx +115 -0
- package/template/apps/web/src/features/common/components/navigations/VersionDisplay.tsx +18 -0
- package/template/apps/web/src/features/common/contexts/ErrorContext.tsx +62 -0
- package/template/apps/web/src/i18n/request.ts +13 -0
- package/template/apps/web/src/i18n/routing.ts +9 -0
- package/template/apps/web/src/i18n/useDateFnsLocale.ts +15 -0
- package/template/apps/web/src/proxy.ts +107 -0
- package/template/apps/web/src/server-actions/auth-cookies.ts +134 -0
- package/template/apps/web/src/types/modules.d.ts +10 -0
- package/template/apps/web/src/utils/metadata.ts +50 -0
- package/template/apps/web/src/utils/revalidation.ts +7 -0
- package/template/apps/web/tsconfig.json +51 -0
- package/template/docker-compose.yml +211 -0
- package/template/package.json +72 -0
- package/template/packages/nestjs-neo4jsonapi/.gitkeep +0 -0
- package/template/packages/nextjs-jsonapi/.gitkeep +0 -0
- package/template/packages/shared/package.json +23 -0
- package/template/packages/shared/src/const/roles.id.ts +5 -0
- package/template/packages/shared/src/const/system.roles.id.ts +4 -0
- package/template/packages/shared/src/index.ts +1 -0
- package/template/packages/shared/tsconfig.json +10 -0
- package/template/packages/shared/tsup.config.ts +16 -0
- package/template/pnpm-workspace.yaml +3 -0
- package/template/tsconfig.base.json +62 -0
- package/template/tsconfig.json +12 -0
- package/template/turbo.json +88 -0
- 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
|
+
}
|