@visns-studio/visns-components 6.32.3 → 6.34.3
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 +483 -0
- package/package.json +1 -1
- package/src/components/DataGrid.jsx +44 -2
- package/src/components/Field.jsx +32 -0
- package/src/components/Form.jsx +39 -2
- package/src/components/Navigation.jsx +274 -15
- package/src/components/Notification.jsx +13 -2
- package/src/components/TableFilter.jsx +14 -2
- package/src/components/auth/Login.jsx +11 -4
- package/src/components/auth/PasskeyEnrolPrompt.jsx +269 -0
- package/src/components/auth/Profile.jsx +422 -26
- package/src/components/auth/Reset.jsx +1 -1
- package/src/components/auth/TwoFactorAuth.jsx +1 -1
- package/src/components/auth/Verify.jsx +2 -2
- package/src/components/auth/authEndpoints.js +43 -0
- package/src/components/auth/passkeyClient.js +102 -0
- package/src/components/auth/passkeyPrompt.js +408 -0
- package/src/components/auth/profileLayout.js +45 -0
- package/src/components/controls/DataGridSearch.jsx +16 -2
- package/src/components/controls/DataGridSortSheet.jsx +2 -0
- package/src/components/emailCampaigns/BlockEditor.jsx +486 -0
- package/src/components/emailCampaigns/CampaignEditor.jsx +614 -0
- package/src/components/emailCampaigns/CampaignReport.jsx +98 -0
- package/src/components/emailCampaigns/EmailCampaigns.jsx +422 -0
- package/src/components/emailCampaigns/EmailLists.jsx +505 -0
- package/src/components/emailCampaigns/emailCampaignApi.js +135 -0
- package/src/components/generic/ActionButtons.jsx +1 -1
- package/src/components/generic/GenericAuth.jsx +197 -21
- package/src/components/generic/GenericDetail.jsx +95 -3
- package/src/components/generic/GenericMain.jsx +7 -0
- package/src/components/generic/StandardModal.jsx +71 -158
- package/src/components/sms/SmsCampaigns.jsx +2517 -0
- package/src/components/sms/SmsInbox.jsx +20 -5
- package/src/components/sms/SmsLineSettings.jsx +2 -5
- package/src/components/sms/SmsThreadPanel.jsx +20 -0
- package/src/components/sms/smsEndpoints.js +23 -0
- package/src/components/styles/DataGrid.module.scss +22 -0
- package/src/components/styles/EmailCampaigns.module.scss +922 -0
- package/src/components/styles/Form.module.scss +100 -2
- package/src/components/styles/Navigation.module.scss +322 -5
- package/src/components/styles/Notification.module.scss +140 -8
- package/src/components/styles/PasskeyEnrolPrompt.module.scss +105 -0
- package/src/components/styles/Profile.module.scss +157 -0
- package/src/components/styles/Sms.module.scss +484 -4
- package/src/components/styles/StandardModal.module.scss +273 -0
- package/src/components/styles/Vault.module.scss +5 -3
- package/src/components/styles/global.css +38 -7
- package/src/components/utils/navCollapsed.js +201 -0
- package/src/components/utils/rowActionSettings.js +76 -0
- package/src/components/utils/usePasskeysEnabled.js +84 -0
- package/src/components/utils/useShellLayout.js +131 -0
- package/src/index.js +107 -1
- package/src/utils/rememberTab.js +70 -0
|
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
2
2
|
import {
|
|
3
3
|
Inbox,
|
|
4
4
|
Loader2,
|
|
5
|
-
|
|
5
|
+
Megaphone,
|
|
6
6
|
PenSquare,
|
|
7
7
|
Unplug,
|
|
8
8
|
Search,
|
|
@@ -103,6 +103,13 @@ const SmsInboxInner = ({
|
|
|
103
103
|
echo = null,
|
|
104
104
|
channelFor = defaultChannelFor,
|
|
105
105
|
settingsUrl = '/settings/sms',
|
|
106
|
+
// Opt-in, and absent by default: a deployment with no campaigns screen
|
|
107
|
+
// must not grow a link to one. Drawn beside the settings link rather than
|
|
108
|
+
// beside "New message", because a campaign is somewhere you GO and a new
|
|
109
|
+
// message is something you DO — and it is deliberately a ghost button
|
|
110
|
+
// rather than a second primary, since the inbox's own primary action is
|
|
111
|
+
// still writing to one person.
|
|
112
|
+
campaignsUrl = null,
|
|
106
113
|
clientUrl = (id) => `/clients/${id}`,
|
|
107
114
|
// Both opt-in, both handed straight to the conversation pane — see
|
|
108
115
|
// SmsThreadPanel for the shapes. Absent, this page is exactly what it was.
|
|
@@ -276,10 +283,8 @@ const SmsInboxInner = ({
|
|
|
276
283
|
<div className={styles.inbox}>
|
|
277
284
|
<div className={styles.inboxHead}>
|
|
278
285
|
<div className={styles.inboxHeading}>
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
{pageTitle}
|
|
282
|
-
</h1>
|
|
286
|
+
{/* No icon: no other page title in a host CRM carries one. */}
|
|
287
|
+
<h1 className={styles.pageTitle}>{pageTitle}</h1>
|
|
283
288
|
<p className={styles.pageIntro}>
|
|
284
289
|
Text messages sent and received on the practice's phone lines.
|
|
285
290
|
Replies come back to the line they were sent from.
|
|
@@ -301,6 +306,16 @@ const SmsInboxInner = ({
|
|
|
301
306
|
</div>
|
|
302
307
|
|
|
303
308
|
<div className={styles.headActions}>
|
|
309
|
+
{campaignsUrl && (
|
|
310
|
+
<button
|
|
311
|
+
type="button"
|
|
312
|
+
className={styles.ghostButton}
|
|
313
|
+
onClick={() => navigate(campaignsUrl)}
|
|
314
|
+
>
|
|
315
|
+
<Megaphone size={14} strokeWidth={2} aria-hidden="true" />
|
|
316
|
+
Campaigns
|
|
317
|
+
</button>
|
|
318
|
+
)}
|
|
304
319
|
{canManage && (
|
|
305
320
|
<button
|
|
306
321
|
type="button"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { Loader2,
|
|
2
|
+
import { Loader2, Plus, TriangleAlert, X } from 'lucide-react';
|
|
3
3
|
import { toast } from 'react-toastify';
|
|
4
4
|
|
|
5
5
|
import CustomFetch from '../Fetch';
|
|
@@ -264,10 +264,7 @@ const SmsLineSettings = ({
|
|
|
264
264
|
{pageTitle !== null && (
|
|
265
265
|
<div className={styles.settingsHead}>
|
|
266
266
|
<div>
|
|
267
|
-
<h1 className={styles.pageTitle}>
|
|
268
|
-
<MessageSquare size={22} strokeWidth={2.2} aria-hidden="true" />
|
|
269
|
-
{pageTitle}
|
|
270
|
-
</h1>
|
|
267
|
+
<h1 className={styles.pageTitle}>{pageTitle}</h1>
|
|
271
268
|
<p className={styles.pageIntro}>
|
|
272
269
|
A line is one Zoom Phone number and the staff who can see its
|
|
273
270
|
conversations. Everything staff send goes out from the line they
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ArrowLeft,
|
|
13
13
|
CheckSquare,
|
|
14
14
|
ChevronUp,
|
|
15
|
+
CircleSlash,
|
|
15
16
|
ExternalLink,
|
|
16
17
|
FileText,
|
|
17
18
|
Link2,
|
|
@@ -1054,6 +1055,25 @@ const SmsThreadPanel = ({
|
|
|
1054
1055
|
</div>
|
|
1055
1056
|
) : (
|
|
1056
1057
|
<div className={styles.composer}>
|
|
1058
|
+
{/*
|
|
1059
|
+
* Opted out of CAMPAIGNS, which is not the same thing as
|
|
1060
|
+
* opted out of being spoken to.
|
|
1061
|
+
*
|
|
1062
|
+
* Nothing is disabled and nothing should be: this person
|
|
1063
|
+
* texted us, and refusing to let a member of staff answer a
|
|
1064
|
+
* question because a bulk send would skip them is the wrong
|
|
1065
|
+
* answer to the wrong question. What the line does is say so,
|
|
1066
|
+
* once, in the muted register — so a reply here is a decision
|
|
1067
|
+
* somebody made rather than one they made unaware.
|
|
1068
|
+
*/}
|
|
1069
|
+
{thread?.opted_out && (
|
|
1070
|
+
<p className={styles.optedOutNote}>
|
|
1071
|
+
<CircleSlash size={13} strokeWidth={2} aria-hidden="true" />
|
|
1072
|
+
This number has opted out of messages. A reply is still allowed,
|
|
1073
|
+
but Zoom blocks texts to them until they reply START.
|
|
1074
|
+
</p>
|
|
1075
|
+
)}
|
|
1076
|
+
|
|
1057
1077
|
<div className={styles.composerBox}>
|
|
1058
1078
|
<textarea
|
|
1059
1079
|
ref={composerRef}
|
|
@@ -36,6 +36,29 @@ export const makeSmsEndpoints = (base = '/ajax/sms') => ({
|
|
|
36
36
|
templates: `${base}/templates`,
|
|
37
37
|
template: (id) => `${base}/templates/${id}`,
|
|
38
38
|
|
|
39
|
+
// Bulk sends. `campaigns` is both the list and the create; everything else
|
|
40
|
+
// hangs off one campaign's id.
|
|
41
|
+
//
|
|
42
|
+
// `campaignPreview` is a POST that only reads, and it is a route rather
|
|
43
|
+
// than a client-side render on purpose: the server owns what `{first_name}`
|
|
44
|
+
// means, and a composer that filled the tokens itself would show a message
|
|
45
|
+
// that differed from the one that went out on any placeholder the two
|
|
46
|
+
// implementations disagreed about.
|
|
47
|
+
campaigns: `${base}/campaigns`,
|
|
48
|
+
campaignPreview: `${base}/campaigns/preview`,
|
|
49
|
+
campaign: (id) => `${base}/campaigns/${id}`,
|
|
50
|
+
campaignRecipients: (id) => `${base}/campaigns/${id}/recipients`,
|
|
51
|
+
campaignStart: (id) => `${base}/campaigns/${id}/start`,
|
|
52
|
+
campaignPause: (id) => `${base}/campaigns/${id}/pause`,
|
|
53
|
+
campaignCancel: (id) => `${base}/campaigns/${id}/cancel`,
|
|
54
|
+
campaignRetryFailed: (id) => `${base}/campaigns/${id}/retry-failed`,
|
|
55
|
+
|
|
56
|
+
// Numbers that must not be sent to again. Kept beside campaigns because a
|
|
57
|
+
// bulk send is the only place the list is applied in anger, but an opt-out
|
|
58
|
+
// is a fact about the number and outlives every campaign that honoured it.
|
|
59
|
+
optOuts: `${base}/opt-outs`,
|
|
60
|
+
optOut: (id) => `${base}/opt-outs/${id}`,
|
|
61
|
+
|
|
39
62
|
settingsLines: `${base}/settings/lines`,
|
|
40
63
|
settingsLine: (id) => `${base}/settings/lines/${id}`,
|
|
41
64
|
});
|
|
@@ -478,6 +478,28 @@
|
|
|
478
478
|
}
|
|
479
479
|
}
|
|
480
480
|
|
|
481
|
+
/* A row action that is PRESENT but not permitted on this row — `disabled` on a
|
|
482
|
+
settings entry (see resolveRowActionDisabled in utils/rowActionSettings.js).
|
|
483
|
+
The icon stays in the cell so the column does not change shape from row to
|
|
484
|
+
row; it simply stops being an offer.
|
|
485
|
+
|
|
486
|
+
POINTER EVENTS ARE NOT TURNED OFF, and that is deliberate rather than an
|
|
487
|
+
omission: the whole value of this over `condition` is the tooltip that says
|
|
488
|
+
why, and a glyph with `pointer-events: none` can never be hovered to show
|
|
489
|
+
one. The click is swallowed in JS instead, where it can also be stopped from
|
|
490
|
+
reaching the row underneath. */
|
|
491
|
+
.vs-action--disabled {
|
|
492
|
+
cursor: not-allowed;
|
|
493
|
+
opacity: 0.38;
|
|
494
|
+
|
|
495
|
+
/* The hover tint below would otherwise say the icon is still live. */
|
|
496
|
+
.tdaction,
|
|
497
|
+
.tdaction:hover {
|
|
498
|
+
cursor: not-allowed;
|
|
499
|
+
color: #6b7280;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
481
503
|
.uploadSection {
|
|
482
504
|
margin-bottom: 16px;
|
|
483
505
|
border-bottom: 1px solid #e5e7eb;
|