@carlonicora/nextjs-jsonapi 1.131.2 → 1.132.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-JAELO23S.js} +19 -19
- package/dist/{BlockNoteEditor-5RUXGWLR.js.map → BlockNoteEditor-JAELO23S.js.map} +1 -1
- package/dist/{BlockNoteEditor-F4AKH532.mjs → BlockNoteEditor-WZFEJS2B.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-BBBQGJEG.mjs → chunk-67LIVKYU.mjs} +89 -114
- package/dist/chunk-67LIVKYU.mjs.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-MP5VURBB.js} +893 -918
- package/dist/chunk-MP5VURBB.js.map +1 -0
- package/dist/{chunk-NATPK5ME.mjs → chunk-VSAAVVHY.mjs} +2 -2
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +3 -2
- package/dist/components/index.d.ts +3 -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/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-WZFEJS2B.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">
|
|
@@ -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";
|
|
@@ -29,7 +29,12 @@ export function validatePartitaIva(partitaIva: string): boolean {
|
|
|
29
29
|
return totalSum % 10 === 0;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export
|
|
32
|
+
export type CodiceFiscaleValidationOptions = {
|
|
33
|
+
/** When true, a valid Partita IVA is also accepted as a valid value. */
|
|
34
|
+
allowPartitaIva?: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function validateCodiceFiscale(codiceFiscale: string, options?: CodiceFiscaleValidationOptions): boolean {
|
|
33
38
|
if (!codiceFiscale || typeof codiceFiscale !== "string") {
|
|
34
39
|
return false;
|
|
35
40
|
}
|
|
@@ -37,13 +42,21 @@ export function validateCodiceFiscale(codiceFiscale: string): boolean {
|
|
|
37
42
|
const cleaned = codiceFiscale.replace(/\s/g, "").toUpperCase();
|
|
38
43
|
|
|
39
44
|
try {
|
|
40
|
-
|
|
45
|
+
if (CodiceFiscale.check(cleaned)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
41
48
|
} catch (error) {
|
|
42
|
-
|
|
49
|
+
// fall through to the optional Partita IVA check
|
|
43
50
|
}
|
|
51
|
+
|
|
52
|
+
return options?.allowPartitaIva === true && validatePartitaIva(cleaned);
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
export function validateItalianTaxCode(
|
|
55
|
+
export function validateItalianTaxCode(
|
|
56
|
+
value: string,
|
|
57
|
+
type: "partitaIva" | "codiceFiscale",
|
|
58
|
+
options?: CodiceFiscaleValidationOptions,
|
|
59
|
+
): boolean {
|
|
47
60
|
if (!value || typeof value !== "string") {
|
|
48
61
|
return false;
|
|
49
62
|
}
|
|
@@ -52,7 +65,7 @@ export function validateItalianTaxCode(value: string, type: "partitaIva" | "codi
|
|
|
52
65
|
case "partitaIva":
|
|
53
66
|
return validatePartitaIva(value);
|
|
54
67
|
case "codiceFiscale":
|
|
55
|
-
return validateCodiceFiscale(value);
|
|
68
|
+
return validateCodiceFiscale(value, options);
|
|
56
69
|
default:
|
|
57
70
|
return false;
|
|
58
71
|
}
|