@varshylinc/team-management 0.1.0 → 0.2.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.
Files changed (254) hide show
  1. package/dist/client/actions.d.ts +20 -0
  2. package/dist/client/actions.d.ts.map +1 -0
  3. package/dist/client/actions.js +23 -0
  4. package/dist/client/actions.js.map +1 -0
  5. package/dist/client/api.d.ts +74 -0
  6. package/dist/client/api.d.ts.map +1 -0
  7. package/dist/client/api.js +245 -0
  8. package/dist/client/api.js.map +1 -0
  9. package/dist/client/components/AddMemberForm.d.ts +12 -0
  10. package/dist/client/components/AddMemberForm.d.ts.map +1 -0
  11. package/dist/client/components/AddMemberForm.js +37 -0
  12. package/dist/client/components/AddMemberForm.js.map +1 -0
  13. package/dist/client/components/AuditEventRow.d.ts +7 -0
  14. package/dist/client/components/AuditEventRow.d.ts.map +1 -0
  15. package/dist/client/components/AuditEventRow.js +20 -0
  16. package/dist/client/components/AuditEventRow.js.map +1 -0
  17. package/dist/client/components/CascadePreview.d.ts +11 -0
  18. package/dist/client/components/CascadePreview.d.ts.map +1 -0
  19. package/dist/client/components/CascadePreview.js +7 -0
  20. package/dist/client/components/CascadePreview.js.map +1 -0
  21. package/dist/client/components/DangerZoneCard.d.ts +10 -0
  22. package/dist/client/components/DangerZoneCard.d.ts.map +1 -0
  23. package/dist/client/components/DangerZoneCard.js +28 -0
  24. package/dist/client/components/DangerZoneCard.js.map +1 -0
  25. package/dist/client/components/InvitationCodeDisplay.d.ts +7 -0
  26. package/dist/client/components/InvitationCodeDisplay.d.ts.map +1 -0
  27. package/dist/client/components/InvitationCodeDisplay.js +26 -0
  28. package/dist/client/components/InvitationCodeDisplay.js.map +1 -0
  29. package/dist/client/components/InviteForm.d.ts +7 -0
  30. package/dist/client/components/InviteForm.d.ts.map +1 -0
  31. package/dist/client/components/InviteForm.js +35 -0
  32. package/dist/client/components/InviteForm.js.map +1 -0
  33. package/dist/client/components/MemberRow.d.ts +10 -0
  34. package/dist/client/components/MemberRow.d.ts.map +1 -0
  35. package/dist/client/components/MemberRow.js +17 -0
  36. package/dist/client/components/MemberRow.js.map +1 -0
  37. package/dist/client/components/OrgPeopleRoster.d.ts +11 -0
  38. package/dist/client/components/OrgPeopleRoster.d.ts.map +1 -0
  39. package/dist/client/components/OrgPeopleRoster.js +13 -0
  40. package/dist/client/components/OrgPeopleRoster.js.map +1 -0
  41. package/dist/client/components/PendingTransferBanner.d.ts +10 -0
  42. package/dist/client/components/PendingTransferBanner.d.ts.map +1 -0
  43. package/dist/client/components/PendingTransferBanner.js +48 -0
  44. package/dist/client/components/PendingTransferBanner.js.map +1 -0
  45. package/dist/client/components/PlaceholderCard.d.ts +7 -0
  46. package/dist/client/components/PlaceholderCard.d.ts.map +1 -0
  47. package/dist/client/components/PlaceholderCard.js +15 -0
  48. package/dist/client/components/PlaceholderCard.js.map +1 -0
  49. package/dist/client/components/RoleBadge.d.ts +5 -0
  50. package/dist/client/components/RoleBadge.d.ts.map +1 -0
  51. package/dist/client/components/RoleBadge.js +17 -0
  52. package/dist/client/components/RoleBadge.js.map +1 -0
  53. package/dist/client/components/RoleSelect.d.ts +10 -0
  54. package/dist/client/components/RoleSelect.d.ts.map +1 -0
  55. package/dist/client/components/RoleSelect.js +12 -0
  56. package/dist/client/components/RoleSelect.js.map +1 -0
  57. package/dist/client/components/SeatUsagePanel.d.ts +6 -0
  58. package/dist/client/components/SeatUsagePanel.d.ts.map +1 -0
  59. package/dist/client/components/SeatUsagePanel.js +13 -0
  60. package/dist/client/components/SeatUsagePanel.js.map +1 -0
  61. package/dist/client/hooks/useCurrentMembership.d.ts +8 -0
  62. package/dist/client/hooks/useCurrentMembership.d.ts.map +1 -0
  63. package/dist/client/hooks/useCurrentMembership.js +20 -0
  64. package/dist/client/hooks/useCurrentMembership.js.map +1 -0
  65. package/dist/client/hooks/useMembers.d.ts +10 -0
  66. package/dist/client/hooks/useMembers.d.ts.map +1 -0
  67. package/dist/client/hooks/useMembers.js +20 -0
  68. package/dist/client/hooks/useMembers.js.map +1 -0
  69. package/dist/client/hooks/useOrgMembers.d.ts +20 -0
  70. package/dist/client/hooks/useOrgMembers.d.ts.map +1 -0
  71. package/dist/client/hooks/useOrgMembers.js +63 -0
  72. package/dist/client/hooks/useOrgMembers.js.map +1 -0
  73. package/dist/client/hooks/usePendingInvitations.d.ts +8 -0
  74. package/dist/client/hooks/usePendingInvitations.d.ts.map +1 -0
  75. package/dist/client/hooks/usePendingInvitations.js +20 -0
  76. package/dist/client/hooks/usePendingInvitations.js.map +1 -0
  77. package/dist/client/hooks/usePendingTransfer.d.ts +8 -0
  78. package/dist/client/hooks/usePendingTransfer.d.ts.map +1 -0
  79. package/dist/client/hooks/usePendingTransfer.js +23 -0
  80. package/dist/client/hooks/usePendingTransfer.js.map +1 -0
  81. package/dist/client/index.d.ts +31 -0
  82. package/dist/client/index.d.ts.map +1 -0
  83. package/{src/client/index.ts → dist/client/index.js} +6 -54
  84. package/dist/client/index.js.map +1 -0
  85. package/dist/client/pages/AuditLogPage.d.ts +6 -0
  86. package/dist/client/pages/AuditLogPage.d.ts.map +1 -0
  87. package/dist/client/pages/AuditLogPage.js +51 -0
  88. package/dist/client/pages/AuditLogPage.js.map +1 -0
  89. package/dist/client/pages/EmailChangePage.d.ts +8 -0
  90. package/dist/client/pages/EmailChangePage.d.ts.map +1 -0
  91. package/dist/client/pages/EmailChangePage.js +52 -0
  92. package/dist/client/pages/EmailChangePage.js.map +1 -0
  93. package/dist/client/pages/InvitationAcceptPage.d.ts +11 -0
  94. package/dist/client/pages/InvitationAcceptPage.d.ts.map +1 -0
  95. package/dist/client/pages/InvitationAcceptPage.js +42 -0
  96. package/dist/client/pages/InvitationAcceptPage.js.map +1 -0
  97. package/dist/client/pages/InvitationCodePage.d.ts +6 -0
  98. package/dist/client/pages/InvitationCodePage.d.ts.map +1 -0
  99. package/dist/client/pages/InvitationCodePage.js +28 -0
  100. package/dist/client/pages/InvitationCodePage.js.map +1 -0
  101. package/dist/client/pages/MembersPage.d.ts +6 -0
  102. package/dist/client/pages/MembersPage.d.ts.map +1 -0
  103. package/dist/client/pages/MembersPage.js +67 -0
  104. package/dist/client/pages/MembersPage.js.map +1 -0
  105. package/dist/client/pages/OrgPeoplePage.d.ts +14 -0
  106. package/dist/client/pages/OrgPeoplePage.d.ts.map +1 -0
  107. package/dist/client/pages/OrgPeoplePage.js +40 -0
  108. package/dist/client/pages/OrgPeoplePage.js.map +1 -0
  109. package/dist/client/pages/OrgSettingsPage.d.ts +6 -0
  110. package/dist/client/pages/OrgSettingsPage.d.ts.map +1 -0
  111. package/dist/client/pages/OrgSettingsPage.js +78 -0
  112. package/dist/client/pages/OrgSettingsPage.js.map +1 -0
  113. package/dist/client/pages/OwnershipTransferPage.d.ts +6 -0
  114. package/dist/client/pages/OwnershipTransferPage.d.ts.map +1 -0
  115. package/dist/client/pages/OwnershipTransferPage.js +68 -0
  116. package/dist/client/pages/OwnershipTransferPage.js.map +1 -0
  117. package/dist/client/pages/PasswordResetPage.d.ts +6 -0
  118. package/dist/client/pages/PasswordResetPage.d.ts.map +1 -0
  119. package/dist/client/pages/PasswordResetPage.js +34 -0
  120. package/dist/client/pages/PasswordResetPage.js.map +1 -0
  121. package/dist/client/pages/PasswordResetRequestPage.d.ts +2 -0
  122. package/dist/client/pages/PasswordResetRequestPage.d.ts.map +1 -0
  123. package/dist/client/pages/PasswordResetRequestPage.js +27 -0
  124. package/dist/client/pages/PasswordResetRequestPage.js.map +1 -0
  125. package/dist/client/pages/PlaceholderPage.d.ts +7 -0
  126. package/dist/client/pages/PlaceholderPage.d.ts.map +1 -0
  127. package/dist/client/pages/PlaceholderPage.js +16 -0
  128. package/dist/client/pages/PlaceholderPage.js.map +1 -0
  129. package/dist/client/pages/SuperAdminDashboard.d.ts +2 -0
  130. package/dist/client/pages/SuperAdminDashboard.d.ts.map +1 -0
  131. package/dist/client/pages/SuperAdminDashboard.js +123 -0
  132. package/dist/client/pages/SuperAdminDashboard.js.map +1 -0
  133. package/dist/client/theme.d.ts +12 -0
  134. package/dist/client/theme.d.ts.map +1 -0
  135. package/dist/client/theme.js +16 -0
  136. package/dist/client/theme.js.map +1 -0
  137. package/dist/client/types.d.ts +78 -0
  138. package/dist/client/types.d.ts.map +1 -0
  139. package/dist/client/types.js +2 -0
  140. package/dist/client/types.js.map +1 -0
  141. package/dist/index.d.ts +3 -1
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js +2 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/server/index.d.ts +2 -0
  146. package/dist/server/index.d.ts.map +1 -1
  147. package/dist/server/index.js +1 -0
  148. package/dist/server/index.js.map +1 -1
  149. package/dist/server/org-admin.d.ts +45 -0
  150. package/dist/server/org-admin.d.ts.map +1 -0
  151. package/dist/server/org-admin.js +63 -0
  152. package/dist/server/org-admin.js.map +1 -0
  153. package/dist/server/routes/orgs.routes.d.ts.map +1 -1
  154. package/dist/server/routes/orgs.routes.js +81 -12
  155. package/dist/server/routes/orgs.routes.js.map +1 -1
  156. package/dist/server/types.d.ts +2 -0
  157. package/dist/server/types.d.ts.map +1 -1
  158. package/dist/server/types.js.map +1 -1
  159. package/package.json +18 -11
  160. package/.eslintrc.cjs +0 -18
  161. package/CHANGELOG.md +0 -159
  162. package/src/client/api.ts +0 -314
  163. package/src/client/components/AuditEventRow.tsx +0 -59
  164. package/src/client/components/CascadePreview.tsx +0 -36
  165. package/src/client/components/DangerZoneCard.tsx +0 -103
  166. package/src/client/components/InvitationCodeDisplay.tsx +0 -48
  167. package/src/client/components/InviteForm.tsx +0 -77
  168. package/src/client/components/MemberRow.tsx +0 -69
  169. package/src/client/components/PendingTransferBanner.tsx +0 -98
  170. package/src/client/components/PlaceholderCard.tsx +0 -26
  171. package/src/client/components/RoleBadge.tsx +0 -26
  172. package/src/client/components/RoleSelect.tsx +0 -35
  173. package/src/client/hooks/.gitkeep +0 -0
  174. package/src/client/hooks/useCurrentMembership.ts +0 -24
  175. package/src/client/hooks/useMembers.ts +0 -24
  176. package/src/client/hooks/usePendingInvitations.ts +0 -24
  177. package/src/client/hooks/usePendingTransfer.ts +0 -27
  178. package/src/client/pages/AuditLogPage.tsx +0 -164
  179. package/src/client/pages/EmailChangePage.tsx +0 -144
  180. package/src/client/pages/InvitationAcceptPage.tsx +0 -163
  181. package/src/client/pages/InvitationCodePage.tsx +0 -108
  182. package/src/client/pages/MembersPage.tsx +0 -290
  183. package/src/client/pages/OrgSettingsPage.tsx +0 -185
  184. package/src/client/pages/OwnershipTransferPage.tsx +0 -163
  185. package/src/client/pages/PasswordResetPage.tsx +0 -104
  186. package/src/client/pages/PasswordResetRequestPage.tsx +0 -71
  187. package/src/client/pages/PlaceholderPage.tsx +0 -20
  188. package/src/client/pages/SuperAdminDashboard.tsx +0 -401
  189. package/src/client/types.ts +0 -78
  190. package/src/index.ts +0 -24
  191. package/src/server/crypto.ts +0 -47
  192. package/src/server/index.ts +0 -167
  193. package/src/server/middleware/require-membership.ts +0 -48
  194. package/src/server/middleware/require-role.ts +0 -19
  195. package/src/server/middleware/require-super-admin.ts +0 -32
  196. package/src/server/migrations/0001_create_tm_schema_migrations.sql +0 -13
  197. package/src/server/migrations/0002_create_tm_organizations.sql +0 -14
  198. package/src/server/migrations/0003_create_tm_memberships.sql +0 -24
  199. package/src/server/migrations/0004_create_tm_invitations.sql +0 -22
  200. package/src/server/migrations/0005_create_tm_audit_events.sql +0 -17
  201. package/src/server/migrations/0006_create_tm_email_change_requests.sql +0 -13
  202. package/src/server/migrations/0007_create_tm_ownership_transfers.sql +0 -22
  203. package/src/server/migrations/0008_create_tm_super_admins.sql +0 -8
  204. package/src/server/migrations/0009_create_tm_password_reset_requests.sql +0 -9
  205. package/src/server/migrations/0010_create_tm_shared_access.sql +0 -8
  206. package/src/server/migrations/0011_seed_super_admin.sql +0 -15
  207. package/src/server/migrations/0012_create_tm_user_locks.sql +0 -7
  208. package/src/server/routes/admin.routes.ts +0 -208
  209. package/src/server/routes/audit.routes.ts +0 -93
  210. package/src/server/routes/health.routes.ts +0 -46
  211. package/src/server/routes/invitations.routes.ts +0 -252
  212. package/src/server/routes/me.routes.ts +0 -143
  213. package/src/server/routes/orgs.routes.ts +0 -428
  214. package/src/server/routes/transfer.routes.ts +0 -110
  215. package/src/server/services/.gitkeep +0 -0
  216. package/src/server/services/audit.service.ts +0 -49
  217. package/src/server/services/email-change.service.ts +0 -178
  218. package/src/server/services/invitations.service.ts +0 -316
  219. package/src/server/services/memberships.service.ts +0 -129
  220. package/src/server/services/organizations.service.ts +0 -110
  221. package/src/server/services/ownership.service.ts +0 -170
  222. package/src/server/services/password-reset.service.ts +0 -94
  223. package/src/server/services/super-admin.service.ts +0 -321
  224. package/src/server/sql/.gitkeep +0 -0
  225. package/src/server/types.ts +0 -145
  226. package/src/shared/types.ts +0 -24
  227. package/tests/integration/audit-fires.test.ts +0 -288
  228. package/tests/integration/cascade-preview.test.ts +0 -157
  229. package/tests/integration/email-change.test.ts +0 -190
  230. package/tests/integration/feature-flags.test.ts +0 -213
  231. package/tests/integration/invitations-code.test.ts +0 -218
  232. package/tests/integration/invitations-expiry.test.ts +0 -216
  233. package/tests/integration/invitations-resend.test.ts +0 -241
  234. package/tests/integration/invitations-revoke.test.ts +0 -226
  235. package/tests/integration/invitations-switch-org.test.ts +0 -156
  236. package/tests/integration/invitations-token.test.ts +0 -221
  237. package/tests/integration/migrations.test.ts +0 -119
  238. package/tests/integration/only-owner-protections.test.ts +0 -130
  239. package/tests/integration/org-lifecycle.test.ts +0 -169
  240. package/tests/integration/ownership-transfer-cancel.test.ts +0 -171
  241. package/tests/integration/ownership-transfer-expire.test.ts +0 -171
  242. package/tests/integration/ownership-transfer-happy.test.ts +0 -184
  243. package/tests/integration/ownership-transfer-locks.test.ts +0 -146
  244. package/tests/integration/password-reset.test.ts +0 -200
  245. package/tests/integration/super-admin-actions.test.ts +0 -180
  246. package/tests/integration/super-admin-restrictions.test.ts +0 -209
  247. package/tests/setup/global-setup.ts +0 -20
  248. package/tests/unit/adapter-shape.test.ts +0 -330
  249. package/tests/unit/role-permissions.test.ts +0 -236
  250. package/tests/unit/validation.test.ts +0 -304
  251. package/tsconfig.client.json +0 -13
  252. package/tsconfig.json +0 -12
  253. package/tsconfig.tsbuildinfo +0 -1
  254. package/vitest.config.ts +0 -13
@@ -1,103 +0,0 @@
1
- import React, { useState } from 'react';
2
-
3
- interface DangerZoneCardProps {
4
- title: string;
5
- description: string;
6
- buttonLabel: string;
7
- onConfirm: () => Promise<void> | void;
8
- confirmPrompt?: string;
9
- }
10
-
11
- export function DangerZoneCard({
12
- title,
13
- description,
14
- buttonLabel,
15
- onConfirm,
16
- confirmPrompt,
17
- }: DangerZoneCardProps) {
18
- const [showModal, setShowModal] = useState(false);
19
- const [confirmText, setConfirmText] = useState('');
20
- const [submitting, setSubmitting] = useState(false);
21
- const [error, setError] = useState<string | null>(null);
22
-
23
- const isConfirmed = !confirmPrompt || confirmText === confirmPrompt;
24
-
25
- async function handleConfirm() {
26
- if (!isConfirmed) return;
27
- setSubmitting(true);
28
- setError(null);
29
- try {
30
- await onConfirm();
31
- setShowModal(false);
32
- setConfirmText('');
33
- } catch (err) {
34
- setError(err instanceof Error ? err.message : 'Action failed');
35
- } finally {
36
- setSubmitting(false);
37
- }
38
- }
39
-
40
- return (
41
- <>
42
- <div className="border border-red-200 rounded-lg p-5 bg-red-50">
43
- <div className="flex flex-col sm:flex-row sm:items-center gap-4">
44
- <div className="flex-1">
45
- <h3 className="text-sm font-semibold text-red-800">{title}</h3>
46
- <p className="mt-1 text-sm text-red-700">{description}</p>
47
- </div>
48
- <button
49
- onClick={() => setShowModal(true)}
50
- className="shrink-0 rounded-md border border-red-600 bg-white px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-600 hover:text-white focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 transition-colors"
51
- >
52
- {buttonLabel}
53
- </button>
54
- </div>
55
- </div>
56
-
57
- {showModal && (
58
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
59
- <div className="bg-white rounded-xl shadow-2xl w-full max-w-md p-6">
60
- <h2 className="text-lg font-semibold text-slate-900 mb-2">{title}</h2>
61
- <p className="text-sm text-slate-600 mb-4">{description}</p>
62
-
63
- {confirmPrompt && (
64
- <div className="mb-4">
65
- <p className="text-sm text-slate-700 mb-1">
66
- Type <strong className="font-mono">{confirmPrompt}</strong> to confirm:
67
- </p>
68
- <input
69
- type="text"
70
- value={confirmText}
71
- onChange={(e) => setConfirmText(e.target.value)}
72
- placeholder={confirmPrompt}
73
- className="w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-red-500 focus:outline-none focus:ring-1 focus:ring-red-500"
74
- />
75
- </div>
76
- )}
77
-
78
- {error && (
79
- <p className="mb-3 text-sm text-red-600">{error}</p>
80
- )}
81
-
82
- <div className="flex gap-3 justify-end">
83
- <button
84
- onClick={() => { setShowModal(false); setConfirmText(''); setError(null); }}
85
- disabled={submitting}
86
- className="rounded-md border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50 disabled:opacity-50 transition-colors"
87
- >
88
- Cancel
89
- </button>
90
- <button
91
- onClick={handleConfirm}
92
- disabled={!isConfirmed || submitting}
93
- className="rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
94
- >
95
- {submitting ? 'Processing…' : buttonLabel}
96
- </button>
97
- </div>
98
- </div>
99
- </div>
100
- )}
101
- </>
102
- );
103
- }
@@ -1,48 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { getInvitationCode } from '../api.js';
3
-
4
- interface InvitationCodeDisplayProps {
5
- orgId: number;
6
- invitationId: number;
7
- }
8
-
9
- export function InvitationCodeDisplay({ orgId, invitationId }: InvitationCodeDisplayProps) {
10
- const [code, setCode] = useState<string | null>(null);
11
- const [loading, setLoading] = useState(false);
12
- const [error, setError] = useState<string | null>(null);
13
-
14
- async function handleShowCode() {
15
- if (code) return;
16
- setLoading(true);
17
- setError(null);
18
- try {
19
- const result = await getInvitationCode(orgId, invitationId);
20
- setCode(result.code);
21
- } catch (err) {
22
- setError(err instanceof Error ? err.message : 'Failed to load code');
23
- } finally {
24
- setLoading(false);
25
- }
26
- }
27
-
28
- return (
29
- <div className="inline-flex items-center gap-2">
30
- {code ? (
31
- <span className="font-mono text-sm font-semibold tracking-widest text-slate-900 bg-slate-100 border border-slate-300 rounded px-3 py-1 select-all">
32
- {code}
33
- </span>
34
- ) : (
35
- <button
36
- onClick={handleShowCode}
37
- disabled={loading}
38
- className="text-xs text-blue-600 hover:text-blue-800 underline disabled:opacity-50 transition-colors"
39
- >
40
- {loading ? 'Loading…' : 'Show Code'}
41
- </button>
42
- )}
43
- {error && (
44
- <span className="text-xs text-red-600">{error}</span>
45
- )}
46
- </div>
47
- );
48
- }
@@ -1,77 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { OrgRole } from '../types.js';
3
- import { createInvitation } from '../api.js';
4
- import { RoleSelect } from './RoleSelect.js';
5
-
6
- interface InviteFormProps {
7
- orgId: number;
8
- onSuccess: () => void;
9
- }
10
-
11
- export function InviteForm({ orgId, onSuccess }: InviteFormProps) {
12
- const [email, setEmail] = useState('');
13
- const [role, setRole] = useState<OrgRole>('member');
14
- const [submitting, setSubmitting] = useState(false);
15
- const [error, setError] = useState<string | null>(null);
16
- const [success, setSuccess] = useState(false);
17
-
18
- async function handleSubmit(e: React.FormEvent) {
19
- e.preventDefault();
20
- if (!email.trim()) return;
21
-
22
- setSubmitting(true);
23
- setError(null);
24
- setSuccess(false);
25
-
26
- try {
27
- await createInvitation(orgId, { email: email.trim(), role });
28
- setEmail('');
29
- setRole('member');
30
- setSuccess(true);
31
- onSuccess();
32
- setTimeout(() => setSuccess(false), 3000);
33
- } catch (err) {
34
- setError(err instanceof Error ? err.message : 'Failed to send invitation');
35
- } finally {
36
- setSubmitting(false);
37
- }
38
- }
39
-
40
- return (
41
- <form onSubmit={handleSubmit} className="bg-white border border-slate-200 rounded-lg p-4 shadow-sm">
42
- <h3 className="text-sm font-semibold text-slate-800 mb-3">Invite a team member</h3>
43
- <div className="flex flex-col sm:flex-row gap-2">
44
- <input
45
- type="email"
46
- placeholder="colleague@company.com"
47
- value={email}
48
- onChange={(e) => setEmail(e.target.value)}
49
- required
50
- disabled={submitting}
51
- className="flex-1 rounded-md border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:bg-slate-50"
52
- />
53
- <div className="w-full sm:w-36">
54
- <RoleSelect
55
- value={role}
56
- onChange={setRole}
57
- disabled={submitting}
58
- disabledRoles={['owner']}
59
- />
60
- </div>
61
- <button
62
- type="submit"
63
- disabled={submitting || !email.trim()}
64
- className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
65
- >
66
- {submitting ? 'Sending…' : 'Send Invite'}
67
- </button>
68
- </div>
69
- {error && (
70
- <p className="mt-2 text-sm text-red-600">{error}</p>
71
- )}
72
- {success && (
73
- <p className="mt-2 text-sm text-green-600">Invitation sent successfully!</p>
74
- )}
75
- </form>
76
- );
77
- }
@@ -1,69 +0,0 @@
1
- import React from 'react';
2
- import type { PublicMember, OrgRole } from '../types.js';
3
- import { RoleBadge } from './RoleBadge.js';
4
- import { RoleSelect } from './RoleSelect.js';
5
-
6
- interface MemberRowProps {
7
- member: PublicMember;
8
- currentUserRole: OrgRole;
9
- onRemove: (userId: number) => void;
10
- onRoleChange: (userId: number, newRole: OrgRole) => void;
11
- }
12
-
13
- const canManage = (currentRole: OrgRole): boolean =>
14
- currentRole === 'owner' || currentRole === 'admin';
15
-
16
-
17
- function formatDate(iso: string): string {
18
- return new Date(iso).toLocaleDateString(undefined, {
19
- year: 'numeric',
20
- month: 'short',
21
- day: 'numeric',
22
- });
23
- }
24
-
25
- export function MemberRow({ member, currentUserRole, onRemove, onRoleChange }: MemberRowProps) {
26
- const canEdit = canManage(currentUserRole) && member.role !== 'owner';
27
- const disabledRoles: OrgRole[] =
28
- currentUserRole === 'admin' ? ['owner'] : [];
29
-
30
- return (
31
- <tr className="border-b border-slate-100 last:border-0 hover:bg-slate-50 transition-colors">
32
- <td className="py-3 px-4">
33
- <div className="flex flex-col">
34
- <span className="text-sm font-medium text-slate-900">
35
- {member.name ?? member.email}
36
- </span>
37
- {member.name && (
38
- <span className="text-xs text-slate-500">{member.email}</span>
39
- )}
40
- </div>
41
- </td>
42
- <td className="py-3 px-4">
43
- {canEdit ? (
44
- <RoleSelect
45
- value={member.role}
46
- onChange={(r) => onRoleChange(member.user_id, r)}
47
- disabledRoles={disabledRoles}
48
- />
49
- ) : (
50
- <RoleBadge role={member.role} />
51
- )}
52
- </td>
53
- <td className="py-3 px-4 text-sm text-slate-500 whitespace-nowrap">
54
- {formatDate(member.joined_at)}
55
- </td>
56
- <td className="py-3 px-4 text-right">
57
- {canEdit && (
58
- <button
59
- onClick={() => onRemove(member.user_id)}
60
- className="text-sm text-red-600 hover:text-red-800 font-medium transition-colors"
61
- >
62
- Remove
63
- </button>
64
- )}
65
- </td>
66
- </tr>
67
- );
68
- }
69
-
@@ -1,98 +0,0 @@
1
- import React, { useState } from 'react';
2
- import type { OwnershipTransfer } from '../types.js';
3
- import { acceptTransfer, cancelTransfer } from '../api.js';
4
-
5
- interface PendingTransferBannerProps {
6
- transfer: OwnershipTransfer;
7
- currentUserId: number;
8
- orgId: number;
9
- onAction: () => void;
10
- }
11
-
12
- function formatDate(iso: string): string {
13
- return new Date(iso).toLocaleString(undefined, {
14
- month: 'short',
15
- day: 'numeric',
16
- hour: '2-digit',
17
- minute: '2-digit',
18
- });
19
- }
20
-
21
- export function PendingTransferBanner({
22
- transfer,
23
- currentUserId,
24
- orgId,
25
- onAction,
26
- }: PendingTransferBannerProps) {
27
- const [submitting, setSubmitting] = useState(false);
28
- const [error, setError] = useState<string | null>(null);
29
-
30
- const isTarget = currentUserId === transfer.to_user_id;
31
-
32
- async function handleAccept() {
33
- setSubmitting(true);
34
- setError(null);
35
- try {
36
- await acceptTransfer(orgId);
37
- onAction();
38
- } catch (err) {
39
- setError(err instanceof Error ? err.message : 'Action failed');
40
- } finally {
41
- setSubmitting(false);
42
- }
43
- }
44
-
45
- async function handleDecline() {
46
- setSubmitting(true);
47
- setError(null);
48
- try {
49
- await cancelTransfer(orgId);
50
- onAction();
51
- } catch (err) {
52
- setError(err instanceof Error ? err.message : 'Action failed');
53
- } finally {
54
- setSubmitting(false);
55
- }
56
- }
57
-
58
- return (
59
- <div className="rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 shadow-sm">
60
- <div className="flex flex-col sm:flex-row sm:items-center gap-3">
61
- <div className="flex-1">
62
- <p className="text-sm font-semibold text-amber-900">
63
- Pending Ownership Transfer
64
- </p>
65
- <p className="text-sm text-amber-800 mt-0.5">
66
- {isTarget
67
- ? `You have been invited to become the new owner of this organization.`
68
- : `An ownership transfer has been initiated to user #${transfer.to_user_id}.`}
69
- </p>
70
- <p className="text-xs text-amber-700 mt-1">
71
- Expires {formatDate(transfer.expires_at)}
72
- </p>
73
- </div>
74
- {isTarget && (
75
- <div className="flex gap-2 shrink-0">
76
- <button
77
- onClick={handleAccept}
78
- disabled={submitting}
79
- className="rounded-md bg-amber-600 px-4 py-2 text-sm font-medium text-white hover:bg-amber-700 focus:outline-none focus:ring-2 focus:ring-amber-500 disabled:opacity-50 transition-colors"
80
- >
81
- Accept
82
- </button>
83
- <button
84
- onClick={handleDecline}
85
- disabled={submitting}
86
- className="rounded-md border border-amber-400 bg-white px-4 py-2 text-sm font-medium text-amber-800 hover:bg-amber-100 focus:outline-none focus:ring-2 focus:ring-amber-400 disabled:opacity-50 transition-colors"
87
- >
88
- Decline
89
- </button>
90
- </div>
91
- )}
92
- </div>
93
- {error && (
94
- <p className="mt-2 text-sm text-red-600">{error}</p>
95
- )}
96
- </div>
97
- );
98
- }
@@ -1,26 +0,0 @@
1
- import React from 'react';
2
-
3
- /**
4
- * PlaceholderCard — rendered by host products while the full
5
- * Team Management feature is in development.
6
- */
7
- export function PlaceholderCard(): React.ReactElement {
8
- return (
9
- <div style={{
10
- border: '1px solid #e2e8f0',
11
- borderRadius: '8px',
12
- padding: '24px',
13
- maxWidth: '400px',
14
- fontFamily: 'system-ui, sans-serif',
15
- }}>
16
- <div style={{ fontSize: '24px', marginBottom: '8px' }}>👥</div>
17
- <h3 style={{ margin: '0 0 8px', fontSize: '16px', fontWeight: 600, color: '#1a2230' }}>
18
- Team Management
19
- </h3>
20
- <p style={{ margin: 0, fontSize: '14px', color: '#64748b' }}>
21
- Coming soon. Invite teammates, manage roles, and control access
22
- to your Varshyl products.
23
- </p>
24
- </div>
25
- );
26
- }
@@ -1,26 +0,0 @@
1
- import React from 'react';
2
- import type { OrgRole } from '../types.js';
3
-
4
- const roleStyles: Record<OrgRole, string> = {
5
- owner: 'bg-purple-100 text-purple-800 border border-purple-300',
6
- admin: 'bg-blue-100 text-blue-800 border border-blue-300',
7
- member: 'bg-green-100 text-green-800 border border-green-300',
8
- viewer: 'bg-slate-100 text-slate-700 border border-slate-300',
9
- };
10
-
11
- const roleLabels: Record<OrgRole, string> = {
12
- owner: 'Owner',
13
- admin: 'Admin',
14
- member: 'Member',
15
- viewer: 'Viewer',
16
- };
17
-
18
- export function RoleBadge({ role }: { role: OrgRole }) {
19
- return (
20
- <span
21
- className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${roleStyles[role]}`}
22
- >
23
- {roleLabels[role]}
24
- </span>
25
- );
26
- }
@@ -1,35 +0,0 @@
1
- import React from 'react';
2
- import type { OrgRole } from '../types.js';
3
-
4
- const ALL_ROLES: OrgRole[] = ['owner', 'admin', 'member', 'viewer'];
5
-
6
- const roleLabels: Record<OrgRole, string> = {
7
- owner: 'Owner',
8
- admin: 'Admin',
9
- member: 'Member',
10
- viewer: 'Viewer',
11
- };
12
-
13
- interface RoleSelectProps {
14
- value: OrgRole;
15
- onChange: (r: OrgRole) => void;
16
- disabled?: boolean;
17
- disabledRoles?: OrgRole[];
18
- }
19
-
20
- export function RoleSelect({ value, onChange, disabled, disabledRoles = [] }: RoleSelectProps) {
21
- return (
22
- <select
23
- value={value}
24
- onChange={(e) => onChange(e.target.value as OrgRole)}
25
- disabled={disabled}
26
- className="block w-full rounded-md border border-slate-300 bg-white px-3 py-1.5 text-sm text-slate-900 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:cursor-not-allowed disabled:bg-slate-50 disabled:text-slate-400"
27
- >
28
- {ALL_ROLES.map((r) => (
29
- <option key={r} value={r} disabled={disabledRoles.includes(r)}>
30
- {roleLabels[r]}
31
- </option>
32
- ))}
33
- </select>
34
- );
35
- }
File without changes
@@ -1,24 +0,0 @@
1
- import { useState, useEffect, useCallback } from 'react';
2
- import { getMyMembership } from '../api.js';
3
- import type { CurrentMembership } from '../types.js';
4
-
5
- export function useCurrentMembership() {
6
- const [membership, setMembership] = useState<CurrentMembership | null>(null);
7
- const [loading, setLoading] = useState(true);
8
- const [error, setError] = useState<string | null>(null);
9
-
10
- const load = useCallback(() => {
11
- setLoading(true);
12
- setError(null);
13
- getMyMembership()
14
- .then(setMembership)
15
- .catch((e: Error) => setError(e.message))
16
- .finally(() => setLoading(false));
17
- }, []);
18
-
19
- useEffect(() => {
20
- load();
21
- }, [load]);
22
-
23
- return { membership, loading, error, refresh: load };
24
- }
@@ -1,24 +0,0 @@
1
- import { useState, useEffect, useCallback } from 'react';
2
- import { listMembers } from '../api.js';
3
- import type { PublicMember } from '../types.js';
4
-
5
- export function useMembers(orgId: number, opts?: { includeFormer?: boolean }) {
6
- const [members, setMembers] = useState<PublicMember[]>([]);
7
- const [loading, setLoading] = useState(true);
8
- const [error, setError] = useState<string | null>(null);
9
-
10
- const load = useCallback(() => {
11
- setLoading(true);
12
- setError(null);
13
- listMembers(orgId, opts)
14
- .then(setMembers)
15
- .catch((e: Error) => setError(e.message))
16
- .finally(() => setLoading(false));
17
- }, [orgId, opts?.includeFormer]);
18
-
19
- useEffect(() => {
20
- load();
21
- }, [load]);
22
-
23
- return { members, loading, error, refresh: load };
24
- }
@@ -1,24 +0,0 @@
1
- import { useState, useEffect, useCallback } from 'react';
2
- import { listInvitations } from '../api.js';
3
- import type { PendingInvitation } from '../types.js';
4
-
5
- export function usePendingInvitations(orgId: number) {
6
- const [invitations, setInvitations] = useState<PendingInvitation[]>([]);
7
- const [loading, setLoading] = useState(true);
8
- const [error, setError] = useState<string | null>(null);
9
-
10
- const load = useCallback(() => {
11
- setLoading(true);
12
- setError(null);
13
- listInvitations(orgId)
14
- .then(setInvitations)
15
- .catch((e: Error) => setError(e.message))
16
- .finally(() => setLoading(false));
17
- }, [orgId]);
18
-
19
- useEffect(() => {
20
- load();
21
- }, [load]);
22
-
23
- return { invitations, loading, error, refresh: load };
24
- }
@@ -1,27 +0,0 @@
1
- import { useState, useEffect, useCallback } from 'react';
2
- import { getPendingTransfer } from '../api.js';
3
- import type { OwnershipTransfer } from '../types.js';
4
-
5
- export function usePendingTransfer(orgId: number) {
6
- const [transfer, setTransfer] = useState<OwnershipTransfer | null>(null);
7
- const [loading, setLoading] = useState(true);
8
- const [error, setError] = useState<string | null>(null);
9
-
10
- const load = useCallback(() => {
11
- setLoading(true);
12
- setError(null);
13
- getPendingTransfer(orgId)
14
- .then(setTransfer)
15
- .catch((e: Error) => setError(e.message))
16
- .finally(() => setLoading(false));
17
- }, [orgId]);
18
-
19
- useEffect(() => {
20
- load();
21
- // Poll every 30 seconds for transfer status changes
22
- const interval = setInterval(load, 30_000);
23
- return () => clearInterval(interval);
24
- }, [load]);
25
-
26
- return { transfer, loading, error, refresh: load };
27
- }