@vendure-platform/create 0.3.0 → 0.5.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/CHANGELOG.md +8 -0
- package/README.md +7 -7
- package/dist/add-to-existing.js +1 -583
- package/dist/assets/storefront/messages/de.json +2 -0
- package/dist/assets/storefront/messages/en.json +2 -0
- package/dist/assets/storefront/package.json +1 -1
- package/dist/assets/storefront/src/app/[locale]/(catalog)/product/[slug]/page.tsx +7 -2
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/activity-feed.tsx +152 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/add-item-search.tsx +130 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/add-member-form.tsx +124 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/members-panel.tsx +240 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/page.tsx +47 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/rename-dialog.tsx +96 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/[id]/shared-cart-detail-client.tsx +555 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/actions.ts +260 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/new/new-shared-cart-form.tsx +77 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/new/page.tsx +47 -0
- package/dist/assets/storefront/src/app/[locale]/account/shared-carts/page.tsx +263 -0
- package/dist/assets/storefront/src/app/[locale]/cart/cart.tsx +12 -1
- package/dist/assets/storefront/src/app/[locale]/cart/order-summary.tsx +23 -0
- package/dist/assets/storefront/src/components/commerce/account-nav-items.ts +2 -0
- package/dist/assets/storefront/src/components/commerce/add-to-shared-cart.tsx +225 -0
- package/dist/assets/storefront/src/components/commerce/product-info.tsx +14 -1
- package/dist/assets/storefront/src/components/commerce/shared-cart-role.ts +39 -0
- package/dist/assets/storefront/src/components/commerce/shared-cart-status-badge.tsx +24 -0
- package/dist/assets/storefront/src/graphql-env.d.ts +28 -15
- package/dist/assets/storefront/src/lib/vendure/api.ts +8 -4
- package/dist/assets/storefront/src/lib/vendure/mutations.ts +96 -0
- package/dist/assets/storefront/src/lib/vendure/queries.ts +142 -0
- package/dist/assets/storefront/src/schema.graphql +321 -61
- package/dist/bin/run.js +1 -119
- package/dist/cli.js +1 -309
- package/dist/git-init.js +1 -60
- package/dist/index.js +1 -19
- package/dist/plugin-registry.js +1 -203
- package/dist/registry.js +1 -29
- package/dist/scaffold-shared.js +1 -124
- package/dist/scaffold-storefront-only.js +1 -101
- package/dist/scaffolder.js +1 -203
- package/dist/sort-package-json.js +1 -26
- package/dist/storefront-copier.js +1 -149
- package/dist/template-renderer.js +1 -221
- package/dist/types.js +1 -3
- package/package.json +5 -4
- package/dist/add-to-existing.js.map +0 -1
- package/dist/assets/storefront/src/lib/quantity.test.js +0 -20
- package/dist/bin/run.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/git-init.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plugin-registry.js.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/scaffold-shared.js.map +0 -1
- package/dist/scaffold-storefront-only.js.map +0 -1
- package/dist/scaffolder.js.map +0 -1
- package/dist/sort-package-json.js.map +0 -1
- package/dist/storefront-copier.js.map +0 -1
- package/dist/template-renderer.js.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {useState, useTransition} from 'react';
|
|
4
|
+
import {toast} from 'sonner';
|
|
5
|
+
import {Badge} from '@/components/ui/badge';
|
|
6
|
+
import {Button} from '@/components/ui/button';
|
|
7
|
+
import {
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuRadioGroup,
|
|
12
|
+
DropdownMenuRadioItem,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
DropdownMenuSub,
|
|
15
|
+
DropdownMenuSubContent,
|
|
16
|
+
DropdownMenuSubTrigger,
|
|
17
|
+
DropdownMenuTrigger,
|
|
18
|
+
} from '@/components/ui/dropdown-menu';
|
|
19
|
+
import {
|
|
20
|
+
AlertDialog,
|
|
21
|
+
AlertDialogAction,
|
|
22
|
+
AlertDialogCancel,
|
|
23
|
+
AlertDialogContent,
|
|
24
|
+
AlertDialogDescription,
|
|
25
|
+
AlertDialogFooter,
|
|
26
|
+
AlertDialogHeader,
|
|
27
|
+
AlertDialogTitle,
|
|
28
|
+
} from '@/components/ui/alert-dialog';
|
|
29
|
+
import {Crown, MoreHorizontal, ShieldCheck, UserMinus, Loader2} from 'lucide-react';
|
|
30
|
+
import {ROLE_OPTIONS, roleLabel} from '@/components/commerce/shared-cart-role';
|
|
31
|
+
import {
|
|
32
|
+
setSharedCartMemberRole,
|
|
33
|
+
removeSharedCartMember,
|
|
34
|
+
transferSharedCartOwnership,
|
|
35
|
+
type SharedCartData,
|
|
36
|
+
type SharedCartRole,
|
|
37
|
+
} from '../actions';
|
|
38
|
+
import {AddMemberDialog} from './add-member-form';
|
|
39
|
+
|
|
40
|
+
export function MembersPanel({
|
|
41
|
+
cart,
|
|
42
|
+
viewerId,
|
|
43
|
+
canManage,
|
|
44
|
+
onChanged,
|
|
45
|
+
}: {
|
|
46
|
+
cart: SharedCartData;
|
|
47
|
+
viewerId: string;
|
|
48
|
+
canManage: boolean;
|
|
49
|
+
onChanged: () => void;
|
|
50
|
+
}) {
|
|
51
|
+
const [transferTarget, setTransferTarget] = useState<{customerId: string; name: string} | null>(null);
|
|
52
|
+
const isOpen = cart.status === 'Open';
|
|
53
|
+
const existingIds = new Set(cart.members.map(m => m.customer.id));
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<section className="border rounded-lg">
|
|
57
|
+
<div className="px-4 py-3 border-b">
|
|
58
|
+
<h2 className="font-semibold">
|
|
59
|
+
Members <span className="text-muted-foreground font-normal">({cart.members.length})</span>
|
|
60
|
+
</h2>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<ul className="divide-y">
|
|
64
|
+
{cart.members.map(member => {
|
|
65
|
+
const isOwner = member.customer.id === cart.owner.id;
|
|
66
|
+
const isYou = member.customer.id === viewerId;
|
|
67
|
+
const name = `${member.customer.firstName} ${member.customer.lastName}`.trim();
|
|
68
|
+
const showMenu = canManage && isOpen && !isOwner;
|
|
69
|
+
return (
|
|
70
|
+
<li key={member.customer.id} className="flex items-center gap-2 px-4 py-3">
|
|
71
|
+
<div className="min-w-0 flex-1">
|
|
72
|
+
<p className="text-sm font-medium truncate">
|
|
73
|
+
{name}
|
|
74
|
+
{isYou && <span className="text-muted-foreground font-normal"> (you)</span>}
|
|
75
|
+
</p>
|
|
76
|
+
<p className="text-xs text-muted-foreground truncate">{member.customer.emailAddress}</p>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{isOwner ? (
|
|
80
|
+
<Badge className="shrink-0 gap-1">
|
|
81
|
+
<Crown className="h-3 w-3" />
|
|
82
|
+
Owner
|
|
83
|
+
</Badge>
|
|
84
|
+
) : (
|
|
85
|
+
<Badge variant="outline" className="shrink-0">
|
|
86
|
+
{roleLabel(member.role)}
|
|
87
|
+
</Badge>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{showMenu && (
|
|
91
|
+
<MemberMenu
|
|
92
|
+
cartId={cart.id}
|
|
93
|
+
member={member}
|
|
94
|
+
onChanged={onChanged}
|
|
95
|
+
onTransfer={() => setTransferTarget({customerId: member.customer.id, name})}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
</li>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</ul>
|
|
102
|
+
|
|
103
|
+
{canManage && isOpen && (
|
|
104
|
+
<div className="px-4 py-3 border-t">
|
|
105
|
+
<AddMemberDialog cartId={cart.id} excludeCustomerIds={existingIds} onAdded={onChanged} />
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
<TransferOwnershipDialog
|
|
110
|
+
cartId={cart.id}
|
|
111
|
+
target={transferTarget}
|
|
112
|
+
onClose={() => setTransferTarget(null)}
|
|
113
|
+
onTransferred={onChanged}
|
|
114
|
+
/>
|
|
115
|
+
</section>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function MemberMenu({
|
|
120
|
+
cartId,
|
|
121
|
+
member,
|
|
122
|
+
onChanged,
|
|
123
|
+
onTransfer,
|
|
124
|
+
}: {
|
|
125
|
+
cartId: string;
|
|
126
|
+
member: SharedCartData['members'][number];
|
|
127
|
+
onChanged: () => void;
|
|
128
|
+
onTransfer: () => void;
|
|
129
|
+
}) {
|
|
130
|
+
const [isPending, startTransition] = useTransition();
|
|
131
|
+
|
|
132
|
+
function changeRole(role: SharedCartRole) {
|
|
133
|
+
if (role === member.role) return;
|
|
134
|
+
startTransition(async () => {
|
|
135
|
+
const result = await setSharedCartMemberRole(cartId, member.customer.id, role);
|
|
136
|
+
if (!result.success) toast.error(result.message ?? 'Could not change role.');
|
|
137
|
+
onChanged();
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function remove() {
|
|
142
|
+
startTransition(async () => {
|
|
143
|
+
const result = await removeSharedCartMember(cartId, member.customer.id);
|
|
144
|
+
if (!result.success) toast.error(result.message ?? 'Could not remove member.');
|
|
145
|
+
else toast.success('Member removed.');
|
|
146
|
+
onChanged();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<DropdownMenu>
|
|
152
|
+
<DropdownMenuTrigger asChild>
|
|
153
|
+
<Button variant="ghost" size="icon" className="h-8 w-8 shrink-0" disabled={isPending} aria-label="Member actions">
|
|
154
|
+
{isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : <MoreHorizontal className="h-4 w-4" />}
|
|
155
|
+
</Button>
|
|
156
|
+
</DropdownMenuTrigger>
|
|
157
|
+
<DropdownMenuContent align="end" className="w-48">
|
|
158
|
+
<DropdownMenuSub>
|
|
159
|
+
<DropdownMenuSubTrigger>
|
|
160
|
+
<ShieldCheck className="h-4 w-4 mr-2" />
|
|
161
|
+
Change role
|
|
162
|
+
</DropdownMenuSubTrigger>
|
|
163
|
+
<DropdownMenuSubContent>
|
|
164
|
+
<DropdownMenuRadioGroup value={member.role} onValueChange={v => changeRole(v as SharedCartRole)}>
|
|
165
|
+
{ROLE_OPTIONS.map(o => (
|
|
166
|
+
<DropdownMenuRadioItem key={o.value} value={o.value}>
|
|
167
|
+
{o.label}
|
|
168
|
+
</DropdownMenuRadioItem>
|
|
169
|
+
))}
|
|
170
|
+
</DropdownMenuRadioGroup>
|
|
171
|
+
</DropdownMenuSubContent>
|
|
172
|
+
</DropdownMenuSub>
|
|
173
|
+
<DropdownMenuItem onSelect={onTransfer}>
|
|
174
|
+
<Crown className="h-4 w-4 mr-2" />
|
|
175
|
+
Make owner
|
|
176
|
+
</DropdownMenuItem>
|
|
177
|
+
<DropdownMenuSeparator />
|
|
178
|
+
<DropdownMenuItem onSelect={remove} className="text-destructive focus:text-destructive">
|
|
179
|
+
<UserMinus className="h-4 w-4 mr-2" />
|
|
180
|
+
Remove
|
|
181
|
+
</DropdownMenuItem>
|
|
182
|
+
</DropdownMenuContent>
|
|
183
|
+
</DropdownMenu>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function TransferOwnershipDialog({
|
|
188
|
+
cartId,
|
|
189
|
+
target,
|
|
190
|
+
onClose,
|
|
191
|
+
onTransferred,
|
|
192
|
+
}: {
|
|
193
|
+
cartId: string;
|
|
194
|
+
target: {customerId: string; name: string} | null;
|
|
195
|
+
onClose: () => void;
|
|
196
|
+
onTransferred: () => void;
|
|
197
|
+
}) {
|
|
198
|
+
const [isPending, startTransition] = useTransition();
|
|
199
|
+
|
|
200
|
+
function confirm() {
|
|
201
|
+
if (!target) return;
|
|
202
|
+
startTransition(async () => {
|
|
203
|
+
const result = await transferSharedCartOwnership(cartId, target.customerId);
|
|
204
|
+
if (!result.success) {
|
|
205
|
+
toast.error(result.message ?? 'Could not transfer ownership.');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
toast.success(`${target.name} is now the owner.`);
|
|
209
|
+
onTransferred();
|
|
210
|
+
onClose();
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<AlertDialog open={target !== null} onOpenChange={open => !open && onClose()}>
|
|
216
|
+
<AlertDialogContent>
|
|
217
|
+
<AlertDialogHeader>
|
|
218
|
+
<AlertDialogTitle>Transfer ownership?</AlertDialogTitle>
|
|
219
|
+
<AlertDialogDescription>
|
|
220
|
+
{target?.name} will become the owner and gain full control of this cart. You'll keep your current
|
|
221
|
+
role.
|
|
222
|
+
</AlertDialogDescription>
|
|
223
|
+
</AlertDialogHeader>
|
|
224
|
+
<AlertDialogFooter>
|
|
225
|
+
<AlertDialogCancel disabled={isPending}>Cancel</AlertDialogCancel>
|
|
226
|
+
<AlertDialogAction
|
|
227
|
+
onClick={e => {
|
|
228
|
+
e.preventDefault();
|
|
229
|
+
confirm();
|
|
230
|
+
}}
|
|
231
|
+
disabled={isPending}
|
|
232
|
+
>
|
|
233
|
+
{isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
|
234
|
+
Transfer ownership
|
|
235
|
+
</AlertDialogAction>
|
|
236
|
+
</AlertDialogFooter>
|
|
237
|
+
</AlertDialogContent>
|
|
238
|
+
</AlertDialog>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {Metadata} from 'next';
|
|
2
|
+
import {Suspense} from 'react';
|
|
3
|
+
import {client} from '@/lib/vendure/api';
|
|
4
|
+
import {GetSharedCartQuery} from '@/lib/vendure/queries';
|
|
5
|
+
import {getActiveCustomer} from '@/lib/vendure/actions';
|
|
6
|
+
import {redirect, Link} from '@/i18n/navigation';
|
|
7
|
+
import {setRequestLocale} from 'next-intl/server';
|
|
8
|
+
import {ArrowLeft} from 'lucide-react';
|
|
9
|
+
import {SharedCartDetailClient} from './shared-cart-detail-client';
|
|
10
|
+
|
|
11
|
+
export const metadata: Metadata = {
|
|
12
|
+
title: 'Shared Cart',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function SharedCartDetailPage(props: {params: Promise<{locale: string; id: string}>}) {
|
|
16
|
+
return (
|
|
17
|
+
<Suspense fallback={<p className="text-sm text-muted-foreground">Loading shared cart…</p>}>
|
|
18
|
+
<SharedCartDetailContent paramsPromise={props.params} />
|
|
19
|
+
</Suspense>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function SharedCartDetailContent({paramsPromise}: {paramsPromise: Promise<{locale: string; id: string}>}) {
|
|
24
|
+
const {locale, id} = await paramsPromise;
|
|
25
|
+
setRequestLocale(locale);
|
|
26
|
+
|
|
27
|
+
const [activeCustomer, [result, error]] = await Promise.all([
|
|
28
|
+
getActiveCustomer(),
|
|
29
|
+
client.query(GetSharedCartQuery, {id}),
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
if (!activeCustomer) return await redirect('/sign-in');
|
|
33
|
+
if (error || !result.data.sharedCart) return await redirect('/account/shared-carts');
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<Link
|
|
38
|
+
href="/account/shared-carts"
|
|
39
|
+
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground mb-4"
|
|
40
|
+
>
|
|
41
|
+
<ArrowLeft className="h-4 w-4" />
|
|
42
|
+
Shared carts
|
|
43
|
+
</Link>
|
|
44
|
+
<SharedCartDetailClient initialCart={result.data.sharedCart} viewerId={activeCustomer.id} locale={locale} />
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {useState, useTransition} from 'react';
|
|
4
|
+
import {toast} from 'sonner';
|
|
5
|
+
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog';
|
|
6
|
+
import {Button} from '@/components/ui/button';
|
|
7
|
+
import {Input} from '@/components/ui/input';
|
|
8
|
+
import {Textarea} from '@/components/ui/textarea';
|
|
9
|
+
import {Label} from '@/components/ui/label';
|
|
10
|
+
import {Loader2} from 'lucide-react';
|
|
11
|
+
import {renameSharedCart} from '../actions';
|
|
12
|
+
|
|
13
|
+
export function RenameSharedCartDialog({
|
|
14
|
+
open,
|
|
15
|
+
onOpenChange,
|
|
16
|
+
cartId,
|
|
17
|
+
currentName,
|
|
18
|
+
currentDescription,
|
|
19
|
+
onRenamed,
|
|
20
|
+
}: {
|
|
21
|
+
open: boolean;
|
|
22
|
+
onOpenChange: (open: boolean) => void;
|
|
23
|
+
cartId: string;
|
|
24
|
+
currentName: string;
|
|
25
|
+
currentDescription: string;
|
|
26
|
+
onRenamed: () => void;
|
|
27
|
+
}) {
|
|
28
|
+
const [name, setName] = useState(currentName);
|
|
29
|
+
const [description, setDescription] = useState(currentDescription);
|
|
30
|
+
const [wasOpen, setWasOpen] = useState(open);
|
|
31
|
+
const [isPending, startTransition] = useTransition();
|
|
32
|
+
|
|
33
|
+
// Re-seed the fields each time the dialog opens, without an effect (React's
|
|
34
|
+
// "adjust state during render" pattern).
|
|
35
|
+
if (open && !wasOpen) {
|
|
36
|
+
setWasOpen(true);
|
|
37
|
+
setName(currentName);
|
|
38
|
+
setDescription(currentDescription);
|
|
39
|
+
} else if (!open && wasOpen) {
|
|
40
|
+
setWasOpen(false);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function handleSubmit(e: React.FormEvent) {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
const trimmed = name.trim();
|
|
46
|
+
if (!trimmed) return;
|
|
47
|
+
startTransition(async () => {
|
|
48
|
+
const result = await renameSharedCart(cartId, trimmed, description.trim() || undefined);
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
toast.error(result.message ?? 'Could not rename the cart.');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
toast.success('Cart updated.');
|
|
54
|
+
onRenamed();
|
|
55
|
+
onOpenChange(false);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
61
|
+
<DialogContent>
|
|
62
|
+
<form onSubmit={handleSubmit}>
|
|
63
|
+
<DialogHeader>
|
|
64
|
+
<DialogTitle>Rename shared cart</DialogTitle>
|
|
65
|
+
<DialogDescription>Update the name and description your colleagues see.</DialogDescription>
|
|
66
|
+
</DialogHeader>
|
|
67
|
+
<div className="space-y-4 py-4">
|
|
68
|
+
<div className="space-y-2">
|
|
69
|
+
<Label htmlFor="rename-name">Name</Label>
|
|
70
|
+
<Input id="rename-name" value={name} onChange={e => setName(e.target.value)} maxLength={255} required autoFocus />
|
|
71
|
+
</div>
|
|
72
|
+
<div className="space-y-2">
|
|
73
|
+
<Label htmlFor="rename-description">Description</Label>
|
|
74
|
+
<Textarea
|
|
75
|
+
id="rename-description"
|
|
76
|
+
value={description}
|
|
77
|
+
onChange={e => setDescription(e.target.value)}
|
|
78
|
+
rows={3}
|
|
79
|
+
maxLength={1000}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<DialogFooter>
|
|
84
|
+
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)} disabled={isPending}>
|
|
85
|
+
Cancel
|
|
86
|
+
</Button>
|
|
87
|
+
<Button type="submit" disabled={!name.trim() || isPending}>
|
|
88
|
+
{isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
|
89
|
+
Save
|
|
90
|
+
</Button>
|
|
91
|
+
</DialogFooter>
|
|
92
|
+
</form>
|
|
93
|
+
</DialogContent>
|
|
94
|
+
</Dialog>
|
|
95
|
+
);
|
|
96
|
+
}
|