@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.
- package/dist/client/actions.d.ts +20 -0
- package/dist/client/actions.d.ts.map +1 -0
- package/dist/client/actions.js +23 -0
- package/dist/client/actions.js.map +1 -0
- package/dist/client/api.d.ts +74 -0
- package/dist/client/api.d.ts.map +1 -0
- package/dist/client/api.js +245 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/components/AddMemberForm.d.ts +12 -0
- package/dist/client/components/AddMemberForm.d.ts.map +1 -0
- package/dist/client/components/AddMemberForm.js +37 -0
- package/dist/client/components/AddMemberForm.js.map +1 -0
- package/dist/client/components/AuditEventRow.d.ts +7 -0
- package/dist/client/components/AuditEventRow.d.ts.map +1 -0
- package/dist/client/components/AuditEventRow.js +20 -0
- package/dist/client/components/AuditEventRow.js.map +1 -0
- package/dist/client/components/CascadePreview.d.ts +11 -0
- package/dist/client/components/CascadePreview.d.ts.map +1 -0
- package/dist/client/components/CascadePreview.js +7 -0
- package/dist/client/components/CascadePreview.js.map +1 -0
- package/dist/client/components/DangerZoneCard.d.ts +10 -0
- package/dist/client/components/DangerZoneCard.d.ts.map +1 -0
- package/dist/client/components/DangerZoneCard.js +28 -0
- package/dist/client/components/DangerZoneCard.js.map +1 -0
- package/dist/client/components/InvitationCodeDisplay.d.ts +7 -0
- package/dist/client/components/InvitationCodeDisplay.d.ts.map +1 -0
- package/dist/client/components/InvitationCodeDisplay.js +26 -0
- package/dist/client/components/InvitationCodeDisplay.js.map +1 -0
- package/dist/client/components/InviteForm.d.ts +7 -0
- package/dist/client/components/InviteForm.d.ts.map +1 -0
- package/dist/client/components/InviteForm.js +35 -0
- package/dist/client/components/InviteForm.js.map +1 -0
- package/dist/client/components/MemberRow.d.ts +10 -0
- package/dist/client/components/MemberRow.d.ts.map +1 -0
- package/dist/client/components/MemberRow.js +17 -0
- package/dist/client/components/MemberRow.js.map +1 -0
- package/dist/client/components/OrgPeopleRoster.d.ts +11 -0
- package/dist/client/components/OrgPeopleRoster.d.ts.map +1 -0
- package/dist/client/components/OrgPeopleRoster.js +13 -0
- package/dist/client/components/OrgPeopleRoster.js.map +1 -0
- package/dist/client/components/PendingTransferBanner.d.ts +10 -0
- package/dist/client/components/PendingTransferBanner.d.ts.map +1 -0
- package/dist/client/components/PendingTransferBanner.js +48 -0
- package/dist/client/components/PendingTransferBanner.js.map +1 -0
- package/dist/client/components/PlaceholderCard.d.ts +7 -0
- package/dist/client/components/PlaceholderCard.d.ts.map +1 -0
- package/dist/client/components/PlaceholderCard.js +15 -0
- package/dist/client/components/PlaceholderCard.js.map +1 -0
- package/dist/client/components/RoleBadge.d.ts +5 -0
- package/dist/client/components/RoleBadge.d.ts.map +1 -0
- package/dist/client/components/RoleBadge.js +17 -0
- package/dist/client/components/RoleBadge.js.map +1 -0
- package/dist/client/components/RoleSelect.d.ts +10 -0
- package/dist/client/components/RoleSelect.d.ts.map +1 -0
- package/dist/client/components/RoleSelect.js +12 -0
- package/dist/client/components/RoleSelect.js.map +1 -0
- package/dist/client/components/SeatUsagePanel.d.ts +6 -0
- package/dist/client/components/SeatUsagePanel.d.ts.map +1 -0
- package/dist/client/components/SeatUsagePanel.js +13 -0
- package/dist/client/components/SeatUsagePanel.js.map +1 -0
- package/dist/client/hooks/useCurrentMembership.d.ts +8 -0
- package/dist/client/hooks/useCurrentMembership.d.ts.map +1 -0
- package/dist/client/hooks/useCurrentMembership.js +20 -0
- package/dist/client/hooks/useCurrentMembership.js.map +1 -0
- package/dist/client/hooks/useMembers.d.ts +10 -0
- package/dist/client/hooks/useMembers.d.ts.map +1 -0
- package/dist/client/hooks/useMembers.js +20 -0
- package/dist/client/hooks/useMembers.js.map +1 -0
- package/dist/client/hooks/useOrgMembers.d.ts +20 -0
- package/dist/client/hooks/useOrgMembers.d.ts.map +1 -0
- package/dist/client/hooks/useOrgMembers.js +63 -0
- package/dist/client/hooks/useOrgMembers.js.map +1 -0
- package/dist/client/hooks/usePendingInvitations.d.ts +8 -0
- package/dist/client/hooks/usePendingInvitations.d.ts.map +1 -0
- package/dist/client/hooks/usePendingInvitations.js +20 -0
- package/dist/client/hooks/usePendingInvitations.js.map +1 -0
- package/dist/client/hooks/usePendingTransfer.d.ts +8 -0
- package/dist/client/hooks/usePendingTransfer.d.ts.map +1 -0
- package/dist/client/hooks/usePendingTransfer.js +23 -0
- package/dist/client/hooks/usePendingTransfer.js.map +1 -0
- package/dist/client/index.d.ts +31 -0
- package/dist/client/index.d.ts.map +1 -0
- package/{src/client/index.ts → dist/client/index.js} +6 -54
- package/dist/client/index.js.map +1 -0
- package/dist/client/pages/AuditLogPage.d.ts +6 -0
- package/dist/client/pages/AuditLogPage.d.ts.map +1 -0
- package/dist/client/pages/AuditLogPage.js +51 -0
- package/dist/client/pages/AuditLogPage.js.map +1 -0
- package/dist/client/pages/EmailChangePage.d.ts +8 -0
- package/dist/client/pages/EmailChangePage.d.ts.map +1 -0
- package/dist/client/pages/EmailChangePage.js +52 -0
- package/dist/client/pages/EmailChangePage.js.map +1 -0
- package/dist/client/pages/InvitationAcceptPage.d.ts +11 -0
- package/dist/client/pages/InvitationAcceptPage.d.ts.map +1 -0
- package/dist/client/pages/InvitationAcceptPage.js +42 -0
- package/dist/client/pages/InvitationAcceptPage.js.map +1 -0
- package/dist/client/pages/InvitationCodePage.d.ts +6 -0
- package/dist/client/pages/InvitationCodePage.d.ts.map +1 -0
- package/dist/client/pages/InvitationCodePage.js +28 -0
- package/dist/client/pages/InvitationCodePage.js.map +1 -0
- package/dist/client/pages/MembersPage.d.ts +6 -0
- package/dist/client/pages/MembersPage.d.ts.map +1 -0
- package/dist/client/pages/MembersPage.js +67 -0
- package/dist/client/pages/MembersPage.js.map +1 -0
- package/dist/client/pages/OrgPeoplePage.d.ts +14 -0
- package/dist/client/pages/OrgPeoplePage.d.ts.map +1 -0
- package/dist/client/pages/OrgPeoplePage.js +40 -0
- package/dist/client/pages/OrgPeoplePage.js.map +1 -0
- package/dist/client/pages/OrgSettingsPage.d.ts +6 -0
- package/dist/client/pages/OrgSettingsPage.d.ts.map +1 -0
- package/dist/client/pages/OrgSettingsPage.js +78 -0
- package/dist/client/pages/OrgSettingsPage.js.map +1 -0
- package/dist/client/pages/OwnershipTransferPage.d.ts +6 -0
- package/dist/client/pages/OwnershipTransferPage.d.ts.map +1 -0
- package/dist/client/pages/OwnershipTransferPage.js +68 -0
- package/dist/client/pages/OwnershipTransferPage.js.map +1 -0
- package/dist/client/pages/PasswordResetPage.d.ts +6 -0
- package/dist/client/pages/PasswordResetPage.d.ts.map +1 -0
- package/dist/client/pages/PasswordResetPage.js +34 -0
- package/dist/client/pages/PasswordResetPage.js.map +1 -0
- package/dist/client/pages/PasswordResetRequestPage.d.ts +2 -0
- package/dist/client/pages/PasswordResetRequestPage.d.ts.map +1 -0
- package/dist/client/pages/PasswordResetRequestPage.js +27 -0
- package/dist/client/pages/PasswordResetRequestPage.js.map +1 -0
- package/dist/client/pages/PlaceholderPage.d.ts +7 -0
- package/dist/client/pages/PlaceholderPage.d.ts.map +1 -0
- package/dist/client/pages/PlaceholderPage.js +16 -0
- package/dist/client/pages/PlaceholderPage.js.map +1 -0
- package/dist/client/pages/SuperAdminDashboard.d.ts +2 -0
- package/dist/client/pages/SuperAdminDashboard.d.ts.map +1 -0
- package/dist/client/pages/SuperAdminDashboard.js +123 -0
- package/dist/client/pages/SuperAdminDashboard.js.map +1 -0
- package/dist/client/theme.d.ts +12 -0
- package/dist/client/theme.d.ts.map +1 -0
- package/dist/client/theme.js +16 -0
- package/dist/client/theme.js.map +1 -0
- package/dist/client/types.d.ts +78 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/org-admin.d.ts +45 -0
- package/dist/server/org-admin.d.ts.map +1 -0
- package/dist/server/org-admin.js +63 -0
- package/dist/server/org-admin.js.map +1 -0
- package/dist/server/routes/orgs.routes.d.ts.map +1 -1
- package/dist/server/routes/orgs.routes.js +81 -12
- package/dist/server/routes/orgs.routes.js.map +1 -1
- package/dist/server/types.d.ts +2 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js.map +1 -1
- package/package.json +18 -11
- package/.eslintrc.cjs +0 -18
- package/CHANGELOG.md +0 -159
- package/src/client/api.ts +0 -314
- package/src/client/components/AuditEventRow.tsx +0 -59
- package/src/client/components/CascadePreview.tsx +0 -36
- package/src/client/components/DangerZoneCard.tsx +0 -103
- package/src/client/components/InvitationCodeDisplay.tsx +0 -48
- package/src/client/components/InviteForm.tsx +0 -77
- package/src/client/components/MemberRow.tsx +0 -69
- package/src/client/components/PendingTransferBanner.tsx +0 -98
- package/src/client/components/PlaceholderCard.tsx +0 -26
- package/src/client/components/RoleBadge.tsx +0 -26
- package/src/client/components/RoleSelect.tsx +0 -35
- package/src/client/hooks/.gitkeep +0 -0
- package/src/client/hooks/useCurrentMembership.ts +0 -24
- package/src/client/hooks/useMembers.ts +0 -24
- package/src/client/hooks/usePendingInvitations.ts +0 -24
- package/src/client/hooks/usePendingTransfer.ts +0 -27
- package/src/client/pages/AuditLogPage.tsx +0 -164
- package/src/client/pages/EmailChangePage.tsx +0 -144
- package/src/client/pages/InvitationAcceptPage.tsx +0 -163
- package/src/client/pages/InvitationCodePage.tsx +0 -108
- package/src/client/pages/MembersPage.tsx +0 -290
- package/src/client/pages/OrgSettingsPage.tsx +0 -185
- package/src/client/pages/OwnershipTransferPage.tsx +0 -163
- package/src/client/pages/PasswordResetPage.tsx +0 -104
- package/src/client/pages/PasswordResetRequestPage.tsx +0 -71
- package/src/client/pages/PlaceholderPage.tsx +0 -20
- package/src/client/pages/SuperAdminDashboard.tsx +0 -401
- package/src/client/types.ts +0 -78
- package/src/index.ts +0 -24
- package/src/server/crypto.ts +0 -47
- package/src/server/index.ts +0 -167
- package/src/server/middleware/require-membership.ts +0 -48
- package/src/server/middleware/require-role.ts +0 -19
- package/src/server/middleware/require-super-admin.ts +0 -32
- package/src/server/migrations/0001_create_tm_schema_migrations.sql +0 -13
- package/src/server/migrations/0002_create_tm_organizations.sql +0 -14
- package/src/server/migrations/0003_create_tm_memberships.sql +0 -24
- package/src/server/migrations/0004_create_tm_invitations.sql +0 -22
- package/src/server/migrations/0005_create_tm_audit_events.sql +0 -17
- package/src/server/migrations/0006_create_tm_email_change_requests.sql +0 -13
- package/src/server/migrations/0007_create_tm_ownership_transfers.sql +0 -22
- package/src/server/migrations/0008_create_tm_super_admins.sql +0 -8
- package/src/server/migrations/0009_create_tm_password_reset_requests.sql +0 -9
- package/src/server/migrations/0010_create_tm_shared_access.sql +0 -8
- package/src/server/migrations/0011_seed_super_admin.sql +0 -15
- package/src/server/migrations/0012_create_tm_user_locks.sql +0 -7
- package/src/server/routes/admin.routes.ts +0 -208
- package/src/server/routes/audit.routes.ts +0 -93
- package/src/server/routes/health.routes.ts +0 -46
- package/src/server/routes/invitations.routes.ts +0 -252
- package/src/server/routes/me.routes.ts +0 -143
- package/src/server/routes/orgs.routes.ts +0 -428
- package/src/server/routes/transfer.routes.ts +0 -110
- package/src/server/services/.gitkeep +0 -0
- package/src/server/services/audit.service.ts +0 -49
- package/src/server/services/email-change.service.ts +0 -178
- package/src/server/services/invitations.service.ts +0 -316
- package/src/server/services/memberships.service.ts +0 -129
- package/src/server/services/organizations.service.ts +0 -110
- package/src/server/services/ownership.service.ts +0 -170
- package/src/server/services/password-reset.service.ts +0 -94
- package/src/server/services/super-admin.service.ts +0 -321
- package/src/server/sql/.gitkeep +0 -0
- package/src/server/types.ts +0 -145
- package/src/shared/types.ts +0 -24
- package/tests/integration/audit-fires.test.ts +0 -288
- package/tests/integration/cascade-preview.test.ts +0 -157
- package/tests/integration/email-change.test.ts +0 -190
- package/tests/integration/feature-flags.test.ts +0 -213
- package/tests/integration/invitations-code.test.ts +0 -218
- package/tests/integration/invitations-expiry.test.ts +0 -216
- package/tests/integration/invitations-resend.test.ts +0 -241
- package/tests/integration/invitations-revoke.test.ts +0 -226
- package/tests/integration/invitations-switch-org.test.ts +0 -156
- package/tests/integration/invitations-token.test.ts +0 -221
- package/tests/integration/migrations.test.ts +0 -119
- package/tests/integration/only-owner-protections.test.ts +0 -130
- package/tests/integration/org-lifecycle.test.ts +0 -169
- package/tests/integration/ownership-transfer-cancel.test.ts +0 -171
- package/tests/integration/ownership-transfer-expire.test.ts +0 -171
- package/tests/integration/ownership-transfer-happy.test.ts +0 -184
- package/tests/integration/ownership-transfer-locks.test.ts +0 -146
- package/tests/integration/password-reset.test.ts +0 -200
- package/tests/integration/super-admin-actions.test.ts +0 -180
- package/tests/integration/super-admin-restrictions.test.ts +0 -209
- package/tests/setup/global-setup.ts +0 -20
- package/tests/unit/adapter-shape.test.ts +0 -330
- package/tests/unit/role-permissions.test.ts +0 -236
- package/tests/unit/validation.test.ts +0 -304
- package/tsconfig.client.json +0 -13
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
- 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
|
-
}
|