@carlonicora/nextjs-jsonapi 1.67.0 → 1.69.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 (100) hide show
  1. package/dist/{AuthComponent-NwQ_ZXsv.d.mts → AuthComponent-DXe3kPzb.d.mts} +1 -1
  2. package/dist/{AuthComponent-DL1D3y7f.d.ts → AuthComponent-Di8DsZ2I.d.ts} +1 -1
  3. package/dist/{BlockNoteEditor-QHWPE3BJ.js → BlockNoteEditor-CBUEPFA7.js} +14 -14
  4. package/dist/{BlockNoteEditor-QHWPE3BJ.js.map → BlockNoteEditor-CBUEPFA7.js.map} +1 -1
  5. package/dist/{BlockNoteEditor-TIX3GDVZ.mjs → BlockNoteEditor-ON5Q6QWO.mjs} +4 -4
  6. package/dist/HowToInterface-DtVWAE1s.d.mts +17 -0
  7. package/dist/HowToInterface-NaqSG9sE.d.ts +17 -0
  8. package/dist/{auth.interface-BX_1qZZJ.d.ts → auth.interface-BTco8PWs.d.ts} +1 -1
  9. package/dist/{auth.interface-yeLelxdI.d.mts → auth.interface-C4uJzBec.d.mts} +1 -1
  10. package/dist/billing/index.js +346 -346
  11. package/dist/billing/index.mjs +3 -3
  12. package/dist/{chunk-CJY63D6U.js → chunk-56VU7A4I.js} +180 -18
  13. package/dist/chunk-56VU7A4I.js.map +1 -0
  14. package/dist/{chunk-3BWYWS3A.js → chunk-5X4MS55M.js} +1655 -695
  15. package/dist/chunk-5X4MS55M.js.map +1 -0
  16. package/dist/{chunk-KFIQTY4O.js → chunk-6ROMPIIP.js} +11 -11
  17. package/dist/{chunk-KFIQTY4O.js.map → chunk-6ROMPIIP.js.map} +1 -1
  18. package/dist/{chunk-RIG2BEXJ.mjs → chunk-GZNHBAZF.mjs} +163 -1
  19. package/dist/chunk-GZNHBAZF.mjs.map +1 -0
  20. package/dist/{chunk-WWP32QYC.mjs → chunk-NPF7P7MU.mjs} +2562 -1602
  21. package/dist/chunk-NPF7P7MU.mjs.map +1 -0
  22. package/dist/{chunk-ZYAAJMZZ.mjs → chunk-WJYWWOTG.mjs} +2 -2
  23. package/dist/client/index.d.mts +5 -6
  24. package/dist/client/index.d.ts +5 -6
  25. package/dist/client/index.js +4 -4
  26. package/dist/client/index.mjs +3 -3
  27. package/dist/components/index.d.mts +76 -8
  28. package/dist/components/index.d.ts +76 -8
  29. package/dist/components/index.js +26 -4
  30. package/dist/components/index.js.map +1 -1
  31. package/dist/components/index.mjs +25 -3
  32. package/dist/{config-D-mqttuF.d.mts → config-Bmr_0qTn.d.mts} +1 -1
  33. package/dist/{config-CyCAWW-d.d.ts → config-n0lfSf27.d.ts} +1 -1
  34. package/dist/contexts/index.d.mts +16 -4
  35. package/dist/contexts/index.d.ts +16 -4
  36. package/dist/contexts/index.js +8 -4
  37. package/dist/contexts/index.js.map +1 -1
  38. package/dist/contexts/index.mjs +7 -3
  39. package/dist/core/index.d.mts +61 -11
  40. package/dist/core/index.d.ts +61 -11
  41. package/dist/core/index.js +10 -2
  42. package/dist/core/index.js.map +1 -1
  43. package/dist/core/index.mjs +9 -1
  44. package/dist/index.d.mts +9 -10
  45. package/dist/index.d.ts +9 -10
  46. package/dist/index.js +11 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/index.mjs +10 -2
  49. package/dist/{notification.interface-ItBxq2au.d.ts → notification.interface-DYDZENx2.d.ts} +18 -1
  50. package/dist/{notification.interface-C6UcmJqu.d.mts → notification.interface-DrHu_1MM.d.mts} +18 -1
  51. package/dist/{s3.service-Cg5TmbU_.d.ts → s3.service-DK2KKXbR.d.ts} +6 -3
  52. package/dist/{s3.service-DLf_a0xS.d.mts → s3.service-TsN2unZr.d.mts} +6 -3
  53. package/dist/server/index.d.mts +3 -4
  54. package/dist/server/index.d.ts +3 -4
  55. package/dist/server/index.js +3 -3
  56. package/dist/server/index.mjs +1 -1
  57. package/dist/{useRbacState-CUj0hp8t.d.ts → useRbacState-BYaSdA78.d.ts} +1 -1
  58. package/dist/{useRbacState-Btk1gkQg.d.mts → useRbacState-CQEJ_ysV.d.mts} +1 -1
  59. package/dist/{useSocket-BSUN9s3p.d.ts → useSocket-Cjt_qvkI.d.ts} +1 -1
  60. package/dist/{useSocket-DKI92Fbg.d.mts → useSocket-VAGetcT3.d.mts} +1 -1
  61. package/package.json +1 -1
  62. package/src/components/index.ts +1 -0
  63. package/src/contexts/index.ts +1 -0
  64. package/src/core/index.ts +2 -0
  65. package/src/core/registry/ModuleRegistry.ts +19 -0
  66. package/src/features/how-to/HowToModule.ts +18 -0
  67. package/src/features/how-to/components/containers/HowToCommand.tsx +230 -0
  68. package/src/features/how-to/components/containers/HowToCommandViewer.tsx +76 -0
  69. package/src/features/how-to/components/containers/HowToContainer.tsx +27 -0
  70. package/src/features/how-to/components/containers/HowToListContainer.tsx +17 -0
  71. package/src/features/how-to/components/details/HowToContent.tsx +16 -0
  72. package/src/features/how-to/components/details/HowToDetails.tsx +52 -0
  73. package/src/features/how-to/components/forms/HowToDeleter.tsx +31 -0
  74. package/src/features/how-to/components/forms/HowToEditor.tsx +270 -0
  75. package/src/features/how-to/components/forms/HowToMultiSelector.tsx +152 -0
  76. package/src/features/how-to/components/forms/HowToSelector.tsx +164 -0
  77. package/src/features/how-to/components/index.ts +11 -0
  78. package/src/features/how-to/components/lists/HowToList.tsx +39 -0
  79. package/src/features/how-to/contexts/HowToContext.tsx +101 -0
  80. package/src/features/how-to/data/HowTo.ts +69 -0
  81. package/src/features/how-to/data/HowToFields.ts +10 -0
  82. package/src/features/how-to/data/HowToInterface.ts +11 -0
  83. package/src/features/how-to/data/HowToService.ts +61 -0
  84. package/src/features/how-to/data/index.ts +4 -0
  85. package/src/features/how-to/hooks/useHowToTableStructure.tsx +86 -0
  86. package/src/features/how-to/index.ts +2 -0
  87. package/src/features/how-to/utils/blocknote.ts +108 -0
  88. package/src/features/how-to/utils/index.ts +1 -0
  89. package/src/features/user/components/details/UserContent.tsx +1 -1
  90. package/src/features/user/data/user.service.ts +9 -0
  91. package/dist/breadcrumb.item.data.interface-CgB4_1EE.d.mts +0 -6
  92. package/dist/breadcrumb.item.data.interface-CgB4_1EE.d.ts +0 -6
  93. package/dist/chunk-3BWYWS3A.js.map +0 -1
  94. package/dist/chunk-CJY63D6U.js.map +0 -1
  95. package/dist/chunk-RIG2BEXJ.mjs.map +0 -1
  96. package/dist/chunk-WWP32QYC.mjs.map +0 -1
  97. package/dist/content.interface-8T5-G84c.d.mts +0 -21
  98. package/dist/content.interface-D-xdYxjt.d.ts +0 -21
  99. /package/dist/{BlockNoteEditor-TIX3GDVZ.mjs.map → BlockNoteEditor-ON5Q6QWO.mjs.map} +0 -0
  100. /package/dist/{chunk-ZYAAJMZZ.mjs.map → chunk-WJYWWOTG.mjs.map} +0 -0
@@ -0,0 +1,230 @@
1
+ "use client";
2
+
3
+ import { ArrowRight, LifeBuoyIcon } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+
7
+ import {
8
+ Command,
9
+ CommandEmpty,
10
+ CommandGroup,
11
+ CommandInput,
12
+ CommandItem,
13
+ CommandList,
14
+ Dialog,
15
+ DialogContent,
16
+ DialogDescription,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ SidebarMenuButton,
20
+ } from "../../../../shadcnui";
21
+ import { Modules } from "../../../../core";
22
+ import { DataListRetriever, useDataListRetriever, useDebounce } from "../../../../hooks";
23
+ import { HowTo } from "../../data/HowTo";
24
+ import { HowToInterface } from "../../data/HowToInterface";
25
+ import { HowToService } from "../../data/HowToService";
26
+ import HowToCommandViewer from "./HowToCommandViewer";
27
+
28
+ function matchPage(pathname: string, pattern: string): boolean {
29
+ if (pattern.includes(":")) {
30
+ const pathSegments = pathname.split("/").filter(Boolean);
31
+ const patternSegments = pattern.split("/").filter(Boolean);
32
+ if (pathSegments.length !== patternSegments.length) return false;
33
+ return patternSegments.every((seg, i) => (seg.startsWith(":") ? true : seg === pathSegments[i]));
34
+ }
35
+ return pathname.toLowerCase().includes(pattern.toLowerCase());
36
+ }
37
+
38
+ type HowToCommandProps = {
39
+ /** Current pathname for page relevance matching */
40
+ pathname: string;
41
+ /** Optional extra command groups to render when not searching */
42
+ extraGroups?: ReactNode;
43
+ /** Called when user starts a chat from the viewer */
44
+ onStartChat?: () => void;
45
+ };
46
+
47
+ export default function HowToCommand({ pathname, extraGroups, onStartChat }: HowToCommandProps) {
48
+ const t = useTranslations();
49
+
50
+ const [dialogOpen, setDialogOpen] = useState<boolean>(false);
51
+ const [selectedHowTo, setSelectedHowTo] = useState<HowToInterface | null>(null);
52
+
53
+ const searchTermRef = useRef<string>("");
54
+ const [searchTerm, setSearchTerm] = useState<string>("");
55
+
56
+ const data: DataListRetriever<HowToInterface> = useDataListRetriever({
57
+ retriever: (params) => {
58
+ return HowToService.findMany(params);
59
+ },
60
+ retrieverParams: {},
61
+ module: Modules.HowTo,
62
+ });
63
+
64
+ // Split HowTos into relevant (matching current page) and others
65
+ const { relevantHowTos, otherHowTos } = useMemo(() => {
66
+ if (!data.data) return { relevantHowTos: [], otherHowTos: [] };
67
+
68
+ const relevant: HowToInterface[] = [];
69
+ const other: HowToInterface[] = [];
70
+
71
+ (data.data as HowToInterface[]).forEach((howTo) => {
72
+ const pages = HowTo.parsePagesFromString(howTo.pages);
73
+ const isRelevant = pages.some((page) => page && matchPage(pathname, page));
74
+ if (isRelevant) {
75
+ relevant.push(howTo);
76
+ } else {
77
+ other.push(howTo);
78
+ }
79
+ });
80
+
81
+ return { relevantHowTos: relevant, otherHowTos: other };
82
+ }, [data.data, pathname]);
83
+
84
+ const search = useCallback(
85
+ async (searchedTerm: string) => {
86
+ if (searchedTerm === searchTermRef.current) return;
87
+ searchTermRef.current = searchedTerm;
88
+ await data.search(searchedTerm);
89
+ },
90
+ [searchTermRef, data],
91
+ );
92
+
93
+ const updateSearchTerm = useDebounce(search, 500);
94
+
95
+ useEffect(() => {
96
+ updateSearchTerm(searchTerm);
97
+ }, [updateSearchTerm, searchTerm]);
98
+
99
+ // Reset search when dialog closes
100
+ useEffect(() => {
101
+ if (!dialogOpen) {
102
+ setSearchTerm("");
103
+ searchTermRef.current = "";
104
+ }
105
+ }, [dialogOpen]);
106
+
107
+ // Keyboard shortcut: Cmd+K or Ctrl+K to toggle dialog
108
+ useEffect(() => {
109
+ const down = (e: KeyboardEvent) => {
110
+ if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
111
+ e.preventDefault();
112
+ setDialogOpen((open) => !open);
113
+ }
114
+ };
115
+
116
+ document.addEventListener("keydown", down);
117
+ return () => document.removeEventListener("keydown", down);
118
+ }, []);
119
+
120
+ const handleStartChat = () => {
121
+ setDialogOpen(false);
122
+ setSelectedHowTo(null);
123
+ if (onStartChat) onStartChat();
124
+ };
125
+
126
+ return (
127
+ <>
128
+ <SidebarMenuButton
129
+ tooltip={t(`howto.command.trigger`)}
130
+ onClick={() => setDialogOpen(true)}
131
+ className="text-muted-foreground"
132
+ >
133
+ <LifeBuoyIcon />
134
+ <span>{t(`howto.command.trigger`)}</span>
135
+ </SidebarMenuButton>
136
+
137
+ {/* Search HowTos Dialog */}
138
+ <Dialog
139
+ open={dialogOpen}
140
+ onOpenChange={(open) => {
141
+ setDialogOpen(open);
142
+ if (!open) setSelectedHowTo(null);
143
+ }}
144
+ modal={true}
145
+ >
146
+ <DialogContent
147
+ className={`flex flex-col gap-0 overflow-hidden p-0 ${selectedHowTo ? "h-[80vh] max-w-3xl" : "max-w-lg"}`}
148
+ >
149
+ <DialogHeader className="sr-only">
150
+ <DialogTitle>{selectedHowTo ? selectedHowTo.name : t("howto.command.title")}</DialogTitle>
151
+ <DialogDescription> </DialogDescription>
152
+ </DialogHeader>
153
+
154
+ {selectedHowTo ? (
155
+ <HowToCommandViewer
156
+ howTo={selectedHowTo}
157
+ onBack={() => setSelectedHowTo(null)}
158
+ onStartChat={handleStartChat}
159
+ />
160
+ ) : (
161
+ <Command
162
+ shouldFilter={false}
163
+ className="[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"
164
+ >
165
+ <CommandInput
166
+ onKeyDown={(e) => {
167
+ if (e.key === "Escape") {
168
+ setSearchTerm("");
169
+ searchTermRef.current = "";
170
+ }
171
+ }}
172
+ onValueChange={setSearchTerm}
173
+ value={searchTerm}
174
+ placeholder={t(`howto.search.placeholder`)}
175
+ />
176
+ <CommandList className="max-h-[60vh] overflow-y-auto">
177
+ <CommandEmpty>{t(`howto.command.empty`)}</CommandEmpty>
178
+
179
+ {/* App-specific extra groups (support, tours, etc.) */}
180
+ {!searchTerm && extraGroups}
181
+
182
+ {/* Relevant to this page */}
183
+ {relevantHowTos.length > 0 && (
184
+ <CommandGroup heading={t(`howto.command.relevant`)}>
185
+ {relevantHowTos.map((howTo: HowToInterface) => (
186
+ <CommandItem key={howTo.id} onSelect={() => setSelectedHowTo(howTo)} className="cursor-pointer">
187
+ <ArrowRight className="h-4 w-4" />
188
+ <span>{howTo.name}</span>
189
+ </CommandItem>
190
+ ))}
191
+ </CommandGroup>
192
+ )}
193
+
194
+ {/* All HowTos / Search Results */}
195
+ {otherHowTos.length > 0 && (
196
+ <CommandGroup heading={searchTerm ? undefined : t(`howto.command.all`)}>
197
+ {otherHowTos.map((howTo: HowToInterface) => (
198
+ <CommandItem key={howTo.id} onSelect={() => setSelectedHowTo(howTo)} className="cursor-pointer">
199
+ <ArrowRight className="h-4 w-4" />
200
+ <span>{howTo.name}</span>
201
+ </CommandItem>
202
+ ))}
203
+ </CommandGroup>
204
+ )}
205
+ </CommandList>
206
+
207
+ {/* Keyboard hints footer */}
208
+ <div className="text-muted-foreground flex items-center justify-center gap-4 border-t px-3 py-2 text-xs">
209
+ <span className="flex items-center gap-1">
210
+ <kbd className="bg-muted rounded border px-1.5 py-0.5 font-mono text-[10px]">&#9166;</kbd>
211
+ {t(`howto.command.keyboard.select`)}
212
+ </span>
213
+ <span className="flex items-center gap-1">
214
+ <kbd className="bg-muted rounded border px-1.5 py-0.5 font-mono text-[10px]">&#8593;&#8595;</kbd>
215
+ {t(`howto.command.keyboard.navigate`)}
216
+ </span>
217
+ <span className="flex items-center gap-1">
218
+ <kbd className="bg-muted rounded border px-1.5 py-0.5 font-mono text-[10px]">
219
+ {t(`howto.keyboard.esc`)}
220
+ </kbd>
221
+ {t(`howto.command.keyboard.close`)}
222
+ </span>
223
+ </div>
224
+ </Command>
225
+ )}
226
+ </DialogContent>
227
+ </Dialog>
228
+ </>
229
+ );
230
+ }
@@ -0,0 +1,76 @@
1
+ "use client";
2
+
3
+ import { ArrowLeft, BookOpen, MessageSquare } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import { useMemo } from "react";
6
+
7
+ import { BlockNoteEditorContainer } from "../../../../components";
8
+ import { Button } from "../../../../shadcnui";
9
+ import { HowToInterface } from "../../data/HowToInterface";
10
+ import { calculateReadingTime, extractHeadings } from "../../utils/blocknote";
11
+
12
+ type HowToCommandViewerProps = {
13
+ howTo: HowToInterface;
14
+ onBack: () => void;
15
+ onStartChat?: () => void;
16
+ };
17
+
18
+ export default function HowToCommandViewer({ howTo, onBack, onStartChat }: HowToCommandViewerProps) {
19
+ const t = useTranslations();
20
+
21
+ const readingTime = useMemo(() => calculateReadingTime(howTo.description), [howTo.description]);
22
+ const headings = useMemo(() => extractHeadings(howTo.description), [howTo.description]);
23
+
24
+ return (
25
+ <div className="flex h-full flex-col">
26
+ {/* Header with back button, title, and reading time */}
27
+ <div className="flex items-center gap-3 border-b px-4 py-3">
28
+ <Button variant="ghost" size="sm" onClick={onBack} className="h-8 px-2">
29
+ <ArrowLeft className="h-4 w-4" />
30
+ <span className="ml-1">{t("howto.command.back")}</span>
31
+ </Button>
32
+ <h2 className="flex-1 truncate text-lg font-semibold">{howTo.name}</h2>
33
+ <div className="text-muted-foreground flex items-center gap-1.5 text-sm">
34
+ <BookOpen className="h-4 w-4" />
35
+ <span>{t("howto.reading_time.label", { minutes: readingTime })}</span>
36
+ </div>
37
+ </div>
38
+
39
+ {/* Two-column body */}
40
+ <div className="flex min-h-0 flex-1">
41
+ {/* Left sidebar - table of contents */}
42
+ {headings.length > 0 && (
43
+ <div className="w-50 shrink-0 overflow-y-auto border-r p-4">
44
+ <nav className="space-y-1">
45
+ {headings.map((heading) => (
46
+ <a
47
+ key={heading.id}
48
+ href={`#${heading.id}`}
49
+ className="text-muted-foreground hover:text-foreground block truncate text-sm"
50
+ style={{ paddingLeft: `${(heading.level - 1) * 0.75}rem` }}
51
+ >
52
+ {heading.text}
53
+ </a>
54
+ ))}
55
+ </nav>
56
+ </div>
57
+ )}
58
+
59
+ {/* Right content - scrollable */}
60
+ <div id="howto-viewer-content" className="min-w-0 flex-1 overflow-y-auto p-4">
61
+ <BlockNoteEditorContainer id={howTo.id} type="howto" initialContent={howTo.description} />
62
+ </div>
63
+ </div>
64
+
65
+ {/* Full-width footer */}
66
+ {onStartChat && (
67
+ <div className="flex items-center justify-end gap-2 border-t px-4 py-3">
68
+ <Button onClick={onStartChat} variant="default" size="sm">
69
+ <MessageSquare className="mr-2 h-4 w-4" />
70
+ {t("howto.command.chat_button")}
71
+ </Button>
72
+ </div>
73
+ )}
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { RoundPageContainer } from "../../../../components";
4
+ import { Modules } from "../../../../core";
5
+ import { useHowToContext } from "../../contexts/HowToContext";
6
+ import { HowToInterface } from "../../data/HowToInterface";
7
+ import HowToContent from "../details/HowToContent";
8
+ import HowToDetails from "../details/HowToDetails";
9
+
10
+ type HowToContainerProps = {
11
+ howTo: HowToInterface;
12
+ };
13
+
14
+ function HowToContainerInternal({ howTo }: HowToContainerProps) {
15
+ return (
16
+ <RoundPageContainer module={Modules.HowTo} details={<HowToDetails />}>
17
+ <HowToContent />
18
+ </RoundPageContainer>
19
+ );
20
+ }
21
+
22
+ export default function HowToContainer() {
23
+ const { howTo } = useHowToContext();
24
+ if (!howTo) return null;
25
+
26
+ return <HowToContainerInternal howTo={howTo} />;
27
+ }
@@ -0,0 +1,17 @@
1
+ "use client";
2
+
3
+ import { RoundPageContainer } from "../../../../components";
4
+ import { Modules } from "../../../../core";
5
+ import HowToList from "../lists/HowToList";
6
+
7
+ function HowToListContainerInternal() {
8
+ return (
9
+ <RoundPageContainer module={Modules.HowTo} fullWidth>
10
+ <HowToList fullWidth />
11
+ </RoundPageContainer>
12
+ );
13
+ }
14
+
15
+ export default function HowToListContainer() {
16
+ return <HowToListContainerInternal />;
17
+ }
@@ -0,0 +1,16 @@
1
+ "use client";
2
+
3
+ import { BlockNoteEditorContainer } from "../../../../components";
4
+ import { Card } from "../../../../shadcnui";
5
+ import { useHowToContext } from "../../contexts/HowToContext";
6
+
7
+ export default function HowToContent() {
8
+ const { howTo } = useHowToContext();
9
+ if (!howTo) return null;
10
+
11
+ return (
12
+ <Card className="flex w-full flex-col p-4">
13
+ <BlockNoteEditorContainer id={howTo.id} type="howto" initialContent={howTo.description} />
14
+ </Card>
15
+ );
16
+ }
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { AttributeElement, ContentTitle } from "../../../../components";
5
+ import { Modules } from "../../../../core";
6
+ import { useSharedContext } from "../../../../contexts/SharedContext";
7
+ import { Link } from "../../../../shadcnui";
8
+ import { HowTo } from "../../data/HowTo";
9
+ import { HowToInterface } from "../../data/HowToInterface";
10
+ import { useHowToContext } from "../../contexts/HowToContext";
11
+
12
+ type HowToDetailsProps = {
13
+ howTo: HowToInterface;
14
+ };
15
+
16
+ function HowToDetailsInternal({ howTo }: HowToDetailsProps) {
17
+ const t = useTranslations();
18
+ const { title } = useSharedContext();
19
+ const pagesList = HowTo.parsePagesFromString(howTo.pages);
20
+
21
+ return (
22
+ <div className="flex w-full flex-col gap-y-4">
23
+ <ContentTitle type={title.type} element={title.element} functions={title.functions} module={Modules.HowTo} />
24
+
25
+ {pagesList.length > 0 && (
26
+ <div className="border-t pt-4">
27
+ <AttributeElement
28
+ title={t(`howto.fields.pages.label`)}
29
+ value={
30
+ <ul className="flex flex-col gap-y-1">
31
+ {pagesList.map((page, index) => (
32
+ <li key={index}>
33
+ <Link href={page} className="text-primary hover:underline">
34
+ {page}
35
+ </Link>
36
+ </li>
37
+ ))}
38
+ </ul>
39
+ }
40
+ />
41
+ </div>
42
+ )}
43
+ </div>
44
+ );
45
+ }
46
+
47
+ export default function HowToDetails() {
48
+ const { howTo } = useHowToContext();
49
+ if (!howTo) return null;
50
+
51
+ return <HowToDetailsInternal howTo={howTo} />;
52
+ }
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { CommonDeleter } from "../../../../components";
5
+ import { Modules } from "../../../../core";
6
+ import { usePageUrlGenerator } from "../../../../hooks";
7
+ import { HowToInterface } from "../../data/HowToInterface";
8
+ import { HowToService } from "../../data/HowToService";
9
+
10
+ type HowToDeleterProps = {
11
+ howTo: HowToInterface;
12
+ };
13
+
14
+ function HowToDeleterInternal({ howTo }: HowToDeleterProps) {
15
+ const t = useTranslations();
16
+ const generateUrl = usePageUrlGenerator();
17
+
18
+ if (!howTo) return null;
19
+
20
+ return (
21
+ <CommonDeleter
22
+ type={`howtos`}
23
+ deleteFunction={() => HowToService.delete({ howToId: howTo.id })}
24
+ redirectTo={generateUrl({ page: Modules.HowTo })}
25
+ />
26
+ );
27
+ }
28
+
29
+ export default function HowToDeleter(props: HowToDeleterProps) {
30
+ return <HowToDeleterInternal {...props} />;
31
+ }