@voyantjs/identity-ui 0.13.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/README.md +17 -0
- package/dist/components/address-dialog.d.ts +11 -0
- package/dist/components/address-dialog.d.ts.map +1 -0
- package/dist/components/address-dialog.js +122 -0
- package/dist/components/contact-point-dialog.d.ts +11 -0
- package/dist/components/contact-point-dialog.d.ts.map +1 -0
- package/dist/components/contact-point-dialog.js +71 -0
- package/dist/components/named-contact-dialog.d.ts +11 -0
- package/dist/components/named-contact-dialog.d.ts.map +1 -0
- package/dist/components/named-contact-dialog.js +94 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @voyantjs/identity-ui
|
|
2
|
+
|
|
3
|
+
Importable React UI components for Voyant identity. Bundler-consumed (Vite, Next.js, webpack, etc.).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @voyantjs/identity-ui @voyantjs/identity-react @voyantjs/voyant-ui @tanstack/react-query react react-dom
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`@voyantjs/voyant-ui` provides the design-system primitives. `@voyantjs/identity-react` provides the data-layer hooks. Both are required peers.
|
|
12
|
+
|
|
13
|
+
All components accept a `className` prop and merge it with `cn()`. Wrap or compose to extend; use the registry copy-paste path (`npx shadcn add @voyant/...`) for components you want to fork outright.
|
|
14
|
+
|
|
15
|
+
## Not included (registry-only)
|
|
16
|
+
|
|
17
|
+
Some components couple to TanStack Router or template-local helpers and remain available only via the shadcn registry: `identity-page`. Import via `npx shadcn add @voyant/<component>` and customize per-project.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AddressRecord } from "@voyantjs/identity-react";
|
|
2
|
+
export interface AddressDialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
entityType: string;
|
|
6
|
+
entityId: string;
|
|
7
|
+
address?: AddressRecord;
|
|
8
|
+
onSuccess?: (address: AddressRecord) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function AddressDialog({ open, onOpenChange, entityType, entityId, address, onSuccess, }: AddressDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=address-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address-dialog.d.ts","sourceRoot":"","sources":["../../src/components/address-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;AA2DjC,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CAC7C;AAED,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,GACV,EAAE,kBAAkB,2CAuMpB"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useAddressMutation, } from "@voyantjs/identity-react";
|
|
4
|
+
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/voyant-ui/components";
|
|
5
|
+
import { CountryCombobox } from "@voyantjs/voyant-ui/components/country-combobox";
|
|
6
|
+
import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
|
|
7
|
+
import { Loader2 } from "lucide-react";
|
|
8
|
+
import { useEffect } from "react";
|
|
9
|
+
import { useForm } from "react-hook-form";
|
|
10
|
+
import { z } from "zod/v4";
|
|
11
|
+
const ADDRESS_LABELS = [
|
|
12
|
+
"primary",
|
|
13
|
+
"billing",
|
|
14
|
+
"shipping",
|
|
15
|
+
"mailing",
|
|
16
|
+
"meeting",
|
|
17
|
+
"service",
|
|
18
|
+
"legal",
|
|
19
|
+
"other",
|
|
20
|
+
];
|
|
21
|
+
const numOrEmpty = z.coerce.number().optional().or(z.literal("")).nullable();
|
|
22
|
+
const formSchema = z.object({
|
|
23
|
+
label: z.enum(ADDRESS_LABELS),
|
|
24
|
+
fullText: z.string().optional().nullable(),
|
|
25
|
+
line1: z.string().optional().nullable(),
|
|
26
|
+
line2: z.string().optional().nullable(),
|
|
27
|
+
city: z.string().optional().nullable(),
|
|
28
|
+
region: z.string().optional().nullable(),
|
|
29
|
+
postalCode: z.string().optional().nullable(),
|
|
30
|
+
country: z.string().optional().nullable(),
|
|
31
|
+
latitude: numOrEmpty,
|
|
32
|
+
longitude: numOrEmpty,
|
|
33
|
+
timezone: z.string().optional().nullable(),
|
|
34
|
+
isPrimary: z.boolean(),
|
|
35
|
+
notes: z.string().optional().nullable(),
|
|
36
|
+
});
|
|
37
|
+
export function AddressDialog({ open, onOpenChange, entityType, entityId, address, onSuccess, }) {
|
|
38
|
+
const isEditing = Boolean(address);
|
|
39
|
+
const { create, update } = useAddressMutation();
|
|
40
|
+
const form = useForm({
|
|
41
|
+
resolver: zodResolver(formSchema),
|
|
42
|
+
defaultValues: {
|
|
43
|
+
label: "primary",
|
|
44
|
+
fullText: "",
|
|
45
|
+
line1: "",
|
|
46
|
+
line2: "",
|
|
47
|
+
city: "",
|
|
48
|
+
region: "",
|
|
49
|
+
postalCode: "",
|
|
50
|
+
country: "",
|
|
51
|
+
latitude: "",
|
|
52
|
+
longitude: "",
|
|
53
|
+
timezone: "",
|
|
54
|
+
isPrimary: false,
|
|
55
|
+
notes: "",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (open && address) {
|
|
60
|
+
form.reset({
|
|
61
|
+
label: address.label,
|
|
62
|
+
fullText: address.fullText ?? "",
|
|
63
|
+
line1: address.line1 ?? "",
|
|
64
|
+
line2: address.line2 ?? "",
|
|
65
|
+
city: address.city ?? "",
|
|
66
|
+
region: address.region ?? "",
|
|
67
|
+
postalCode: address.postalCode ?? "",
|
|
68
|
+
country: address.country ?? "",
|
|
69
|
+
latitude: address.latitude ?? "",
|
|
70
|
+
longitude: address.longitude ?? "",
|
|
71
|
+
timezone: address.timezone ?? "",
|
|
72
|
+
isPrimary: address.isPrimary,
|
|
73
|
+
notes: address.notes ?? "",
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (open) {
|
|
78
|
+
form.reset({
|
|
79
|
+
label: "primary",
|
|
80
|
+
fullText: "",
|
|
81
|
+
line1: "",
|
|
82
|
+
line2: "",
|
|
83
|
+
city: "",
|
|
84
|
+
region: "",
|
|
85
|
+
postalCode: "",
|
|
86
|
+
country: "",
|
|
87
|
+
latitude: "",
|
|
88
|
+
longitude: "",
|
|
89
|
+
timezone: "",
|
|
90
|
+
isPrimary: false,
|
|
91
|
+
notes: "",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}, [address, form, open]);
|
|
95
|
+
const onSubmit = async (values) => {
|
|
96
|
+
const toNum = (value) => typeof value === "number" ? value : null;
|
|
97
|
+
const payload = {
|
|
98
|
+
entityType,
|
|
99
|
+
entityId,
|
|
100
|
+
label: values.label,
|
|
101
|
+
fullText: values.fullText || null,
|
|
102
|
+
line1: values.line1 || null,
|
|
103
|
+
line2: values.line2 || null,
|
|
104
|
+
city: values.city || null,
|
|
105
|
+
region: values.region || null,
|
|
106
|
+
postalCode: values.postalCode || null,
|
|
107
|
+
country: values.country || null,
|
|
108
|
+
latitude: toNum(values.latitude),
|
|
109
|
+
longitude: toNum(values.longitude),
|
|
110
|
+
timezone: values.timezone || null,
|
|
111
|
+
isPrimary: values.isPrimary,
|
|
112
|
+
notes: values.notes || null,
|
|
113
|
+
};
|
|
114
|
+
const saved = isEditing
|
|
115
|
+
? await update.mutateAsync({ id: address.id, input: payload })
|
|
116
|
+
: await create.mutateAsync(payload);
|
|
117
|
+
onOpenChange(false);
|
|
118
|
+
onSuccess?.(saved);
|
|
119
|
+
};
|
|
120
|
+
const isSubmitting = form.formState.isSubmitting || create.isPending || update.isPending;
|
|
121
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Address" : "Add Address" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Label" }), _jsxs(Select, { items: ADDRESS_LABELS.map((x) => ({ label: x.replace(/_/g, " "), value: x })), value: form.watch("label"), onValueChange: (value) => form.setValue("label", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: ADDRESS_LABELS.map((label) => (_jsx(SelectItem, { value: label, className: "capitalize", children: label }, label))) })] })] }), _jsxs("div", { className: "flex items-center gap-2 self-end pb-1", children: [_jsx(Switch, { checked: form.watch("isPrimary"), onCheckedChange: (value) => form.setValue("isPrimary", value) }), _jsx(Label, { children: "Primary" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Line 1" }), _jsx(Input, { ...form.register("line1") })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Line 2" }), _jsx(Input, { ...form.register("line2") })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "City" }), _jsx(Input, { ...form.register("city") })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Region" }), _jsx(Input, { ...form.register("region") })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Postal code" }), _jsx(Input, { ...form.register("postalCode") })] })] }), _jsxs("div", { className: "grid grid-cols-3 gap-3", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Country" }), _jsx(CountryCombobox, { value: form.watch("country") ?? null, onChange: (code) => form.setValue("country", code) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Timezone" }), _jsx(Input, { ...form.register("timezone"), placeholder: "Europe/Istanbul" })] }), _jsx("div", {})] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Latitude" }), _jsx(Input, { ...form.register("latitude"), type: "number", step: "any" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Longitude" }), _jsx(Input, { ...form.register("longitude"), type: "number", step: "any" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes") })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Add Address"] })] })] })] }) }));
|
|
122
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ContactPointRecord } from "@voyantjs/identity-react";
|
|
2
|
+
export interface ContactPointDialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
entityType: string;
|
|
6
|
+
entityId: string;
|
|
7
|
+
contactPoint?: ContactPointRecord;
|
|
8
|
+
onSuccess?: (contactPoint: ContactPointRecord) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function ContactPointDialog({ open, onOpenChange, entityType, entityId, contactPoint, onSuccess, }: ContactPointDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=contact-point-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contact-point-dialog.d.ts","sourceRoot":"","sources":["../../src/components/contact-point-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,kBAAkB,EAIxB,MAAM,0BAA0B,CAAA;AAkDjC,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,SAAS,CAAC,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,IAAI,CAAA;CACvD;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,SAAS,GACV,EAAE,uBAAuB,2CAoHzB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useContactPointMutation, } from "@voyantjs/identity-react";
|
|
4
|
+
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/voyant-ui/components";
|
|
5
|
+
import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
|
|
6
|
+
import { Loader2 } from "lucide-react";
|
|
7
|
+
import { useEffect } from "react";
|
|
8
|
+
import { useForm } from "react-hook-form";
|
|
9
|
+
import { z } from "zod/v4";
|
|
10
|
+
const CONTACT_POINT_KINDS = [
|
|
11
|
+
"email",
|
|
12
|
+
"phone",
|
|
13
|
+
"mobile",
|
|
14
|
+
"whatsapp",
|
|
15
|
+
"website",
|
|
16
|
+
"sms",
|
|
17
|
+
"fax",
|
|
18
|
+
"social",
|
|
19
|
+
"other",
|
|
20
|
+
];
|
|
21
|
+
const formSchema = z.object({
|
|
22
|
+
kind: z.enum(CONTACT_POINT_KINDS),
|
|
23
|
+
label: z.string().optional().nullable(),
|
|
24
|
+
value: z.string().min(1, "Value is required").max(500),
|
|
25
|
+
isPrimary: z.boolean(),
|
|
26
|
+
notes: z.string().optional().nullable(),
|
|
27
|
+
});
|
|
28
|
+
export function ContactPointDialog({ open, onOpenChange, entityType, entityId, contactPoint, onSuccess, }) {
|
|
29
|
+
const isEditing = Boolean(contactPoint);
|
|
30
|
+
const { create, update } = useContactPointMutation();
|
|
31
|
+
const form = useForm({
|
|
32
|
+
resolver: zodResolver(formSchema),
|
|
33
|
+
defaultValues: { kind: "email", label: "", value: "", isPrimary: false, notes: "" },
|
|
34
|
+
});
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (open && contactPoint) {
|
|
37
|
+
form.reset({
|
|
38
|
+
kind: contactPoint.kind,
|
|
39
|
+
label: contactPoint.label ?? "",
|
|
40
|
+
value: contactPoint.value,
|
|
41
|
+
isPrimary: contactPoint.isPrimary,
|
|
42
|
+
notes: contactPoint.notes ?? "",
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (open) {
|
|
47
|
+
form.reset({ kind: "email", label: "", value: "", isPrimary: false, notes: "" });
|
|
48
|
+
}
|
|
49
|
+
}, [contactPoint, form, open]);
|
|
50
|
+
const onSubmit = async (values) => {
|
|
51
|
+
const payload = {
|
|
52
|
+
entityType,
|
|
53
|
+
entityId,
|
|
54
|
+
kind: values.kind,
|
|
55
|
+
label: values.label || null,
|
|
56
|
+
value: values.value,
|
|
57
|
+
isPrimary: values.isPrimary,
|
|
58
|
+
notes: values.notes || null,
|
|
59
|
+
};
|
|
60
|
+
const saved = isEditing
|
|
61
|
+
? await update.mutateAsync({ id: contactPoint.id, input: payload })
|
|
62
|
+
: await create.mutateAsync(payload);
|
|
63
|
+
onOpenChange(false);
|
|
64
|
+
onSuccess?.(saved);
|
|
65
|
+
};
|
|
66
|
+
const isSubmitting = form.formState.isSubmitting || create.isPending || update.isPending;
|
|
67
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Contact Point" : "Add Contact Point" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Kind" }), _jsxs(Select, { items: CONTACT_POINT_KINDS.map((x) => ({
|
|
68
|
+
label: x.replace(/_/g, " "),
|
|
69
|
+
value: x,
|
|
70
|
+
})), value: form.watch("kind"), onValueChange: (value) => form.setValue("kind", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: CONTACT_POINT_KINDS.map((kind) => (_jsx(SelectItem, { value: kind, className: "capitalize", children: kind }, kind))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Label" }), _jsx(Input, { ...form.register("label"), placeholder: "work, personal..." })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Value" }), _jsx(Input, { ...form.register("value"), placeholder: "name@example.com" }), form.formState.errors.value ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.value.message })) : null] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("isPrimary"), onCheckedChange: (value) => form.setValue("isPrimary", value) }), _jsx(Label, { children: "Primary" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes") })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Add Contact Point"] })] })] })] }) }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type NamedContactRecord } from "@voyantjs/identity-react";
|
|
2
|
+
export interface NamedContactDialogProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
entityType: string;
|
|
6
|
+
entityId: string;
|
|
7
|
+
namedContact?: NamedContactRecord;
|
|
8
|
+
onSuccess?: (namedContact: NamedContactRecord) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function NamedContactDialog({ open, onOpenChange, entityType, entityId, namedContact, onSuccess, }: NamedContactDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=named-contact-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"named-contact-dialog.d.ts","sourceRoot":"","sources":["../../src/components/named-contact-dialog.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,kBAAkB,EAGxB,MAAM,0BAA0B,CAAA;AAqDjC,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,SAAS,CAAC,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,IAAI,CAAA;CACvD;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,SAAS,GACV,EAAE,uBAAuB,2CAoJzB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useNamedContactMutation, } from "@voyantjs/identity-react";
|
|
4
|
+
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Textarea, } from "@voyantjs/voyant-ui/components";
|
|
5
|
+
import { zodResolver } from "@voyantjs/voyant-ui/lib/zod-resolver";
|
|
6
|
+
import { Loader2 } from "lucide-react";
|
|
7
|
+
import { useEffect } from "react";
|
|
8
|
+
import { useForm } from "react-hook-form";
|
|
9
|
+
import { z } from "zod/v4";
|
|
10
|
+
const NAMED_CONTACT_ROLES = [
|
|
11
|
+
"general",
|
|
12
|
+
"primary",
|
|
13
|
+
"reservations",
|
|
14
|
+
"operations",
|
|
15
|
+
"front_desk",
|
|
16
|
+
"sales",
|
|
17
|
+
"emergency",
|
|
18
|
+
"accounting",
|
|
19
|
+
"legal",
|
|
20
|
+
"other",
|
|
21
|
+
];
|
|
22
|
+
const formSchema = z.object({
|
|
23
|
+
role: z.enum(NAMED_CONTACT_ROLES),
|
|
24
|
+
name: z.string().min(1, "Name is required").max(255),
|
|
25
|
+
title: z.string().optional().nullable(),
|
|
26
|
+
email: z.string().optional().nullable(),
|
|
27
|
+
phone: z.string().optional().nullable(),
|
|
28
|
+
isPrimary: z.boolean(),
|
|
29
|
+
notes: z.string().optional().nullable(),
|
|
30
|
+
});
|
|
31
|
+
export function NamedContactDialog({ open, onOpenChange, entityType, entityId, namedContact, onSuccess, }) {
|
|
32
|
+
const isEditing = Boolean(namedContact);
|
|
33
|
+
const { create, update } = useNamedContactMutation();
|
|
34
|
+
const form = useForm({
|
|
35
|
+
resolver: zodResolver(formSchema),
|
|
36
|
+
defaultValues: {
|
|
37
|
+
role: "general",
|
|
38
|
+
name: "",
|
|
39
|
+
title: "",
|
|
40
|
+
email: "",
|
|
41
|
+
phone: "",
|
|
42
|
+
isPrimary: false,
|
|
43
|
+
notes: "",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (open && namedContact) {
|
|
48
|
+
form.reset({
|
|
49
|
+
role: namedContact.role,
|
|
50
|
+
name: namedContact.name,
|
|
51
|
+
title: namedContact.title ?? "",
|
|
52
|
+
email: namedContact.email ?? "",
|
|
53
|
+
phone: namedContact.phone ?? "",
|
|
54
|
+
isPrimary: namedContact.isPrimary,
|
|
55
|
+
notes: namedContact.notes ?? "",
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (open) {
|
|
60
|
+
form.reset({
|
|
61
|
+
role: "general",
|
|
62
|
+
name: "",
|
|
63
|
+
title: "",
|
|
64
|
+
email: "",
|
|
65
|
+
phone: "",
|
|
66
|
+
isPrimary: false,
|
|
67
|
+
notes: "",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}, [form, namedContact, open]);
|
|
71
|
+
const onSubmit = async (values) => {
|
|
72
|
+
const payload = {
|
|
73
|
+
entityType,
|
|
74
|
+
entityId,
|
|
75
|
+
role: values.role,
|
|
76
|
+
name: values.name,
|
|
77
|
+
title: values.title || null,
|
|
78
|
+
email: values.email || null,
|
|
79
|
+
phone: values.phone || null,
|
|
80
|
+
isPrimary: values.isPrimary,
|
|
81
|
+
notes: values.notes || null,
|
|
82
|
+
};
|
|
83
|
+
const saved = isEditing
|
|
84
|
+
? await update.mutateAsync({ id: namedContact.id, input: payload })
|
|
85
|
+
: await create.mutateAsync(payload);
|
|
86
|
+
onOpenChange(false);
|
|
87
|
+
onSuccess?.(saved);
|
|
88
|
+
};
|
|
89
|
+
const isSubmitting = form.formState.isSubmitting || create.isPending || update.isPending;
|
|
90
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? "Edit Named Contact" : "Add Named Contact" }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Role" }), _jsxs(Select, { items: NAMED_CONTACT_ROLES.map((x) => ({
|
|
91
|
+
label: x.replace(/_/g, " "),
|
|
92
|
+
value: x,
|
|
93
|
+
})), value: form.watch("role"), onValueChange: (value) => form.setValue("role", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: NAMED_CONTACT_ROLES.map((role) => (_jsx(SelectItem, { value: role, className: "capitalize", children: role.replace(/_/g, " ") }, role))) })] })] }), _jsxs("div", { className: "flex items-center gap-2 self-end pb-1", children: [_jsx(Switch, { checked: form.watch("isPrimary"), onCheckedChange: (value) => form.setValue("isPrimary", value) }), _jsx(Label, { children: "Primary" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Name" }), _jsx(Input, { ...form.register("name"), placeholder: "Jane Doe" }), form.formState.errors.name ? (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.name.message })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Title" }), _jsx(Input, { ...form.register("title"), placeholder: "Director of Sales" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Email" }), _jsx(Input, { ...form.register("email"), placeholder: "jane@example.com" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Phone" }), _jsx(Input, { ...form.register("phone") })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Notes" }), _jsx(Textarea, { ...form.register("notes") })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, isEditing ? "Save Changes" : "Add Named Contact"] })] })] })] }) }));
|
|
94
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { AddressDialog, type AddressDialogProps } from "./components/address-dialog";
|
|
2
|
+
export { ContactPointDialog, type ContactPointDialogProps } from "./components/contact-point-dialog";
|
|
3
|
+
export { NamedContactDialog, type NamedContactDialogProps } from "./components/named-contact-dialog";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AACpF,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mCAAmC,CAAA;AACpG,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mCAAmC,CAAA"}
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voyantjs/identity-ui",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/voyantjs/voyant.git",
|
|
8
|
+
"directory": "packages/identity-ui"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./src/index.ts",
|
|
14
|
+
"./components/*": "./src/components/*.tsx"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.build.json",
|
|
18
|
+
"clean": "rm -rf dist",
|
|
19
|
+
"prepack": "pnpm run build",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"lint": "biome check src/",
|
|
22
|
+
"test": "vitest run --passWithNoTests"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@tanstack/react-query": "^5.0.0",
|
|
26
|
+
"@tanstack/react-table": "^8.0.0",
|
|
27
|
+
"@voyantjs/identity-react": "workspace:*",
|
|
28
|
+
"@voyantjs/voyant-ui": "workspace:*",
|
|
29
|
+
"react": "^19.0.0",
|
|
30
|
+
"react-dom": "^19.0.0",
|
|
31
|
+
"react-hook-form": "^7.60.0",
|
|
32
|
+
"zod": "^3.25.76"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@tanstack/react-query": "^5.96.2",
|
|
36
|
+
"@tanstack/react-table": "^8.21.3",
|
|
37
|
+
"@types/react": "^19.2.14",
|
|
38
|
+
"@types/react-dom": "^19.2.3",
|
|
39
|
+
"@voyantjs/identity-react": "workspace:*",
|
|
40
|
+
"@voyantjs/voyant-typescript-config": "workspace:*",
|
|
41
|
+
"@voyantjs/voyant-ui": "workspace:*",
|
|
42
|
+
"lucide-react": "^0.475.0",
|
|
43
|
+
"react": "^19.2.4",
|
|
44
|
+
"react-dom": "^19.2.4",
|
|
45
|
+
"react-hook-form": "^7.60.0",
|
|
46
|
+
"typescript": "^6.0.2",
|
|
47
|
+
"vitest": "^4.1.2",
|
|
48
|
+
"zod": "^3.25.76"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist"
|
|
52
|
+
],
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public",
|
|
55
|
+
"exports": {
|
|
56
|
+
".": {
|
|
57
|
+
"types": "./dist/index.d.ts",
|
|
58
|
+
"import": "./dist/index.js"
|
|
59
|
+
},
|
|
60
|
+
"./components/*": {
|
|
61
|
+
"types": "./dist/components/*.d.ts",
|
|
62
|
+
"import": "./dist/components/*.js"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"main": "./dist/index.js",
|
|
66
|
+
"types": "./dist/index.d.ts"
|
|
67
|
+
}
|
|
68
|
+
}
|