@startsimpli/ui 0.4.14 → 0.4.16
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 +457 -398
- package/package.json +18 -13
- package/src/components/__tests__/calendar-view-popup.test.tsx +42 -0
- package/src/components/__tests__/chat.test.tsx +129 -0
- package/src/components/__tests__/meetings-list.test.tsx +114 -0
- package/src/components/__tests__/slide-deck-viewer.test.tsx +82 -0
- package/src/components/__tests__/workspace.test.tsx +106 -0
- package/src/components/account/__tests__/account.test.tsx +5 -32
- package/src/components/account/change-password-form.tsx +1 -28
- package/src/components/calendar/calendar-view.tsx +31 -0
- package/src/components/calendar/index.ts +7 -0
- package/src/components/calendar/meetings-list.tsx +202 -0
- package/src/components/calendar/upcoming-meetings.tsx +5 -5
- package/src/components/chat/ChatComposer.tsx +113 -0
- package/src/components/chat/ChatMessage.tsx +81 -0
- package/src/components/chat/ChatThread.tsx +57 -0
- package/src/components/chat/index.ts +12 -0
- package/src/components/chat/types.ts +20 -0
- package/src/components/index.ts +13 -0
- package/src/components/slide-deck/SlideCanvas.tsx +68 -0
- package/src/components/slide-deck/SlideDeckViewer.tsx +144 -0
- package/src/components/slide-deck/SlideFilmstrip.tsx +73 -0
- package/src/components/slide-deck/index.ts +7 -0
- package/src/components/slide-deck/types.ts +18 -0
- package/src/components/team/DomainClaimCard.tsx +170 -0
- package/src/components/team/InviteMemberDialog.tsx +182 -0
- package/src/components/team/LeaveTeamDialog.tsx +130 -0
- package/src/components/team/MembersTable.tsx +138 -0
- package/src/components/team/OrgSwitcher.tsx +68 -0
- package/src/components/team/PendingInvitationCallout.tsx +106 -0
- package/src/components/team/RoleSelector.tsx +68 -0
- package/src/components/team/__tests__/team-components.test.tsx +352 -0
- package/src/components/team/__tests__/team-settings-page.test.tsx +146 -0
- package/src/components/team/domain-claim-card-default-class-names.ts +45 -0
- package/src/components/team/index.ts +62 -0
- package/src/components/team/invite-member-dialog-default-class-names.ts +41 -0
- package/src/components/team/leave-team-dialog-default-class-names.ts +33 -0
- package/src/components/team/members-table-default-class-names.ts +39 -0
- package/src/components/team/org-switcher-default-class-names.ts +13 -0
- package/src/components/team/pages/DomainsSettingsPage.tsx +289 -0
- package/src/components/team/pages/TeamSettingsPage.tsx +423 -0
- package/src/components/team/pages/domains-settings-page-default-class-names.ts +89 -0
- package/src/components/team/pages/index.ts +33 -0
- package/src/components/team/pages/team-settings-page-default-class-names.ts +116 -0
- package/src/components/team/pages/types.ts +135 -0
- package/src/components/team/pending-invitation-callout-default-class-names.ts +22 -0
- package/src/components/team/role-selector-default-class-names.ts +11 -0
- package/src/components/team/types.ts +97 -0
- package/src/components/workflows/ExecNodeDetails.tsx +83 -0
- package/src/components/workflows/ExecutionTimeline.tsx +146 -0
- package/src/components/workflows/NodeInspector.tsx +257 -0
- package/src/components/workflows/NodePalette.tsx +119 -0
- package/src/components/workflows/WorkflowCanvas.tsx +113 -0
- package/src/components/workflows/WorkflowEdge.tsx +65 -0
- package/src/components/workflows/WorkflowEditor.tsx +130 -0
- package/src/components/workflows/WorkflowNode.tsx +198 -0
- package/src/components/workflows/WorkflowRunViewer.tsx +81 -0
- package/src/components/workflows/__tests__/ExecutionTimeline.test.tsx +99 -0
- package/src/components/workflows/__tests__/NodeInspector.test.tsx +74 -0
- package/src/components/workflows/__tests__/NodePalette.test.tsx +46 -0
- package/src/components/workflows/__tests__/WorkflowCanvas.test.tsx +59 -0
- package/src/components/workflows/__tests__/WorkflowNode.test.tsx +92 -0
- package/src/components/workflows/__tests__/WorkflowRunViewer.test.tsx +138 -0
- package/src/components/workflows/__tests__/auto-layout.test.ts +107 -0
- package/src/components/workflows/__tests__/serialization.test.ts +278 -0
- package/src/components/workflows/exec-status.ts +90 -0
- package/src/components/workflows/hooks/useCanvasGraph.ts +70 -0
- package/src/components/workflows/hooks/useNodeStatusOverlay.ts +47 -0
- package/src/components/workflows/index.ts +78 -0
- package/src/components/workflows/layout/auto-layout.ts +142 -0
- package/src/components/workflows/node-icons.ts +31 -0
- package/src/components/workflows/serialization.ts +171 -0
- package/src/components/workflows/theme/categories.ts +96 -0
- package/src/components/workflows/types.ts +231 -0
- package/src/components/workflows/workflows.css +29 -0
- package/src/components/workspace/DualPaneWorkspace.tsx +187 -0
- package/src/components/workspace/SplitPane.tsx +174 -0
- package/src/components/workspace/index.ts +4 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Default Tailwind classes for {@link MembersTable}. startsim-o7s. */
|
|
2
|
+
export interface MembersTableClassNames {
|
|
3
|
+
root?: string
|
|
4
|
+
table?: string
|
|
5
|
+
thead?: string
|
|
6
|
+
headerRow?: string
|
|
7
|
+
th?: string
|
|
8
|
+
tbody?: string
|
|
9
|
+
row?: string
|
|
10
|
+
cell?: string
|
|
11
|
+
avatar?: string
|
|
12
|
+
name?: string
|
|
13
|
+
email?: string
|
|
14
|
+
roleBadge?: string
|
|
15
|
+
actions?: string
|
|
16
|
+
removeButton?: string
|
|
17
|
+
emptyRow?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const MEMBERS_TABLE_DEFAULTS: Required<MembersTableClassNames> = {
|
|
21
|
+
root: 'w-full overflow-x-auto rounded-lg border border-gray-200',
|
|
22
|
+
table: 'w-full text-sm text-left',
|
|
23
|
+
thead: 'bg-gray-50 text-xs uppercase tracking-wide text-gray-500',
|
|
24
|
+
headerRow: '',
|
|
25
|
+
th: 'px-4 py-2 font-medium',
|
|
26
|
+
tbody: 'divide-y divide-gray-100 bg-white',
|
|
27
|
+
row: 'hover:bg-gray-50',
|
|
28
|
+
cell: 'px-4 py-3 align-middle',
|
|
29
|
+
avatar:
|
|
30
|
+
'flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-xs font-medium text-gray-600',
|
|
31
|
+
name: 'font-medium text-gray-900',
|
|
32
|
+
email: 'text-gray-500',
|
|
33
|
+
roleBadge:
|
|
34
|
+
'inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700',
|
|
35
|
+
actions: 'flex items-center justify-end gap-2',
|
|
36
|
+
removeButton:
|
|
37
|
+
'rounded-md border border-gray-300 px-2 py-1 text-xs text-gray-700 hover:bg-gray-100 disabled:opacity-50',
|
|
38
|
+
emptyRow: 'px-4 py-6 text-center text-sm text-gray-500',
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Default Tailwind classes for {@link OrgSwitcher}. startsim-o7s. */
|
|
2
|
+
export interface OrgSwitcherClassNames {
|
|
3
|
+
root?: string
|
|
4
|
+
label?: string
|
|
5
|
+
select?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const ORG_SWITCHER_DEFAULTS: Required<OrgSwitcherClassNames> = {
|
|
9
|
+
root: 'inline-flex items-center gap-2',
|
|
10
|
+
label: 'text-xs font-medium uppercase tracking-wide text-gray-500',
|
|
11
|
+
select:
|
|
12
|
+
'rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-900 outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500',
|
|
13
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DomainsSettingsPage — shared composer for /settings/domains (startsim-o7s).
|
|
5
|
+
*
|
|
6
|
+
* Renders the list of EmailDomainClaim rows for the active company, the
|
|
7
|
+
* add-domain form, and the per-card verify-DNS / verify-email actions.
|
|
8
|
+
* Admin gating is read off useMembership; only admins see Add + Revoke.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as React from 'react'
|
|
12
|
+
import { useDomainClaims, useMembershipFromApi } from '@startsimpli/auth'
|
|
13
|
+
import type { DomainClaimRow } from '@startsimpli/auth'
|
|
14
|
+
import { DomainClaimCard } from '../DomainClaimCard'
|
|
15
|
+
import type { DomainClaimLite } from '../types'
|
|
16
|
+
import {
|
|
17
|
+
DOMAINS_SETTINGS_PAGE_DEFAULTS,
|
|
18
|
+
type DomainsSettingsPageClassNames,
|
|
19
|
+
} from './domains-settings-page-default-class-names'
|
|
20
|
+
import type { DomainsSettingsApi } from './types'
|
|
21
|
+
|
|
22
|
+
export interface DomainsSettingsPageProps {
|
|
23
|
+
/** The @startsimpli/api client. */
|
|
24
|
+
api: DomainsSettingsApi
|
|
25
|
+
/** Per-slot className overrides. */
|
|
26
|
+
classNames?: DomainsSettingsPageClassNames
|
|
27
|
+
/**
|
|
28
|
+
* Optional override for the company id. Defaults to the company resolved
|
|
29
|
+
* by useMembership() on the current user.
|
|
30
|
+
*/
|
|
31
|
+
companyId?: string
|
|
32
|
+
/** Back link href. Defaults to '/'. */
|
|
33
|
+
backHref?: string
|
|
34
|
+
/** Back link text. Defaults to '← Back'. */
|
|
35
|
+
backLabel?: string
|
|
36
|
+
/** Title. Defaults to 'Email domains'. */
|
|
37
|
+
title?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Loose domain pattern — backend is the canonical validator. */
|
|
41
|
+
const DOMAIN_RE = /^[a-z0-9.-]+\.[a-z]{2,}$/i
|
|
42
|
+
|
|
43
|
+
function toClaimLite(c: DomainClaimRow): DomainClaimLite {
|
|
44
|
+
return {
|
|
45
|
+
id: c.id,
|
|
46
|
+
companyId: c.companyId,
|
|
47
|
+
domain: c.domain,
|
|
48
|
+
verified: c.verified,
|
|
49
|
+
verificationMethod: c.verificationMethod,
|
|
50
|
+
verificationToken: c.verificationToken,
|
|
51
|
+
verifiedAt: c.verifiedAt,
|
|
52
|
+
createdAt: c.createdAt,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function DomainsSettingsPage({
|
|
57
|
+
api,
|
|
58
|
+
classNames,
|
|
59
|
+
companyId: companyIdProp,
|
|
60
|
+
backHref = '/',
|
|
61
|
+
backLabel = '← Back',
|
|
62
|
+
title = 'Email domains',
|
|
63
|
+
}: DomainsSettingsPageProps) {
|
|
64
|
+
const cls = { ...DOMAINS_SETTINGS_PAGE_DEFAULTS, ...(classNames ?? {}) }
|
|
65
|
+
const membership = useMembershipFromApi(api)
|
|
66
|
+
const { company, isAdmin, isLoading: membershipLoading } = membership
|
|
67
|
+
const companyId = companyIdProp ?? company?.id
|
|
68
|
+
|
|
69
|
+
const claims = useDomainClaims({
|
|
70
|
+
fetchClaims: async () => {
|
|
71
|
+
if (!companyId) return []
|
|
72
|
+
const res = await api.domainClaims.list({ companyId })
|
|
73
|
+
return res.results.map((c) => ({
|
|
74
|
+
id: c.id,
|
|
75
|
+
companyId: c.companyId,
|
|
76
|
+
domain: c.domain,
|
|
77
|
+
verified: c.verified,
|
|
78
|
+
verificationMethod: c.verificationMethod,
|
|
79
|
+
verificationToken: c.verificationToken,
|
|
80
|
+
verifiedAt: c.verifiedAt,
|
|
81
|
+
createdAt: c.createdAt,
|
|
82
|
+
}))
|
|
83
|
+
},
|
|
84
|
+
createClaim: async ({ companyId: cid, domain }) => {
|
|
85
|
+
const c = await api.domainClaims.create({ companyId: cid, domain })
|
|
86
|
+
return {
|
|
87
|
+
id: c.id,
|
|
88
|
+
companyId: c.companyId,
|
|
89
|
+
domain: c.domain,
|
|
90
|
+
verified: c.verified,
|
|
91
|
+
verificationMethod: c.verificationMethod,
|
|
92
|
+
verificationToken: c.verificationToken,
|
|
93
|
+
verifiedAt: c.verifiedAt,
|
|
94
|
+
createdAt: c.createdAt,
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
verifyDns: async (id) => {
|
|
98
|
+
const c = await api.domainClaims.verifyDns(id)
|
|
99
|
+
return {
|
|
100
|
+
id: c.id,
|
|
101
|
+
companyId: c.companyId,
|
|
102
|
+
domain: c.domain,
|
|
103
|
+
verified: c.verified,
|
|
104
|
+
verificationMethod: c.verificationMethod,
|
|
105
|
+
verificationToken: c.verificationToken,
|
|
106
|
+
verifiedAt: c.verifiedAt,
|
|
107
|
+
createdAt: c.createdAt,
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
initiateEmailVerification: (id) => api.domainClaims.verifyEmailInitiate(id),
|
|
111
|
+
submitEmailCode: async (id, code) => {
|
|
112
|
+
const c = await api.domainClaims.verifyEmailCode(id, code)
|
|
113
|
+
return {
|
|
114
|
+
id: c.id,
|
|
115
|
+
companyId: c.companyId,
|
|
116
|
+
domain: c.domain,
|
|
117
|
+
verified: c.verified,
|
|
118
|
+
verificationMethod: c.verificationMethod,
|
|
119
|
+
verificationToken: c.verificationToken,
|
|
120
|
+
verifiedAt: c.verifiedAt,
|
|
121
|
+
createdAt: c.createdAt,
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
revokeClaim: (id) => api.domainClaims.revoke(id),
|
|
125
|
+
autoFetch: !!companyId,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const [showAdd, setShowAdd] = React.useState(false)
|
|
129
|
+
const [domain, setDomain] = React.useState('')
|
|
130
|
+
const [addError, setAddError] = React.useState('')
|
|
131
|
+
const [submitting, setSubmitting] = React.useState(false)
|
|
132
|
+
|
|
133
|
+
async function handleAdd(e: React.FormEvent) {
|
|
134
|
+
e.preventDefault()
|
|
135
|
+
setAddError('')
|
|
136
|
+
const clean = domain.trim().toLowerCase()
|
|
137
|
+
if (!clean) {
|
|
138
|
+
setAddError('Domain is required')
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (!DOMAIN_RE.test(clean)) {
|
|
142
|
+
setAddError('Enter a valid domain like "example.com"')
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if (!companyId) {
|
|
146
|
+
setAddError('No company in scope')
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
setSubmitting(true)
|
|
150
|
+
try {
|
|
151
|
+
await claims.create({ companyId, domain: clean })
|
|
152
|
+
setDomain('')
|
|
153
|
+
setShowAdd(false)
|
|
154
|
+
} catch (err) {
|
|
155
|
+
setAddError(err instanceof Error ? err.message : 'Could not add domain')
|
|
156
|
+
} finally {
|
|
157
|
+
setSubmitting(false)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (membershipLoading) {
|
|
162
|
+
return <p className={cls.loadingText}>Loading…</p>
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!companyId) {
|
|
166
|
+
return (
|
|
167
|
+
<div className={cls.root}>
|
|
168
|
+
<a href={backHref} className={cls.backLink}>
|
|
169
|
+
{backLabel}
|
|
170
|
+
</a>
|
|
171
|
+
<h1 className={cls.title}>{title}</h1>
|
|
172
|
+
<p className={cls.subtitle}>
|
|
173
|
+
You need a company to claim domains. Accept a team invitation first.
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div className={cls.root}>
|
|
181
|
+
<div className={cls.header}>
|
|
182
|
+
<a href={backHref} className={cls.backLink}>
|
|
183
|
+
{backLabel}
|
|
184
|
+
</a>
|
|
185
|
+
<div className={cls.titleRow}>
|
|
186
|
+
<div>
|
|
187
|
+
<h1 className={cls.title}>{title}</h1>
|
|
188
|
+
<p className={cls.subtitle}>
|
|
189
|
+
Verified domains auto-join new signups to your team. Add a domain
|
|
190
|
+
you own.
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
{isAdmin && !showAdd && (
|
|
194
|
+
<button
|
|
195
|
+
type="button"
|
|
196
|
+
onClick={() => setShowAdd(true)}
|
|
197
|
+
className={cls.primaryButton}
|
|
198
|
+
>
|
|
199
|
+
+ Add domain
|
|
200
|
+
</button>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
{showAdd && (
|
|
206
|
+
<form onSubmit={handleAdd} className={cls.formCard}>
|
|
207
|
+
<label htmlFor="domain" className={cls.label}>
|
|
208
|
+
Domain
|
|
209
|
+
</label>
|
|
210
|
+
<input
|
|
211
|
+
id="domain"
|
|
212
|
+
type="text"
|
|
213
|
+
value={domain}
|
|
214
|
+
onChange={(e) => setDomain(e.target.value)}
|
|
215
|
+
placeholder="acme.com"
|
|
216
|
+
className={cls.input}
|
|
217
|
+
disabled={submitting}
|
|
218
|
+
autoFocus
|
|
219
|
+
/>
|
|
220
|
+
{addError && <p className={cls.errorText}>{addError}</p>}
|
|
221
|
+
<div className={cls.formActions}>
|
|
222
|
+
<button type="submit" disabled={submitting} className={cls.primaryButton}>
|
|
223
|
+
{submitting ? 'Adding…' : 'Add domain'}
|
|
224
|
+
</button>
|
|
225
|
+
<button
|
|
226
|
+
type="button"
|
|
227
|
+
onClick={() => {
|
|
228
|
+
setShowAdd(false)
|
|
229
|
+
setDomain('')
|
|
230
|
+
setAddError('')
|
|
231
|
+
}}
|
|
232
|
+
className={cls.cancelButton}
|
|
233
|
+
>
|
|
234
|
+
Cancel
|
|
235
|
+
</button>
|
|
236
|
+
</div>
|
|
237
|
+
</form>
|
|
238
|
+
)}
|
|
239
|
+
|
|
240
|
+
{claims.isLoading ? (
|
|
241
|
+
<p className={cls.loadingText}>Loading domains…</p>
|
|
242
|
+
) : claims.error ? (
|
|
243
|
+
<div className="space-y-2">
|
|
244
|
+
<p className={cls.errorText}>Could not load domains.</p>
|
|
245
|
+
<button
|
|
246
|
+
type="button"
|
|
247
|
+
onClick={() => void claims.refresh()}
|
|
248
|
+
className={cls.retryButton}
|
|
249
|
+
>
|
|
250
|
+
Retry
|
|
251
|
+
</button>
|
|
252
|
+
</div>
|
|
253
|
+
) : claims.claims.length === 0 ? (
|
|
254
|
+
<div className={cls.emptyState}>
|
|
255
|
+
<p className={cls.emptyText}>
|
|
256
|
+
No domains claimed yet. Add one to auto-join new signups from your
|
|
257
|
+
team's email domain.
|
|
258
|
+
</p>
|
|
259
|
+
</div>
|
|
260
|
+
) : (
|
|
261
|
+
<div className={cls.list}>
|
|
262
|
+
{claims.claims.map((c) => (
|
|
263
|
+
<DomainClaimCard
|
|
264
|
+
key={c.id}
|
|
265
|
+
claim={toClaimLite(c)}
|
|
266
|
+
classNames={cls.claimCard}
|
|
267
|
+
onVerifyDns={async () => {
|
|
268
|
+
await claims.verifyDns(c.id)
|
|
269
|
+
}}
|
|
270
|
+
onInitiateEmail={async () => {
|
|
271
|
+
await claims.initiateEmail(c.id)
|
|
272
|
+
}}
|
|
273
|
+
onVerifyEmail={async (_claim, code) => {
|
|
274
|
+
await claims.verifyEmail(c.id, code)
|
|
275
|
+
}}
|
|
276
|
+
onRevoke={
|
|
277
|
+
isAdmin
|
|
278
|
+
? async () => {
|
|
279
|
+
await claims.revoke(c.id)
|
|
280
|
+
}
|
|
281
|
+
: undefined
|
|
282
|
+
}
|
|
283
|
+
/>
|
|
284
|
+
))}
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
</div>
|
|
288
|
+
)
|
|
289
|
+
}
|