@accounter/client 0.0.8-alpha-20251021150615-800574fc6d416cd319de216c97b431643d8958a2 → 0.0.8-alpha-20251021163440-2ab1a9ffaec95fd99fac5495c3a392b97429ce77
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 +43 -1
- package/dist/assets/index-B2UYAO1O.css +1 -0
- package/dist/assets/index-BexxGuN6.js +1224 -0
- package/dist/assets/{index.es-DHwHzag1.js → index.es-CWwhWGxX.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +6 -5
- package/src/app.tsx +35 -25
- package/src/components/business/business-header.tsx +68 -0
- package/src/components/business/charges-section.tsx +82 -0
- package/src/components/business/charts-section.tsx +115 -0
- package/src/components/business/configurations-section.tsx +885 -0
- package/src/components/business/contact-info-section.tsx +536 -0
- package/src/components/business/contracts-section.tsx +196 -0
- package/src/components/business/documents-section.tsx +26 -0
- package/src/components/business/index.tsx +171 -0
- package/src/components/business/integrations-section.tsx +477 -0
- package/src/components/business/transactions-section.tsx +26 -0
- package/src/components/business-transactions/business-extended-info.tsx +11 -15
- package/src/components/business-transactions/business-transactions-single.tsx +1 -1
- package/src/components/business-transactions/index.tsx +1 -1
- package/src/components/charges/charge-extended-info-menu.tsx +27 -21
- package/src/components/charges/charges-row.tsx +12 -10
- package/src/components/charges/charges-table.tsx +15 -9
- package/src/components/clients/contracts/modify-contract-dialog.tsx +464 -0
- package/src/components/clients/modify-client-dialog.tsx +276 -0
- package/src/components/common/documents/issue-document/index.tsx +3 -3
- package/src/components/common/documents/issue-document/{recent-client-docs.tsx → recent-business-docs.tsx} +19 -13
- package/src/components/common/forms/business-card.tsx +1 -0
- package/src/components/common/forms/modify-business-fields.tsx +2 -19
- package/src/components/common/inputs/combo-box.tsx +1 -1
- package/src/components/layout/sidelinks.tsx +3 -3
- package/src/components/reports/trial-balance-report/trial-balance-report-group.tsx +4 -6
- package/src/components/reports/trial-balance-report/trial-balance-report-sort-code.tsx +8 -11
- package/src/components/screens/businesses/business.tsx +44 -0
- package/src/components/screens/documents/issue-documents/edit-issue-document-modal.tsx +4 -4
- package/src/components/ui/progress.tsx +25 -0
- package/src/components/ui/skeleton.tsx +12 -0
- package/src/gql/gql.ts +93 -9
- package/src/gql/graphql.ts +289 -9
- package/src/helpers/contracts.ts +22 -0
- package/src/helpers/currency.ts +5 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/pcn874.ts +17 -0
- package/src/hooks/use-add-sort-code.ts +1 -1
- package/src/hooks/use-add-tag.ts +1 -1
- package/src/hooks/use-create-contract.ts +62 -0
- package/src/hooks/use-delete-contract.ts +64 -0
- package/src/hooks/use-delete-tag.ts +1 -1
- package/src/hooks/use-get-all-contracts.ts +0 -1
- package/src/hooks/use-insert-client.ts +80 -0
- package/src/hooks/use-merge-businesses.ts +1 -1
- package/src/hooks/use-merge-charges.ts +1 -1
- package/src/hooks/use-update-client.ts +75 -0
- package/src/hooks/use-update-contract.ts +69 -0
- package/dist/assets/index-0eCf1BcD.css +0 -1
- package/dist/assets/index-DHTbHvtz.js +0 -1188
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import { Globe, Mail, MapPin, Phone, Plus, Save, X } from 'lucide-react';
|
|
3
|
+
import { useForm } from 'react-hook-form';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { Badge } from '@/components/ui/badge.js';
|
|
6
|
+
import { Button } from '@/components/ui/button.js';
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardFooter,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from '@/components/ui/card.js';
|
|
15
|
+
import {
|
|
16
|
+
Form,
|
|
17
|
+
FormControl,
|
|
18
|
+
FormField,
|
|
19
|
+
FormItem,
|
|
20
|
+
FormLabel,
|
|
21
|
+
FormMessage,
|
|
22
|
+
} from '@/components/ui/form.js';
|
|
23
|
+
import { Input } from '@/components/ui/input.js';
|
|
24
|
+
import { Textarea } from '@/components/ui/textarea.js';
|
|
25
|
+
import {
|
|
26
|
+
BusinessContactSectionFragmentDoc,
|
|
27
|
+
type BusinessContactSectionFragment,
|
|
28
|
+
type UpdateBusinessInput,
|
|
29
|
+
} from '@/gql/graphql.js';
|
|
30
|
+
import { getFragmentData, type FragmentType } from '@/gql/index.js';
|
|
31
|
+
import { dirtyFieldMarker, relevantDataPicker, type MakeBoolean } from '@/helpers/index.js';
|
|
32
|
+
import { useAllCountries } from '@/hooks/use-get-countries.js';
|
|
33
|
+
import { useUpdateBusiness } from '@/hooks/use-update-business.js';
|
|
34
|
+
import { UserContext } from '@/providers/user-provider.js';
|
|
35
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
36
|
+
import { ComboBox } from '../common';
|
|
37
|
+
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
39
|
+
/* GraphQL */ `
|
|
40
|
+
fragment BusinessContactSection on Business {
|
|
41
|
+
__typename
|
|
42
|
+
id
|
|
43
|
+
... on LtdFinancialEntity {
|
|
44
|
+
name
|
|
45
|
+
hebrewName
|
|
46
|
+
country
|
|
47
|
+
governmentId
|
|
48
|
+
address
|
|
49
|
+
email
|
|
50
|
+
# localAddress
|
|
51
|
+
phoneNumber
|
|
52
|
+
website
|
|
53
|
+
clientInfo {
|
|
54
|
+
id
|
|
55
|
+
emails
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const contactInfoSchema = z.object({
|
|
62
|
+
businessName: z.string().min(1, 'Business name is required'),
|
|
63
|
+
locality: z.string().min(1, 'Locality is required'),
|
|
64
|
+
localName: z.string().optional(),
|
|
65
|
+
govId: z.string().optional(),
|
|
66
|
+
address: z.string().optional(),
|
|
67
|
+
localAddress: z.string().optional(),
|
|
68
|
+
phone: z.string().optional(),
|
|
69
|
+
website: z.url('Invalid URL').optional().or(z.literal('')),
|
|
70
|
+
generalContacts: z.array(z.email()).optional(),
|
|
71
|
+
billingEmails: z.array(z.email()).optional(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
type ContactInfoFormValues = z.infer<typeof contactInfoSchema>;
|
|
75
|
+
|
|
76
|
+
function ContactsSectionFragmentToFormValues(
|
|
77
|
+
business?: BusinessContactSectionFragment,
|
|
78
|
+
): ContactInfoFormValues {
|
|
79
|
+
if (!business || business.__typename !== 'LtdFinancialEntity') {
|
|
80
|
+
return {} as ContactInfoFormValues;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
businessName: business.name,
|
|
85
|
+
locality: business.country,
|
|
86
|
+
localName: business.hebrewName ?? undefined,
|
|
87
|
+
govId: business.governmentId ?? undefined,
|
|
88
|
+
address: business.address ?? undefined,
|
|
89
|
+
// TODO: activate this field later. requires additional backend support
|
|
90
|
+
// localAddress: ,
|
|
91
|
+
phone: business.phoneNumber ?? undefined,
|
|
92
|
+
website: business.website ?? undefined,
|
|
93
|
+
generalContacts: business.email?.split(',').map(email => email.trim()),
|
|
94
|
+
billingEmails: business.clientInfo?.emails,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function convertFormDataToUpdateBusinessInput(
|
|
99
|
+
formData: Partial<ContactInfoFormValues>,
|
|
100
|
+
): UpdateBusinessInput {
|
|
101
|
+
return {
|
|
102
|
+
name: formData.businessName,
|
|
103
|
+
country: formData.locality,
|
|
104
|
+
hebrewName: formData.localName,
|
|
105
|
+
governmentId: formData.govId,
|
|
106
|
+
address: formData.address,
|
|
107
|
+
// localAddress: formData.localAddress,
|
|
108
|
+
phoneNumber: formData.phone,
|
|
109
|
+
website: formData.website,
|
|
110
|
+
email: formData.generalContacts?.join(', '),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface Props {
|
|
115
|
+
data?: FragmentType<typeof BusinessContactSectionFragmentDoc>;
|
|
116
|
+
refetchBusiness?: () => Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function ContactInfoSection({ data, refetchBusiness }: Props) {
|
|
120
|
+
const business = getFragmentData(BusinessContactSectionFragmentDoc, data);
|
|
121
|
+
const [defaultFormValues, setDefaultFormValues] = useState(
|
|
122
|
+
ContactsSectionFragmentToFormValues(business),
|
|
123
|
+
);
|
|
124
|
+
const { userContext } = useContext(UserContext);
|
|
125
|
+
|
|
126
|
+
const { updateBusiness: updateDbBusiness, fetching: isBusinessUpdating } = useUpdateBusiness();
|
|
127
|
+
|
|
128
|
+
const form = useForm<ContactInfoFormValues>({
|
|
129
|
+
resolver: zodResolver(contactInfoSchema),
|
|
130
|
+
defaultValues: defaultFormValues,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// handle countries
|
|
134
|
+
const { countries, fetching: fetchingCountries } = useAllCountries();
|
|
135
|
+
|
|
136
|
+
const [newContact, setNewContact] = useState('');
|
|
137
|
+
const [newBillingEmail, setNewBillingEmail] = useState('');
|
|
138
|
+
|
|
139
|
+
const locality = form.watch('locality');
|
|
140
|
+
const address = form.watch('address');
|
|
141
|
+
const localAddress = form.watch('localAddress');
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (address !== defaultFormValues.address) {
|
|
145
|
+
form.setValue('address', address, { shouldDirty: true });
|
|
146
|
+
}
|
|
147
|
+
}, [address, defaultFormValues.address, form]);
|
|
148
|
+
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (localAddress !== defaultFormValues.localAddress) {
|
|
151
|
+
form.setValue('localAddress', localAddress, { shouldDirty: true });
|
|
152
|
+
}
|
|
153
|
+
}, [localAddress, defaultFormValues.localAddress, form]);
|
|
154
|
+
|
|
155
|
+
const isClient = business && 'clientInfo' in business && !!business.clientInfo;
|
|
156
|
+
const isLocalEntity = locality === userContext?.context.locality;
|
|
157
|
+
|
|
158
|
+
const addGeneralContact = (currentContacts: string[]) => {
|
|
159
|
+
if (newContact.trim()) {
|
|
160
|
+
form.setValue('generalContacts', [...currentContacts, newContact.trim()], {
|
|
161
|
+
shouldDirty: true,
|
|
162
|
+
});
|
|
163
|
+
setNewContact('');
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const removeGeneralContact = (currentContacts: string[], index: number) => {
|
|
168
|
+
form.setValue(
|
|
169
|
+
'generalContacts',
|
|
170
|
+
currentContacts.filter((_, i) => i !== index),
|
|
171
|
+
{ shouldDirty: true },
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const addBillingEmail = (currentEmails: string[]) => {
|
|
176
|
+
if (newBillingEmail.trim()) {
|
|
177
|
+
form.setValue('billingEmails', [...currentEmails, newBillingEmail.trim()], {
|
|
178
|
+
shouldDirty: true,
|
|
179
|
+
});
|
|
180
|
+
setNewBillingEmail('');
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const removeBillingEmail = (currentEmails: string[], index: number) => {
|
|
185
|
+
form.setValue(
|
|
186
|
+
'billingEmails',
|
|
187
|
+
currentEmails.filter((_, i) => i !== index),
|
|
188
|
+
{ shouldDirty: true },
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const onSubmit = async (data: ContactInfoFormValues) => {
|
|
193
|
+
if (!business || !userContext?.context.adminBusinessId) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const dataToUpdate = relevantDataPicker(
|
|
198
|
+
data,
|
|
199
|
+
form.formState.dirtyFields as MakeBoolean<typeof data>,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
if (!dataToUpdate) return;
|
|
203
|
+
|
|
204
|
+
const updateBusinessInput = convertFormDataToUpdateBusinessInput(dataToUpdate);
|
|
205
|
+
|
|
206
|
+
await updateDbBusiness({
|
|
207
|
+
businessId: business.id,
|
|
208
|
+
ownerId: userContext.context.adminBusinessId,
|
|
209
|
+
fields: updateBusinessInput,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
refetchBusiness?.();
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
if (business) {
|
|
217
|
+
const formValues = ContactsSectionFragmentToFormValues(business);
|
|
218
|
+
setDefaultFormValues(formValues);
|
|
219
|
+
form.reset(formValues);
|
|
220
|
+
}
|
|
221
|
+
}, [business, form]);
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Card>
|
|
225
|
+
<CardHeader>
|
|
226
|
+
<CardTitle>Contact Information</CardTitle>
|
|
227
|
+
<CardDescription>Business contact details and address information</CardDescription>
|
|
228
|
+
</CardHeader>
|
|
229
|
+
<Form {...form}>
|
|
230
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
231
|
+
<CardContent>
|
|
232
|
+
<div className="grid gap-6 md:grid-cols-2">
|
|
233
|
+
<FormField
|
|
234
|
+
control={form.control}
|
|
235
|
+
name="businessName"
|
|
236
|
+
render={({ field, fieldState }) => (
|
|
237
|
+
<FormItem>
|
|
238
|
+
<FormLabel>Business Name</FormLabel>
|
|
239
|
+
<FormControl>
|
|
240
|
+
<Input {...field} className={dirtyFieldMarker(fieldState)} />
|
|
241
|
+
</FormControl>
|
|
242
|
+
<FormMessage />
|
|
243
|
+
</FormItem>
|
|
244
|
+
)}
|
|
245
|
+
/>
|
|
246
|
+
|
|
247
|
+
<FormField
|
|
248
|
+
control={form.control}
|
|
249
|
+
name="locality"
|
|
250
|
+
render={({ field, fieldState }) => (
|
|
251
|
+
<FormItem>
|
|
252
|
+
<FormLabel>Locality / Country</FormLabel>
|
|
253
|
+
<ComboBox
|
|
254
|
+
onChange={field.onChange}
|
|
255
|
+
data={countries.map(country => ({
|
|
256
|
+
value: country.code,
|
|
257
|
+
label: country.name,
|
|
258
|
+
}))}
|
|
259
|
+
value={field.value}
|
|
260
|
+
disabled={fetchingCountries}
|
|
261
|
+
placeholder="Scroll to see all options"
|
|
262
|
+
formPart
|
|
263
|
+
triggerProps={{
|
|
264
|
+
className: dirtyFieldMarker(fieldState),
|
|
265
|
+
}}
|
|
266
|
+
/>
|
|
267
|
+
<FormMessage />
|
|
268
|
+
</FormItem>
|
|
269
|
+
)}
|
|
270
|
+
/>
|
|
271
|
+
|
|
272
|
+
{isLocalEntity && (
|
|
273
|
+
<FormField
|
|
274
|
+
control={form.control}
|
|
275
|
+
name="localName"
|
|
276
|
+
render={({ field, fieldState }) => (
|
|
277
|
+
<FormItem>
|
|
278
|
+
<FormLabel>Local Name</FormLabel>
|
|
279
|
+
<FormControl>
|
|
280
|
+
<Input
|
|
281
|
+
{...field}
|
|
282
|
+
placeholder="Business name in local language"
|
|
283
|
+
className={dirtyFieldMarker(fieldState)}
|
|
284
|
+
/>
|
|
285
|
+
</FormControl>
|
|
286
|
+
<FormMessage />
|
|
287
|
+
</FormItem>
|
|
288
|
+
)}
|
|
289
|
+
/>
|
|
290
|
+
)}
|
|
291
|
+
|
|
292
|
+
{isLocalEntity && (
|
|
293
|
+
<FormField
|
|
294
|
+
control={form.control}
|
|
295
|
+
name="govId"
|
|
296
|
+
render={({ field, fieldState }) => (
|
|
297
|
+
<FormItem>
|
|
298
|
+
<FormLabel>Government ID</FormLabel>
|
|
299
|
+
<FormControl>
|
|
300
|
+
<Input
|
|
301
|
+
{...field}
|
|
302
|
+
placeholder="Enter Government ID"
|
|
303
|
+
className={dirtyFieldMarker(fieldState)}
|
|
304
|
+
/>
|
|
305
|
+
</FormControl>
|
|
306
|
+
<FormMessage />
|
|
307
|
+
</FormItem>
|
|
308
|
+
)}
|
|
309
|
+
/>
|
|
310
|
+
)}
|
|
311
|
+
|
|
312
|
+
<FormField
|
|
313
|
+
control={form.control}
|
|
314
|
+
name="address"
|
|
315
|
+
render={({ field, fieldState }) => (
|
|
316
|
+
<FormItem className="md:col-span-2">
|
|
317
|
+
<FormLabel className="flex items-center gap-2">
|
|
318
|
+
<MapPin className="h-4 w-4" />
|
|
319
|
+
Address
|
|
320
|
+
</FormLabel>
|
|
321
|
+
<FormControl>
|
|
322
|
+
<Textarea
|
|
323
|
+
{...field}
|
|
324
|
+
rows={3}
|
|
325
|
+
placeholder="Enter business address"
|
|
326
|
+
className={dirtyFieldMarker(fieldState)}
|
|
327
|
+
/>
|
|
328
|
+
</FormControl>
|
|
329
|
+
<FormMessage />
|
|
330
|
+
</FormItem>
|
|
331
|
+
)}
|
|
332
|
+
/>
|
|
333
|
+
|
|
334
|
+
{isLocalEntity && (
|
|
335
|
+
<FormField
|
|
336
|
+
control={form.control}
|
|
337
|
+
name="localAddress"
|
|
338
|
+
render={({ field, fieldState }) => (
|
|
339
|
+
<FormItem className="md:col-span-2">
|
|
340
|
+
<FormLabel className="flex items-center gap-2">
|
|
341
|
+
<MapPin className="h-4 w-4" />
|
|
342
|
+
Local Address
|
|
343
|
+
</FormLabel>
|
|
344
|
+
<FormControl>
|
|
345
|
+
<Textarea
|
|
346
|
+
{...field}
|
|
347
|
+
rows={3}
|
|
348
|
+
placeholder="Enter address in local language"
|
|
349
|
+
className={dirtyFieldMarker(fieldState)}
|
|
350
|
+
/>
|
|
351
|
+
</FormControl>
|
|
352
|
+
<FormMessage />
|
|
353
|
+
</FormItem>
|
|
354
|
+
)}
|
|
355
|
+
/>
|
|
356
|
+
)}
|
|
357
|
+
|
|
358
|
+
<FormField
|
|
359
|
+
control={form.control}
|
|
360
|
+
name="phone"
|
|
361
|
+
render={({ field, fieldState }) => (
|
|
362
|
+
<FormItem>
|
|
363
|
+
<FormLabel className="flex items-center gap-2">
|
|
364
|
+
<Phone className="h-4 w-4" />
|
|
365
|
+
Phone
|
|
366
|
+
</FormLabel>
|
|
367
|
+
<FormControl>
|
|
368
|
+
<Input
|
|
369
|
+
{...field}
|
|
370
|
+
type="tel"
|
|
371
|
+
placeholder="+1 (555) 123-4567"
|
|
372
|
+
className={dirtyFieldMarker(fieldState)}
|
|
373
|
+
/>
|
|
374
|
+
</FormControl>
|
|
375
|
+
<FormMessage />
|
|
376
|
+
</FormItem>
|
|
377
|
+
)}
|
|
378
|
+
/>
|
|
379
|
+
|
|
380
|
+
<FormField
|
|
381
|
+
control={form.control}
|
|
382
|
+
name="website"
|
|
383
|
+
render={({ field, fieldState }) => (
|
|
384
|
+
<FormItem>
|
|
385
|
+
<FormLabel className="flex items-center gap-2">
|
|
386
|
+
<Globe className="h-4 w-4" />
|
|
387
|
+
Website
|
|
388
|
+
</FormLabel>
|
|
389
|
+
<FormControl>
|
|
390
|
+
<Input
|
|
391
|
+
{...field}
|
|
392
|
+
type="url"
|
|
393
|
+
placeholder="https://example.com"
|
|
394
|
+
className={dirtyFieldMarker(fieldState)}
|
|
395
|
+
/>
|
|
396
|
+
</FormControl>
|
|
397
|
+
<FormMessage />
|
|
398
|
+
</FormItem>
|
|
399
|
+
)}
|
|
400
|
+
/>
|
|
401
|
+
|
|
402
|
+
<FormField
|
|
403
|
+
control={form.control}
|
|
404
|
+
name="generalContacts"
|
|
405
|
+
render={({ field, fieldState }) => (
|
|
406
|
+
<FormItem className="md:col-span-2">
|
|
407
|
+
<FormLabel className="flex items-center gap-2">
|
|
408
|
+
<Mail className="h-4 w-4" />
|
|
409
|
+
General Contacts
|
|
410
|
+
</FormLabel>
|
|
411
|
+
<FormControl>
|
|
412
|
+
<div className="space-y-2">
|
|
413
|
+
<div
|
|
414
|
+
className={
|
|
415
|
+
dirtyFieldMarker(fieldState) + ' flex flex-wrap gap-2 mb-2 rounded-md'
|
|
416
|
+
}
|
|
417
|
+
>
|
|
418
|
+
{field.value?.map((contact, index) => (
|
|
419
|
+
<Badge key={index} variant="secondary" className="gap-1 pr-1">
|
|
420
|
+
{contact}
|
|
421
|
+
<button
|
|
422
|
+
type="button"
|
|
423
|
+
onClick={() => removeGeneralContact(field.value ?? [], index)}
|
|
424
|
+
className="ml-1 hover:bg-muted rounded-sm p-0.5"
|
|
425
|
+
>
|
|
426
|
+
<X className="h-3 w-3" />
|
|
427
|
+
</button>
|
|
428
|
+
</Badge>
|
|
429
|
+
))}
|
|
430
|
+
</div>
|
|
431
|
+
<div className="flex gap-2">
|
|
432
|
+
<Input
|
|
433
|
+
value={newContact}
|
|
434
|
+
onChange={e => setNewContact(e.target.value)}
|
|
435
|
+
onKeyDown={e => {
|
|
436
|
+
if (e.key === 'Enter') {
|
|
437
|
+
e.preventDefault();
|
|
438
|
+
addGeneralContact(field.value ?? []);
|
|
439
|
+
}
|
|
440
|
+
}}
|
|
441
|
+
placeholder="Add contact email"
|
|
442
|
+
type="email"
|
|
443
|
+
/>
|
|
444
|
+
<Button
|
|
445
|
+
type="button"
|
|
446
|
+
variant="outline"
|
|
447
|
+
size="icon"
|
|
448
|
+
disabled={!newContact.trim()}
|
|
449
|
+
onClick={() => addGeneralContact(field.value ?? [])}
|
|
450
|
+
>
|
|
451
|
+
<Plus className="h-4 w-4" />
|
|
452
|
+
</Button>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
</FormControl>
|
|
456
|
+
<FormMessage />
|
|
457
|
+
</FormItem>
|
|
458
|
+
)}
|
|
459
|
+
/>
|
|
460
|
+
|
|
461
|
+
{isClient && (
|
|
462
|
+
<FormField
|
|
463
|
+
control={form.control}
|
|
464
|
+
name="billingEmails"
|
|
465
|
+
render={({ field, fieldState }) => (
|
|
466
|
+
<FormItem className="md:col-span-2">
|
|
467
|
+
<FormLabel className="flex items-center gap-2">
|
|
468
|
+
<Mail className="h-4 w-4" />
|
|
469
|
+
Billing Emails
|
|
470
|
+
</FormLabel>
|
|
471
|
+
<FormControl>
|
|
472
|
+
<div className="space-y-2">
|
|
473
|
+
<div
|
|
474
|
+
className={
|
|
475
|
+
dirtyFieldMarker(fieldState) + ' flex flex-wrap gap-2 mb-2 rounded-md'
|
|
476
|
+
}
|
|
477
|
+
>
|
|
478
|
+
{field.value?.map((email, index) => (
|
|
479
|
+
<Badge key={index} variant="secondary" className="gap-1 pr-1">
|
|
480
|
+
{email}
|
|
481
|
+
<button
|
|
482
|
+
type="button"
|
|
483
|
+
onClick={() => removeBillingEmail(field.value ?? [], index)}
|
|
484
|
+
className="ml-1 hover:bg-muted rounded-sm p-0.5"
|
|
485
|
+
>
|
|
486
|
+
<X className="h-3 w-3" />
|
|
487
|
+
</button>
|
|
488
|
+
</Badge>
|
|
489
|
+
))}
|
|
490
|
+
</div>
|
|
491
|
+
<div className="flex gap-2">
|
|
492
|
+
<Input
|
|
493
|
+
value={newBillingEmail}
|
|
494
|
+
onChange={e => setNewBillingEmail(e.target.value)}
|
|
495
|
+
onKeyDown={e => {
|
|
496
|
+
if (e.key === 'Enter') {
|
|
497
|
+
e.preventDefault();
|
|
498
|
+
addBillingEmail(field.value ?? []);
|
|
499
|
+
}
|
|
500
|
+
}}
|
|
501
|
+
placeholder="Add billing email"
|
|
502
|
+
type="email"
|
|
503
|
+
/>
|
|
504
|
+
<Button
|
|
505
|
+
type="button"
|
|
506
|
+
variant="outline"
|
|
507
|
+
size="icon"
|
|
508
|
+
disabled={!newBillingEmail.trim()}
|
|
509
|
+
onClick={() => addBillingEmail(field.value ?? [])}
|
|
510
|
+
>
|
|
511
|
+
<Plus className="h-4 w-4" />
|
|
512
|
+
</Button>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
</FormControl>
|
|
516
|
+
<FormMessage />
|
|
517
|
+
</FormItem>
|
|
518
|
+
)}
|
|
519
|
+
/>
|
|
520
|
+
)}
|
|
521
|
+
</div>
|
|
522
|
+
</CardContent>
|
|
523
|
+
<CardFooter className="flex justify-end border-t mt-4 pt-6">
|
|
524
|
+
<Button
|
|
525
|
+
type="submit"
|
|
526
|
+
disabled={isBusinessUpdating || Object.keys(form.formState.dirtyFields).length === 0}
|
|
527
|
+
>
|
|
528
|
+
<Save className="h-4 w-4 mr-2" />
|
|
529
|
+
Save Changes
|
|
530
|
+
</Button>
|
|
531
|
+
</CardFooter>
|
|
532
|
+
</form>
|
|
533
|
+
</Form>
|
|
534
|
+
</Card>
|
|
535
|
+
);
|
|
536
|
+
}
|