@carlonicora/nextjs-jsonapi 1.131.2 → 1.133.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/dist/{BlockNoteEditor-5RUXGWLR.js → BlockNoteEditor-H626BS5Y.js} +19 -19
- package/dist/{BlockNoteEditor-5RUXGWLR.js.map → BlockNoteEditor-H626BS5Y.js.map} +1 -1
- package/dist/{BlockNoteEditor-F4AKH532.mjs → BlockNoteEditor-VXK7INMR.mjs} +4 -4
- package/dist/billing/index.js +357 -357
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-LBMNRFCY.js → chunk-5IE6DZ2D.js} +153 -77
- package/dist/chunk-5IE6DZ2D.js.map +1 -0
- package/dist/{chunk-5MNE72ZE.mjs → chunk-DZL2G7NB.mjs} +77 -1
- package/dist/chunk-DZL2G7NB.mjs.map +1 -0
- package/dist/{chunk-XTLTQQ7X.js → chunk-F6KBHC2X.js} +7 -7
- package/dist/{chunk-XTLTQQ7X.js.map → chunk-F6KBHC2X.js.map} +1 -1
- package/dist/{chunk-T7F2EVPR.js → chunk-TVJFCWST.js} +908 -929
- package/dist/chunk-TVJFCWST.js.map +1 -0
- package/dist/{chunk-NATPK5ME.mjs → chunk-VSAAVVHY.mjs} +2 -2
- package/dist/{chunk-BBBQGJEG.mjs → chunk-WEDLGKSS.mjs} +104 -125
- package/dist/chunk-WEDLGKSS.mjs.map +1 -0
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +6 -2
- package/dist/components/index.d.ts +6 -2
- package/dist/components/index.js +4 -4
- package/dist/components/index.mjs +3 -3
- package/dist/contexts/index.d.mts +9 -2
- package/dist/contexts/index.d.ts +9 -2
- package/dist/contexts/index.js +6 -4
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +5 -3
- package/dist/core/index.d.mts +11 -1
- package/dist/core/index.d.ts +11 -1
- package/dist/core/index.js +12 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +13 -3
- package/dist/features/help/index.js +39 -39
- package/dist/features/help/index.js.map +1 -1
- package/dist/features/help/index.mjs +5 -5
- package/dist/features/help/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -4
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/containers/ReactMarkdownContainer.tsx +1 -1
- package/src/components/containers/RoundPageContainer.tsx +7 -0
- package/src/components/forms/EditorSheet.tsx +24 -1
- package/src/components/forms/FormDateTime.tsx +5 -0
- package/src/components/navigations/Breadcrumb.tsx +13 -11
- package/src/components/navigations/Header.tsx +3 -1
- package/src/components/tables/ContentListTable.tsx +33 -19
- package/src/components/tables/__tests__/ContentListTable.test.tsx +77 -0
- package/src/contexts/HeaderChildrenContext.tsx +17 -2
- package/src/features/assistant-message/components/MessageItem.tsx +1 -1
- package/src/features/auth/components/GdprConsentSection.tsx +2 -2
- package/src/features/auth/components/forms/Register.tsx +2 -2
- package/src/features/help/components/HelpArticleBody.tsx +2 -2
- package/src/features/notification/components/common/NotificationErrorBoundary.tsx +1 -4
- package/src/features/oauth/components/consent/OAuthConsentScreen.tsx +2 -2
- package/src/features/waitlist/components/forms/WaitlistForm.tsx +2 -2
- package/src/features/waitlist/components/lists/WaitlistList.tsx +13 -2
- package/src/shadcnui/custom/link.tsx +1 -1
- package/src/shadcnui/ui/accordion.tsx +1 -1
- package/src/shadcnui/ui/alert-dialog.tsx +1 -1
- package/src/shadcnui/ui/alert.tsx +2 -5
- package/src/shadcnui/ui/badge.tsx +1 -1
- package/src/shadcnui/ui/button.tsx +1 -1
- package/src/shadcnui/ui/dialog.tsx +1 -4
- package/src/shadcnui/ui/field.tsx +1 -1
- package/src/utils/__tests__/italian-validators.test.ts +63 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/italian-validators.ts +18 -5
- package/dist/chunk-5MNE72ZE.mjs.map +0 -1
- package/dist/chunk-BBBQGJEG.mjs.map +0 -1
- package/dist/chunk-LBMNRFCY.js.map +0 -1
- package/dist/chunk-T7F2EVPR.js.map +0 -1
- /package/dist/{BlockNoteEditor-F4AKH532.mjs.map → BlockNoteEditor-VXK7INMR.mjs.map} +0 -0
- /package/dist/{chunk-NATPK5ME.mjs.map → chunk-VSAAVVHY.mjs.map} +0 -0
|
@@ -19,18 +19,18 @@ import {
|
|
|
19
19
|
Breadcrumb as UIBreadcrumb,
|
|
20
20
|
} from "../../shadcnui";
|
|
21
21
|
|
|
22
|
-
type BreadcrumbProps = { items: BreadcrumbItemData[] };
|
|
22
|
+
type BreadcrumbProps = { items: BreadcrumbItemData[]; rootLabel?: string };
|
|
23
23
|
|
|
24
24
|
const ITEMS_TO_DISPLAY = 4;
|
|
25
25
|
|
|
26
26
|
function BreadcrumbDesktop({
|
|
27
27
|
items,
|
|
28
28
|
generateUrl,
|
|
29
|
-
|
|
29
|
+
rootLabel,
|
|
30
30
|
}: {
|
|
31
31
|
items: BreadcrumbItemData[];
|
|
32
32
|
generateUrl: ReturnType<typeof usePageUrlGenerator>;
|
|
33
|
-
|
|
33
|
+
rootLabel: string;
|
|
34
34
|
}) {
|
|
35
35
|
const [open, setOpen] = useState<boolean>(false);
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ function BreadcrumbDesktop({
|
|
|
38
38
|
<UIBreadcrumb>
|
|
39
39
|
<BreadcrumbList>
|
|
40
40
|
<BreadcrumbItem>
|
|
41
|
-
<Link href={generateUrl({ page: `/` })}>{
|
|
41
|
+
<Link href={generateUrl({ page: `/` })}>{rootLabel}</Link>
|
|
42
42
|
</BreadcrumbItem>
|
|
43
43
|
{items.length > 0 && <BreadcrumbSeparator />}
|
|
44
44
|
|
|
@@ -112,23 +112,23 @@ function BreadcrumbDesktop({
|
|
|
112
112
|
function BreadcrumbMobile({
|
|
113
113
|
items,
|
|
114
114
|
generateUrl,
|
|
115
|
-
|
|
115
|
+
rootLabel,
|
|
116
116
|
}: {
|
|
117
117
|
items: BreadcrumbItemData[];
|
|
118
118
|
generateUrl: ReturnType<typeof usePageUrlGenerator>;
|
|
119
|
-
|
|
119
|
+
rootLabel: string;
|
|
120
120
|
}) {
|
|
121
121
|
const [open, setOpen] = useState<boolean>(false);
|
|
122
122
|
|
|
123
123
|
const lastItem = items[items.length - 1];
|
|
124
|
-
const allItems = [{ name:
|
|
124
|
+
const allItems = [{ name: rootLabel, href: generateUrl({ page: `/` }) }, ...items];
|
|
125
125
|
|
|
126
126
|
if (!lastItem && items.length === 0) {
|
|
127
127
|
return (
|
|
128
128
|
<UIBreadcrumb>
|
|
129
129
|
<BreadcrumbList>
|
|
130
130
|
<BreadcrumbItem>
|
|
131
|
-
<Link href={generateUrl({ page: `/` })}>{
|
|
131
|
+
<Link href={generateUrl({ page: `/` })}>{rootLabel}</Link>
|
|
132
132
|
</BreadcrumbItem>
|
|
133
133
|
</BreadcrumbList>
|
|
134
134
|
</UIBreadcrumb>
|
|
@@ -158,14 +158,16 @@ function BreadcrumbMobile({
|
|
|
158
158
|
);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
export function BreadcrumbNavigation({ items }: BreadcrumbProps) {
|
|
161
|
+
export function BreadcrumbNavigation({ items, rootLabel }: BreadcrumbProps) {
|
|
162
162
|
const generateUrl = usePageUrlGenerator();
|
|
163
163
|
const t = useTranslations();
|
|
164
164
|
const isMobile = useIsMobile();
|
|
165
165
|
|
|
166
|
+
const root = rootLabel?.trim() ? rootLabel : t(`common.home`);
|
|
167
|
+
|
|
166
168
|
if (isMobile) {
|
|
167
|
-
return <BreadcrumbMobile items={items} generateUrl={generateUrl}
|
|
169
|
+
return <BreadcrumbMobile items={items} generateUrl={generateUrl} rootLabel={root} />;
|
|
168
170
|
}
|
|
169
171
|
|
|
170
|
-
return <BreadcrumbDesktop items={items} generateUrl={generateUrl}
|
|
172
|
+
return <BreadcrumbDesktop items={items} generateUrl={generateUrl} rootLabel={root} />;
|
|
171
173
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useIsMobile } from "@/utils";
|
|
4
|
+
import { useHeaderRootLabel } from "../../contexts/HeaderChildrenContext";
|
|
4
5
|
import { useSharedContext } from "../../contexts/SharedContext";
|
|
5
6
|
import { SidebarTrigger } from "../../shadcnui";
|
|
6
7
|
import { BreadcrumbNavigation } from "./Breadcrumb";
|
|
@@ -13,6 +14,7 @@ type HeaderProps = {
|
|
|
13
14
|
|
|
14
15
|
export function Header({ children, leftContent, className }: HeaderProps) {
|
|
15
16
|
const { breadcrumbs } = useSharedContext();
|
|
17
|
+
const rootLabel = useHeaderRootLabel();
|
|
16
18
|
const isMobile = useIsMobile();
|
|
17
19
|
|
|
18
20
|
return (
|
|
@@ -21,7 +23,7 @@ export function Header({ children, leftContent, className }: HeaderProps) {
|
|
|
21
23
|
<SidebarTrigger aria-label="Toggle sidebar" id="sidebar-trigger" />
|
|
22
24
|
{leftContent}
|
|
23
25
|
<div className="flex w-full flex-row items-center justify-start">
|
|
24
|
-
<BreadcrumbNavigation items={breadcrumbs} />
|
|
26
|
+
<BreadcrumbNavigation items={breadcrumbs} rootLabel={rootLabel ?? undefined} />
|
|
25
27
|
</div>
|
|
26
28
|
{!isMobile && children && (
|
|
27
29
|
<div className="flex w-64 flex-row items-center justify-end gap-x-4 whitespace-nowrap">
|
|
@@ -10,6 +10,7 @@ import React, { ReactNode, memo, useMemo, useState } from "react";
|
|
|
10
10
|
import { DataListRetriever, TableContent, useTableGenerator } from "../../hooks";
|
|
11
11
|
import { ModuleWithPermissions } from "../../permissions";
|
|
12
12
|
import { Button, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "../../shadcnui";
|
|
13
|
+
import { MicroLabel } from "../typography";
|
|
13
14
|
import { ContentTableSearch } from "./ContentTableSearch";
|
|
14
15
|
|
|
15
16
|
const EMPTY_ARRAY: any[] = [];
|
|
@@ -49,6 +50,9 @@ type ContentListTableProps = {
|
|
|
49
50
|
defaultExpanded?: boolean | ExpandedState;
|
|
50
51
|
fullWidth?: boolean;
|
|
51
52
|
groupBy?: string;
|
|
53
|
+
groupLabel?: (key: string) => ReactNode;
|
|
54
|
+
groupOrder?: string[];
|
|
55
|
+
hideHeader?: boolean;
|
|
52
56
|
emptyState?: ReactNode;
|
|
53
57
|
onRowClick?: (rowData: any) => void;
|
|
54
58
|
};
|
|
@@ -118,12 +122,24 @@ export const ContentListTable = memo(function ContentListTable(props: ContentLis
|
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
const
|
|
125
|
+
const order = props.groupOrder;
|
|
126
|
+
const sortedKeys = [...groupMap.keys()].sort((a, b) => {
|
|
127
|
+
if (order) {
|
|
128
|
+
const ia = order.indexOf(a);
|
|
129
|
+
const ib = order.indexOf(b);
|
|
130
|
+
// keys absent from groupOrder sort last, alphabetically among themselves
|
|
131
|
+
if (ia === -1 && ib === -1) return a.localeCompare(b);
|
|
132
|
+
if (ia === -1) return 1;
|
|
133
|
+
if (ib === -1) return -1;
|
|
134
|
+
return ia - ib;
|
|
135
|
+
}
|
|
136
|
+
return a.localeCompare(b);
|
|
137
|
+
});
|
|
122
138
|
return sortedKeys.map((groupKey) => ({
|
|
123
139
|
groupKey,
|
|
124
140
|
rows: groupMap.get(groupKey)!,
|
|
125
141
|
}));
|
|
126
|
-
}, [props.groupBy, rowModel]);
|
|
142
|
+
}, [props.groupBy, props.groupOrder, rowModel]);
|
|
127
143
|
|
|
128
144
|
const showFooter = !!(data.next || data.previous);
|
|
129
145
|
|
|
@@ -168,18 +184,19 @@ export const ContentListTable = memo(function ContentListTable(props: ContentLis
|
|
|
168
184
|
</TableHead>
|
|
169
185
|
</TableRow>
|
|
170
186
|
)}
|
|
171
|
-
{
|
|
172
|
-
|
|
173
|
-
{headerGroup.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
{header.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
187
|
+
{!props.hideHeader &&
|
|
188
|
+
table.getHeaderGroups().map((headerGroup) => (
|
|
189
|
+
<TableRow key={headerGroup.id}>
|
|
190
|
+
{headerGroup.headers.map((header) => {
|
|
191
|
+
const meta = header.column.columnDef.meta as { className?: string } | undefined;
|
|
192
|
+
return (
|
|
193
|
+
<TableHead key={header.id} className={meta?.className}>
|
|
194
|
+
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
|
195
|
+
</TableHead>
|
|
196
|
+
);
|
|
197
|
+
})}
|
|
198
|
+
</TableRow>
|
|
199
|
+
))}
|
|
183
200
|
</TableHeader>
|
|
184
201
|
<TableBody>
|
|
185
202
|
{rowModel && rowModel.rows?.length ? (
|
|
@@ -187,11 +204,8 @@ export const ContentListTable = memo(function ContentListTable(props: ContentLis
|
|
|
187
204
|
groupedRows.map((group) => (
|
|
188
205
|
<React.Fragment key={group.groupKey}>
|
|
189
206
|
<TableRow>
|
|
190
|
-
<TableCell
|
|
191
|
-
|
|
192
|
-
className="bg-muted text-muted-foreground px-4 py-2 text-sm font-semibold"
|
|
193
|
-
>
|
|
194
|
-
{group.groupKey}
|
|
207
|
+
<TableCell colSpan={tableColumns.length} className="bg-muted px-4 py-2">
|
|
208
|
+
<MicroLabel>{props.groupLabel?.(group.groupKey) ?? group.groupKey}</MicroLabel>
|
|
195
209
|
</TableCell>
|
|
196
210
|
</TableRow>
|
|
197
211
|
{group.rows.map((row) => (
|
|
@@ -338,6 +338,37 @@ describe("ContentListTable", () => {
|
|
|
338
338
|
});
|
|
339
339
|
});
|
|
340
340
|
|
|
341
|
+
describe("hideHeader", () => {
|
|
342
|
+
it("should render the column header row by default", () => {
|
|
343
|
+
const dataRetriever = createMockDataRetriever({
|
|
344
|
+
data: [{ id: "1", title: "Article 1" }],
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
render(<ContentListTable data={dataRetriever} tableGeneratorType={mockModule as any} fields={["id", "title"]} />);
|
|
348
|
+
|
|
349
|
+
expect(screen.getByText("Title")).toBeInTheDocument();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("should not render the column header row when hideHeader is set", () => {
|
|
353
|
+
const dataRetriever = createMockDataRetriever({
|
|
354
|
+
data: [{ id: "1", title: "Article 1" }],
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
render(
|
|
358
|
+
<ContentListTable
|
|
359
|
+
data={dataRetriever}
|
|
360
|
+
tableGeneratorType={mockModule as any}
|
|
361
|
+
fields={["id", "title"]}
|
|
362
|
+
hideHeader
|
|
363
|
+
/>,
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
expect(screen.queryByText("Title")).not.toBeInTheDocument();
|
|
367
|
+
// Body rows must still render — only the header row is suppressed.
|
|
368
|
+
expect(screen.getByText("Article 1")).toBeInTheDocument();
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
341
372
|
describe("grouping", () => {
|
|
342
373
|
it("should not group rows when groupBy is not provided", () => {
|
|
343
374
|
const dataRetriever = createMockDataRetriever({
|
|
@@ -511,6 +542,52 @@ describe("ContentListTable", () => {
|
|
|
511
542
|
const article3Count = allText.filter((t) => t === "Article 3").length;
|
|
512
543
|
expect(article3Count).toBe(2);
|
|
513
544
|
});
|
|
545
|
+
|
|
546
|
+
it("should render group headers through groupLabel when provided", () => {
|
|
547
|
+
const dataRetriever = createMockDataRetriever({
|
|
548
|
+
data: [
|
|
549
|
+
{ id: "1", title: "Article 1", category: "CIVIL" },
|
|
550
|
+
{ id: "2", title: "Article 2", category: "CRIMINAL" },
|
|
551
|
+
],
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
render(
|
|
555
|
+
<ContentListTable
|
|
556
|
+
data={dataRetriever}
|
|
557
|
+
tableGeneratorType={mockModule as any}
|
|
558
|
+
fields={["id", "title"]}
|
|
559
|
+
groupBy="category"
|
|
560
|
+
groupLabel={(key) => `Branch: ${key}`}
|
|
561
|
+
/>,
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
expect(screen.getByText("Branch: CIVIL")).toBeInTheDocument();
|
|
565
|
+
expect(screen.getByText("Branch: CRIMINAL")).toBeInTheDocument();
|
|
566
|
+
expect(screen.queryByText("CIVIL")).not.toBeInTheDocument();
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it("should order groups by groupOrder instead of alphabetically", () => {
|
|
570
|
+
const dataRetriever = createMockDataRetriever({
|
|
571
|
+
data: [
|
|
572
|
+
{ id: "1", title: "A Article", category: "A" },
|
|
573
|
+
{ id: "2", title: "B Article", category: "B" },
|
|
574
|
+
{ id: "3", title: "C Article", category: "C" },
|
|
575
|
+
],
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
render(
|
|
579
|
+
<ContentListTable
|
|
580
|
+
data={dataRetriever}
|
|
581
|
+
tableGeneratorType={mockModule as any}
|
|
582
|
+
fields={["id", "title"]}
|
|
583
|
+
groupBy="category"
|
|
584
|
+
groupOrder={["C", "A", "B"]}
|
|
585
|
+
/>,
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
const headings = screen.getAllByRole("heading", { level: 4 });
|
|
589
|
+
expect(headings.map((h) => h.textContent)).toEqual(["C", "A", "B"]);
|
|
590
|
+
});
|
|
514
591
|
});
|
|
515
592
|
|
|
516
593
|
describe("onRowClick", () => {
|
|
@@ -4,24 +4,30 @@ import { createContext, useContext, ReactNode } from "react";
|
|
|
4
4
|
|
|
5
5
|
interface HeaderChildrenContextType {
|
|
6
6
|
headerChildren: ReactNode | null;
|
|
7
|
+
headerRootLabel: string | null;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
const HeaderChildrenContext = createContext<HeaderChildrenContextType>({
|
|
10
11
|
headerChildren: null,
|
|
12
|
+
headerRootLabel: null,
|
|
11
13
|
});
|
|
12
14
|
|
|
13
15
|
interface HeaderChildrenProviderProps {
|
|
14
16
|
children: ReactNode;
|
|
15
17
|
content: ReactNode;
|
|
18
|
+
rootLabel?: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Provider to supply custom content to be rendered in the Header component.
|
|
20
23
|
* Wrap your layout with this provider and pass the content you want in the header.
|
|
24
|
+
* Optionally pass `rootLabel` to override the first breadcrumb entry (defaults to `common.home`).
|
|
21
25
|
*/
|
|
22
|
-
export function HeaderChildrenProvider({ children, content }: HeaderChildrenProviderProps) {
|
|
26
|
+
export function HeaderChildrenProvider({ children, content, rootLabel }: HeaderChildrenProviderProps) {
|
|
23
27
|
return (
|
|
24
|
-
<HeaderChildrenContext.Provider value={{ headerChildren: content }}>
|
|
28
|
+
<HeaderChildrenContext.Provider value={{ headerChildren: content, headerRootLabel: rootLabel ?? null }}>
|
|
29
|
+
{children}
|
|
30
|
+
</HeaderChildrenContext.Provider>
|
|
25
31
|
);
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -33,3 +39,12 @@ export function useHeaderChildren(): ReactNode | null {
|
|
|
33
39
|
const context = useContext(HeaderChildrenContext);
|
|
34
40
|
return context.headerChildren;
|
|
35
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hook to get the label of the first breadcrumb entry, when the application supplies one.
|
|
45
|
+
* Returns null when no override was provided, in which case the Header falls back to `common.home`.
|
|
46
|
+
*/
|
|
47
|
+
export function useHeaderRootLabel(): string | null {
|
|
48
|
+
const context = useContext(HeaderChildrenContext);
|
|
49
|
+
return context.headerRootLabel;
|
|
50
|
+
}
|
|
@@ -46,7 +46,7 @@ export function MessageItem({
|
|
|
46
46
|
<div className="text-destructive flex items-center gap-2 text-xs">
|
|
47
47
|
<AlertCircle className="h-3.5 w-3.5" />
|
|
48
48
|
<span>{t("features.assistant.send_failed")}</span>
|
|
49
|
-
<button type="button"
|
|
49
|
+
<button type="button" onClick={() => onRetry?.(message.id)}>
|
|
50
50
|
{t("features.assistant.retry")}
|
|
51
51
|
</button>
|
|
52
52
|
</div>
|
|
@@ -21,11 +21,11 @@ export function GdprConsentSection<T extends FieldValues>({
|
|
|
21
21
|
const termsLabel = (
|
|
22
22
|
<>
|
|
23
23
|
{t("terms_prefix")}{" "}
|
|
24
|
-
<Link href="/terms" target="_blank" rel="noopener"
|
|
24
|
+
<Link href="/terms" target="_blank" rel="noopener">
|
|
25
25
|
{t("terms_of_service")}
|
|
26
26
|
</Link>{" "}
|
|
27
27
|
{t("and")}{" "}
|
|
28
|
-
<Link href="/privacy" target="_blank" rel="noopener"
|
|
28
|
+
<Link href="/privacy" target="_blank" rel="noopener">
|
|
29
29
|
{t("privacy_policy")}
|
|
30
30
|
</Link>
|
|
31
31
|
</>
|
|
@@ -194,7 +194,7 @@ export default function Register() {
|
|
|
194
194
|
<CardContent className="text-center">
|
|
195
195
|
<p className="text-destructive mb-4">{inviteError}</p>
|
|
196
196
|
<p className="mb-4">{t("waitlist.invite.join_prompt")}</p>
|
|
197
|
-
<Link href="/waitlist" className="text-primary
|
|
197
|
+
<Link href="/waitlist" className="text-primary">
|
|
198
198
|
{t("waitlist.invite.join_link")}
|
|
199
199
|
</Link>
|
|
200
200
|
</CardContent>
|
|
@@ -215,7 +215,7 @@ export default function Register() {
|
|
|
215
215
|
<CardContent className="text-center">
|
|
216
216
|
<p className="mb-4">{t("waitlist.invite.registration_description")}</p>
|
|
217
217
|
<p className="mb-4">{t("waitlist.invite.registration_hint")}</p>
|
|
218
|
-
<Link href="/waitlist" className="text-primary
|
|
218
|
+
<Link href="/waitlist" className="text-primary">
|
|
219
219
|
{t("waitlist.invite.join_link")}
|
|
220
220
|
</Link>
|
|
221
221
|
</CardContent>
|
|
@@ -43,14 +43,14 @@ export function HelpArticleBody(props: {
|
|
|
43
43
|
) : null}
|
|
44
44
|
<div className="mt-4 flex justify-between text-sm">
|
|
45
45
|
{prev ? (
|
|
46
|
-
<Link href={generateUrl({ page: `/help/${prev.howToType}/${prev.slug}` })}
|
|
46
|
+
<Link href={generateUrl({ page: `/help/${prev.howToType}/${prev.slug}` })}>
|
|
47
47
|
← {t("help.article.previous")}: {prev.title}
|
|
48
48
|
</Link>
|
|
49
49
|
) : (
|
|
50
50
|
<span />
|
|
51
51
|
)}
|
|
52
52
|
{next ? (
|
|
53
|
-
<Link href={generateUrl({ page: `/help/${next.howToType}/${next.slug}` })}
|
|
53
|
+
<Link href={generateUrl({ page: `/help/${next.howToType}/${next.slug}` })}>
|
|
54
54
|
{t("help.article.next")}: {next.title} →
|
|
55
55
|
</Link>
|
|
56
56
|
) : (
|
|
@@ -34,10 +34,7 @@ export class NotificationErrorBoundary extends Component<Props, State> {
|
|
|
34
34
|
<div className="flex items-center justify-center p-4 text-center">
|
|
35
35
|
<div className="text-muted-foreground text-sm">
|
|
36
36
|
<p>Something went wrong with notifications.</p>
|
|
37
|
-
<button
|
|
38
|
-
onClick={() => this.setState({ hasError: false })}
|
|
39
|
-
className="text-primary mt-2 underline hover:no-underline"
|
|
40
|
-
>
|
|
37
|
+
<button onClick={() => this.setState({ hasError: false })} className="text-primary mt-2">
|
|
41
38
|
Try again
|
|
42
39
|
</button>
|
|
43
40
|
</div>
|
|
@@ -111,11 +111,11 @@ export function OAuthConsentScreen({
|
|
|
111
111
|
<CardFooter className="justify-center">
|
|
112
112
|
<p className="text-xs text-center text-muted-foreground">
|
|
113
113
|
By authorizing, you agree to the app's{" "}
|
|
114
|
-
<a href={termsUrl} className="
|
|
114
|
+
<a href={termsUrl} className="hover:text-foreground" target="_blank" rel="noopener">
|
|
115
115
|
Terms of Service
|
|
116
116
|
</a>{" "}
|
|
117
117
|
and{" "}
|
|
118
|
-
<a href={privacyUrl} className="
|
|
118
|
+
<a href={privacyUrl} className="hover:text-foreground" target="_blank" rel="noopener">
|
|
119
119
|
Privacy Policy
|
|
120
120
|
</a>
|
|
121
121
|
.
|
|
@@ -159,11 +159,11 @@ export function WaitlistForm({ onSuccess }: WaitlistFormProps) {
|
|
|
159
159
|
label={
|
|
160
160
|
<>
|
|
161
161
|
{t("auth.gdpr.terms_prefix")}
|
|
162
|
-
<Link href="/terms" target="_blank" rel="noopener"
|
|
162
|
+
<Link href="/terms" target="_blank" rel="noopener">
|
|
163
163
|
{t("auth.gdpr.terms_of_service")}
|
|
164
164
|
</Link>
|
|
165
165
|
{t("auth.gdpr.and")}
|
|
166
|
-
<Link href="/privacy" target="_blank" rel="noopener"
|
|
166
|
+
<Link href="/privacy" target="_blank" rel="noopener">
|
|
167
167
|
{t("auth.gdpr.privacy_policy")}
|
|
168
168
|
</Link>
|
|
169
169
|
</>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
|
4
4
|
import { RefreshCw, Users } from "lucide-react";
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
|
-
import { useCallback, useEffect, useState } from "react";
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
7
7
|
import { errorToast } from "../../../../components";
|
|
8
8
|
import { SectionHeader } from "../../../../components/typography";
|
|
9
9
|
import {
|
|
@@ -64,6 +64,17 @@ export function WaitlistList() {
|
|
|
64
64
|
|
|
65
65
|
const columns = useWaitlistTableStructure({ onInvite: handleInvite });
|
|
66
66
|
|
|
67
|
+
const statusItems = useMemo(
|
|
68
|
+
() => ({
|
|
69
|
+
all: t("waitlist.admin.all_statuses"),
|
|
70
|
+
pending: t("waitlist.admin.status.pending"),
|
|
71
|
+
confirmed: t("waitlist.admin.status.confirmed"),
|
|
72
|
+
invited: t("waitlist.admin.status.invited"),
|
|
73
|
+
registered: t("waitlist.admin.status.registered"),
|
|
74
|
+
}),
|
|
75
|
+
[t],
|
|
76
|
+
);
|
|
77
|
+
|
|
67
78
|
const table = useReactTable({
|
|
68
79
|
data: entries,
|
|
69
80
|
columns,
|
|
@@ -82,7 +93,7 @@ export function WaitlistList() {
|
|
|
82
93
|
|
|
83
94
|
<div className="flex items-center gap-4">
|
|
84
95
|
{/* Status Filter */}
|
|
85
|
-
<Select value={statusFilter} onValueChange={(value) => setStatusFilter(value ?? "all")}>
|
|
96
|
+
<Select items={statusItems} value={statusFilter} onValueChange={(value) => setStatusFilter(value ?? "all")}>
|
|
86
97
|
<SelectTrigger className="w-40">
|
|
87
98
|
<SelectValue placeholder={t("waitlist.admin.filter_placeholder")} />
|
|
88
99
|
</SelectTrigger>
|
|
@@ -21,7 +21,7 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(({ className, href,
|
|
|
21
21
|
prefetch={false}
|
|
22
22
|
ref={ref}
|
|
23
23
|
href={href}
|
|
24
|
-
className={cn("text-primary font-medium
|
|
24
|
+
className={cn("text-primary font-medium", className)}
|
|
25
25
|
{...props}
|
|
26
26
|
>
|
|
27
27
|
{children}
|
|
@@ -59,7 +59,7 @@ function AccordionContent({ className, children, ...props }: AccordionPrimitive.
|
|
|
59
59
|
>
|
|
60
60
|
<div
|
|
61
61
|
className={cn(
|
|
62
|
-
"pt-0 pb-4 [&_a]:hover:text-foreground h-(--accordion-panel-height) data-ending-style:h-0 data-starting-style:h-0 [&
|
|
62
|
+
"pt-0 pb-4 [&_a]:hover:text-foreground h-(--accordion-panel-height) data-ending-style:h-0 data-starting-style:h-0 [&_p:not(:last-child)]:mb-4",
|
|
63
63
|
className,
|
|
64
64
|
)}
|
|
65
65
|
>
|
|
@@ -114,7 +114,7 @@ function AlertDialogDescription({
|
|
|
114
114
|
<AlertDialogPrimitive.Description
|
|
115
115
|
data-slot="alert-dialog-description"
|
|
116
116
|
className={cn(
|
|
117
|
-
"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty
|
|
117
|
+
"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty",
|
|
118
118
|
className,
|
|
119
119
|
)}
|
|
120
120
|
{...props}
|
|
@@ -27,10 +27,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
27
27
|
return (
|
|
28
28
|
<div
|
|
29
29
|
data-slot="alert-title"
|
|
30
|
-
className={cn(
|
|
31
|
-
"text-sm font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
|
32
|
-
className,
|
|
33
|
-
)}
|
|
30
|
+
className={cn("text-sm font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground", className)}
|
|
34
31
|
{...props}
|
|
35
32
|
/>
|
|
36
33
|
);
|
|
@@ -41,7 +38,7 @@ function AlertDescription({ className, ...props }: React.ComponentProps<"div">)
|
|
|
41
38
|
<div
|
|
42
39
|
data-slot="alert-description"
|
|
43
40
|
className={cn(
|
|
44
|
-
"text-muted-foreground text-xs/relaxed text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground
|
|
41
|
+
"text-muted-foreground text-xs/relaxed text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground",
|
|
45
42
|
className,
|
|
46
43
|
)}
|
|
47
44
|
{...props}
|
|
@@ -16,7 +16,7 @@ const badgeVariants = cva(
|
|
|
16
16
|
outline:
|
|
17
17
|
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30",
|
|
18
18
|
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
19
|
-
link: "text-primary
|
|
19
|
+
link: "text-primary",
|
|
20
20
|
warning: "bg-warning text-warning-foreground [a]:hover:bg-warning/80",
|
|
21
21
|
softGreen: "bg-green-500/10 text-green-700 dark:bg-green-500/15 dark:text-green-400",
|
|
22
22
|
softRed: "bg-red-500/10 text-red-700 dark:bg-red-500/15 dark:text-red-400",
|
|
@@ -17,7 +17,7 @@ const buttonVariants = cva(
|
|
|
17
17
|
"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
18
18
|
destructive:
|
|
19
19
|
"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
|
20
|
-
link: "text-primary
|
|
20
|
+
link: "text-primary",
|
|
21
21
|
},
|
|
22
22
|
size: {
|
|
23
23
|
default:
|
|
@@ -102,10 +102,7 @@ function DialogDescription({ className, ...props }: DialogPrimitive.Description.
|
|
|
102
102
|
return (
|
|
103
103
|
<DialogPrimitive.Description
|
|
104
104
|
data-slot="dialog-description"
|
|
105
|
-
className={cn(
|
|
106
|
-
"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3",
|
|
107
|
-
className,
|
|
108
|
-
)}
|
|
105
|
+
className={cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed", className)}
|
|
109
106
|
{...props}
|
|
110
107
|
/>
|
|
111
108
|
);
|
|
@@ -123,7 +123,7 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
123
123
|
className={cn(
|
|
124
124
|
"text-muted-foreground text-left text-xs/relaxed [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
|
|
125
125
|
"last:mt-0 nth-last-2:-mt-1",
|
|
126
|
-
"[&>a:hover]:text-primary
|
|
126
|
+
"[&>a:hover]:text-primary",
|
|
127
127
|
className,
|
|
128
128
|
)}
|
|
129
129
|
{...props}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { validateCodiceFiscale, validateItalianTaxCode, validatePartitaIva } from "../italian-validators";
|
|
4
|
+
|
|
5
|
+
const VALID_CODICE_FISCALE = "RSSMRA85M01H501Q";
|
|
6
|
+
const VALID_PARTITA_IVA = "00743110157";
|
|
7
|
+
|
|
8
|
+
describe("validatePartitaIva", () => {
|
|
9
|
+
it("accepts a valid partita iva", () => {
|
|
10
|
+
expect(validatePartitaIva(VALID_PARTITA_IVA)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("rejects a partita iva with a wrong check digit", () => {
|
|
14
|
+
expect(validatePartitaIva("00743110158")).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("rejects a codice fiscale", () => {
|
|
18
|
+
expect(validatePartitaIva(VALID_CODICE_FISCALE)).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("validateCodiceFiscale", () => {
|
|
23
|
+
it("accepts a valid codice fiscale", () => {
|
|
24
|
+
expect(validateCodiceFiscale(VALID_CODICE_FISCALE)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("rejects a partita iva by default", () => {
|
|
28
|
+
expect(validateCodiceFiscale(VALID_PARTITA_IVA)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("accepts a partita iva when allowPartitaIva is set", () => {
|
|
32
|
+
expect(validateCodiceFiscale(VALID_PARTITA_IVA, { allowPartitaIva: true })).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("still accepts a codice fiscale when allowPartitaIva is set", () => {
|
|
36
|
+
expect(validateCodiceFiscale(VALID_CODICE_FISCALE, { allowPartitaIva: true })).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("rejects a value that is neither, even when allowPartitaIva is set", () => {
|
|
40
|
+
expect(validateCodiceFiscale("NOT-A-CODE", { allowPartitaIva: true })).toBe(false);
|
|
41
|
+
expect(validateCodiceFiscale("00743110158", { allowPartitaIva: true })).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("ignores whitespace", () => {
|
|
45
|
+
expect(validateCodiceFiscale("0074 3110 157", { allowPartitaIva: true })).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("rejects empty values", () => {
|
|
49
|
+
expect(validateCodiceFiscale("", { allowPartitaIva: true })).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("validateItalianTaxCode", () => {
|
|
54
|
+
it("forwards the options to the codice fiscale validator", () => {
|
|
55
|
+
expect(validateItalianTaxCode(VALID_PARTITA_IVA, "codiceFiscale")).toBe(false);
|
|
56
|
+
expect(validateItalianTaxCode(VALID_PARTITA_IVA, "codiceFiscale", { allowPartitaIva: true })).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("leaves partita iva validation unchanged", () => {
|
|
60
|
+
expect(validateItalianTaxCode(VALID_PARTITA_IVA, "partitaIva")).toBe(true);
|
|
61
|
+
expect(validateItalianTaxCode(VALID_CODICE_FISCALE, "partitaIva")).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
package/src/utils/index.ts
CHANGED
|
@@ -12,6 +12,14 @@ export { getTableComponents, getTableOptions, TableOptions } from "./table-optio
|
|
|
12
12
|
export { entityObjectSchema, userObjectSchema, type EntityObject, type UserObject } from "./schemas";
|
|
13
13
|
|
|
14
14
|
export { getInitials } from "./getInitials";
|
|
15
|
+
export {
|
|
16
|
+
validatePartitaIva,
|
|
17
|
+
validateCodiceFiscale,
|
|
18
|
+
validateItalianTaxCode,
|
|
19
|
+
formatPartitaIva,
|
|
20
|
+
formatCodiceFiscale,
|
|
21
|
+
type CodiceFiscaleValidationOptions,
|
|
22
|
+
} from "./italian-validators";
|
|
15
23
|
export * from "./blocknote-diff.util";
|
|
16
24
|
export * from "./blocknote-word-diff-renderer.util";
|
|
17
25
|
export * from "./icons";
|