@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
package/package.json
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@varshylinc/team-management",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Team management shared module for Varshyl products.",
|
|
5
5
|
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
6
10
|
"main": "./dist/index.js",
|
|
7
11
|
"types": "./dist/index.d.ts",
|
|
8
12
|
"exports": {
|
|
9
13
|
".": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"require": "./dist/index.js",
|
|
16
|
+
"import": "./dist/index.js"
|
|
12
17
|
},
|
|
13
18
|
"./server": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
19
|
+
"types": "./dist/server/index.d.ts",
|
|
20
|
+
"require": "./dist/server/index.js",
|
|
21
|
+
"import": "./dist/server/index.js"
|
|
16
22
|
},
|
|
17
23
|
"./client": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
24
|
+
"types": "./dist/client/index.d.ts",
|
|
25
|
+
"require": "./dist/client/index.js",
|
|
26
|
+
"import": "./dist/client/index.js"
|
|
20
27
|
}
|
|
21
28
|
},
|
|
22
29
|
"dependencies": {
|
|
@@ -40,16 +47,16 @@
|
|
|
40
47
|
},
|
|
41
48
|
"peerDependencies": {
|
|
42
49
|
"express": "^4.18.3",
|
|
43
|
-
"pg": "^8.11.5"
|
|
50
|
+
"pg": "^8.11.5",
|
|
51
|
+
"react": "^18.0.0"
|
|
44
52
|
},
|
|
45
|
-
"type": "module",
|
|
46
53
|
"publishConfig": {
|
|
47
54
|
"access": "public",
|
|
48
55
|
"registry": "https://registry.npmjs.org/"
|
|
49
56
|
},
|
|
50
57
|
"scripts": {
|
|
51
|
-
"build": "tsc -p tsconfig.json && mkdir -p dist/server/migrations && cp src/server/migrations/*.sql dist/server/migrations/",
|
|
52
|
-
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json",
|
|
58
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.build.json && mkdir -p dist/server/migrations && cp src/server/migrations/*.sql dist/server/migrations/",
|
|
59
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
53
60
|
"test": "vitest run",
|
|
54
61
|
"lint": "eslint src --ext .ts,.tsx --max-warnings 0"
|
|
55
62
|
}
|
package/.eslintrc.cjs
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
root: true,
|
|
3
|
-
parser: '@typescript-eslint/parser',
|
|
4
|
-
parserOptions: {
|
|
5
|
-
project: ['./tsconfig.json', './tsconfig.client.json'],
|
|
6
|
-
tsconfigRootDir: __dirname,
|
|
7
|
-
},
|
|
8
|
-
plugins: ['@typescript-eslint'],
|
|
9
|
-
extends: [
|
|
10
|
-
'eslint:recommended',
|
|
11
|
-
'plugin:@typescript-eslint/recommended',
|
|
12
|
-
],
|
|
13
|
-
rules: {
|
|
14
|
-
'@typescript-eslint/no-explicit-any': 'warn',
|
|
15
|
-
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
16
|
-
},
|
|
17
|
-
env: { node: true, browser: true, es2022: true },
|
|
18
|
-
};
|
package/CHANGELOG.md
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# Changelog — @varshylinc/team-management
|
|
2
|
-
|
|
3
|
-
## 0.1.0 — 2026-05-08
|
|
4
|
-
|
|
5
|
-
First production-grade release. v0.0.1 was a stub with no business logic; v0.1.0 is the first version products can actually consume — a complete team management module with org model, RBAC, invitations, audit log, ownership transfer, email change, password reset, and super-admin back office.
|
|
6
|
-
|
|
7
|
-
### Added
|
|
8
|
-
|
|
9
|
-
**Org management**
|
|
10
|
-
- `POST /orgs` — create organization (name, slug)
|
|
11
|
-
- `GET /orgs/:id` — fetch org details
|
|
12
|
-
- `PATCH /orgs/:id` — update org name and slug (admin+)
|
|
13
|
-
- `DELETE /orgs/:id` — soft-delete with 30-day grace period; must type org name to confirm (owner only)
|
|
14
|
-
- Org slug unique partial index (active orgs only)
|
|
15
|
-
- Soft-delete columns: `deleted_at`, `delete_scheduled_for`, `deleted_by_user_id`
|
|
16
|
-
|
|
17
|
-
**Member management**
|
|
18
|
-
- `GET /orgs/:id/members` — list active members with roles
|
|
19
|
-
- `GET /orgs/:id/members/former` — list removed members with removal metadata
|
|
20
|
-
- `DELETE /orgs/:id/members/:userId` — soft-remove member; records `removed_by_user_id` and `removal_reason`
|
|
21
|
-
- `PATCH /orgs/:id/members/:userId` — change member role (role-gated per matrix)
|
|
22
|
-
- `GET /orgs/:id/members/:userId/cascade-preview` — preview what will be affected by removal
|
|
23
|
-
|
|
24
|
-
**Invitations** (flag: `enableInvites`)
|
|
25
|
-
- `POST /orgs/:id/invitations` — invite by email; sends magic link + 6-digit code in same email; 7-day expiry
|
|
26
|
-
- `GET /orgs/:id/invitations` — list pending invitations (admin+)
|
|
27
|
-
- `DELETE /orgs/:id/invitations/:invId` — revoke pending invitation (admin+)
|
|
28
|
-
- `POST /orgs/:id/invitations/:invId/resend` — regenerate token + code, invalidate old (admin+)
|
|
29
|
-
- `GET /orgs/:id/invitations/:invId/code` — fetch decrypted 6-digit code for phone fallback (admin+)
|
|
30
|
-
- `GET /invitations/accept/:token` — public magic-link landing; shows org, role, inviter
|
|
31
|
-
- `POST /invitations/accept/code` — public code-entry; `{email, code}`
|
|
32
|
-
- Sub-A enforcement: member accepting while already in an org gets warning; owner is blocked entirely
|
|
33
|
-
|
|
34
|
-
**Audit log** (flag: `enableAuditLog`)
|
|
35
|
-
- `GET /orgs/:id/audit` — paginated audit events (admin+ only)
|
|
36
|
-
- 13 event types: `org.created`, `org.settings.updated`, `org.deleted`, `member.invited`, `member.invite_accepted`, `member.invite_revoked`, `member.removed`, `member.role_changed`, `ownership.transfer_initiated`, `ownership.transfer_accepted`, `ownership.transfer_cancelled`, `email.change_requested`, `email.change_completed`
|
|
37
|
-
- Each event stores: `actor_user_id`, `actor_type` (`user`/`super_admin`), `action`, `target_type`, `target_id`, `before` JSONB, `after` JSONB, `ip`, `user_agent`, `reason`, `created_at`
|
|
38
|
-
- Super-admin events appear as "Varshyl Support" (real identity never exposed to org)
|
|
39
|
-
|
|
40
|
-
**Ownership transfer** (flag: `enableOwnershipTransfer`)
|
|
41
|
-
- `POST /orgs/:id/transfer` — initiate transfer to an admin (owner only; target must already be admin)
|
|
42
|
-
- `GET /orgs/:id/transfer` — get pending transfer details
|
|
43
|
-
- `POST /orgs/:id/transfer/:transferId/accept` — accept transfer (atomic role swap)
|
|
44
|
-
- `DELETE /orgs/:id/transfer/:transferId` — cancel transfer (either party)
|
|
45
|
-
- Transfer locks: no second transfer, no org delete, no party removal during pending transfer
|
|
46
|
-
- 7-day expiration; auto-cancelled on expiry
|
|
47
|
-
|
|
48
|
-
**Email change** (flag: `enableEmailChange`)
|
|
49
|
-
- `POST /me/email-change` — request email change; sends verification to new + notice to old
|
|
50
|
-
- `GET /me/email-change/verify` — verify new email via token
|
|
51
|
-
- `POST /me/email-change/cancel` — cancel via old-email cancellation link; triggers mandatory password reset
|
|
52
|
-
- Rate limit: 3 requests per user per 24 hours
|
|
53
|
-
- Old-email notice contains cancel link with mandatory-reset semantics if clicked
|
|
54
|
-
|
|
55
|
-
**Password reset** (flag: `enablePasswordReset`)
|
|
56
|
-
- `POST /me/password-reset/request` — self-service; always returns 200 (no user enumeration)
|
|
57
|
-
- `POST /me/password-reset/confirm` — confirm with token + new password
|
|
58
|
-
- Rate limit: 3 requests per email per hour
|
|
59
|
-
- 1-hour token expiry
|
|
60
|
-
|
|
61
|
-
**Super-admin back office** (flag: `enableSuperAdmin`, default OFF)
|
|
62
|
-
- `GET /admin/orgs` — list all orgs including deleted
|
|
63
|
-
- `GET /admin/orgs/:id` — org details + members
|
|
64
|
-
- `POST /admin/orgs/:id/restore` — restore soft-deleted org during grace period
|
|
65
|
-
- `POST /admin/orgs/:id/appoint-owner` — appoint new owner (requires `reason`)
|
|
66
|
-
- `POST /admin/orgs/:id/hard-delete` — legal-compliance hard delete (requires `legal_basis`)
|
|
67
|
-
- `POST /admin/orgs/:id/members/add` — add member to any org (requires `reason`)
|
|
68
|
-
- `POST /admin/orgs/:id/members/remove` — remove member from any org (requires `reason`)
|
|
69
|
-
- `POST /admin/users/:id/lock` — lock user account (requires `reason`)
|
|
70
|
-
- `POST /admin/users/:id/unlock` — unlock account
|
|
71
|
-
- `POST /admin/users/:id/password-reset` — trigger reset link; super-admin never sees password
|
|
72
|
-
- All super-admin actions require `reason` text; write audit event with `actor_type='super_admin'`
|
|
73
|
-
- Cannot impersonate users; cannot modify product data; cannot set passwords directly
|
|
74
|
-
|
|
75
|
-
**Self-service**
|
|
76
|
-
- `GET /me/membership` — current user's org membership info
|
|
77
|
-
|
|
78
|
-
**Health**
|
|
79
|
-
- `GET /health` — module health with flag states
|
|
80
|
-
|
|
81
|
-
### Adapter contract changes
|
|
82
|
-
|
|
83
|
-
11 new required methods added to `ServerModuleAdapter` (all hosts must implement):
|
|
84
|
-
|
|
85
|
-
| Method | Purpose |
|
|
86
|
-
|--------|---------|
|
|
87
|
-
| `getUserById(userId)` | Look up user by ID |
|
|
88
|
-
| `getUsersByIds(userIds[])` | Batch user lookup |
|
|
89
|
-
| `findUserByEmail(email)` | Look up user by email |
|
|
90
|
-
| `createUserFromInvite({email, orgId, role})` | Create user on invite accept |
|
|
91
|
-
| `setUserPassword(userId, hash)` | Store new password hash |
|
|
92
|
-
| `hashPassword(plaintext)` | Hash a password |
|
|
93
|
-
| `verifyPassword(plaintext, hash)` | Verify password against hash |
|
|
94
|
-
| `invalidateAllUserSessions(userId)` | Invalidate all sessions for user |
|
|
95
|
-
| `sendInviteEmail({to, orgName, inviterName, role, magicLinkUrl, code})` | Send invitation email |
|
|
96
|
-
| `sendOwnershipTransferEmail({to, orgName, fromName, transferUrl})` | Send transfer notification |
|
|
97
|
-
| `sendEmailChangeVerification({to, verifyUrl})` | Send verification to new email |
|
|
98
|
-
| `sendEmailChangeOldNotice({to, newEmail, cancelUrl})` | Send notice to old email |
|
|
99
|
-
| `sendEmailChangedFinalNotice({to, oldEmail, newEmail})` | Send final confirmation |
|
|
100
|
-
| `sendPasswordResetEmail({to, resetUrl})` | Send reset link |
|
|
101
|
-
| `sendOrgDeletionNotice({to, orgName, scheduledFor})` | Notify members on org delete |
|
|
102
|
-
| `emitNotification({userId, type, payload})` | In-app notification hook |
|
|
103
|
-
|
|
104
|
-
### Migrations included
|
|
105
|
-
|
|
106
|
-
| File | Description |
|
|
107
|
-
|------|-------------|
|
|
108
|
-
| `0001_create_tm_schema_migrations.sql` | Migration ledger table |
|
|
109
|
-
| `0002_create_tm_organizations.sql` | Organizations table |
|
|
110
|
-
| `0003_create_tm_memberships.sql` | Memberships with soft-delete columns |
|
|
111
|
-
| `0004_create_tm_invitations.sql` | Invitations with encrypted code storage |
|
|
112
|
-
| `0005_create_tm_audit_events.sql` | Audit events log |
|
|
113
|
-
| `0006_create_tm_email_change_requests.sql` | Email change requests |
|
|
114
|
-
| `0007_create_tm_ownership_transfers.sql` | Ownership transfer requests |
|
|
115
|
-
| `0008_create_tm_super_admins.sql` | Super-admin registry |
|
|
116
|
-
| `0009_create_tm_password_reset_requests.sql` | Password reset tokens |
|
|
117
|
-
| `0010_create_tm_shared_access.sql` | Shared access scaffold (empty, v0.2.0) |
|
|
118
|
-
| `0011_seed_super_admin.sql` | Super-admin seed (idempotent, flag-gated) |
|
|
119
|
-
|
|
120
|
-
All migrations are forward-only, idempotent (`IF NOT EXISTS`), and tracked in `tm_schema_migrations`.
|
|
121
|
-
|
|
122
|
-
### Feature flags introduced
|
|
123
|
-
|
|
124
|
-
| Flag | Default | Description |
|
|
125
|
-
|------|---------|-------------|
|
|
126
|
-
| `enableInvites` | `true` | Invitation flows (magic link + code) |
|
|
127
|
-
| `enableAuditLog` | `true` | Audit log table and routes |
|
|
128
|
-
| `enableOwnershipTransfer` | `true` | Ownership transfer flow |
|
|
129
|
-
| `enableEmailChange` | `true` | Self-service email change |
|
|
130
|
-
| `enablePasswordReset` | `true` | Self-service password reset |
|
|
131
|
-
| `enableSuperAdmin` | `false` | Super-admin back office (Varshyl internal) |
|
|
132
|
-
| `enableSharedAccess` | `false` | Shared access scaffold (reserved v0.2.0) |
|
|
133
|
-
| `enableHardDelete` | `false` | Super-admin direct hard-delete |
|
|
134
|
-
|
|
135
|
-
Routes return `501 Not Implemented` when their flag is off.
|
|
136
|
-
|
|
137
|
-
### Locked design decisions (set in v0.1.0; future major bumps may revise)
|
|
138
|
-
|
|
139
|
-
**Decision 1 — Org model:** Every user belongs to exactly one org at a time. Solo accounts = org of one. `tm_shared_access` scaffolded empty (v0.2.0 placeholder). Multi-contractor vendor case handled by existing vendor-invite system in ConstructIInv (out of scope).
|
|
140
|
-
|
|
141
|
-
**Decision 2 — Roles:** Four roles: `owner` (unique per org, billing, delete, transfer), `admin` (invite/remove/settings, no audit-visible super-admin identity), `member` (day-to-day, no team powers), `viewer` (read-only). Permission matrix enforced at middleware level.
|
|
142
|
-
|
|
143
|
-
**Decision 3 — Audit log:** Stored forever. Visible to admin+ only. 13 event types. Super-admin entries visible as "Varshyl Support" — real identity never exposed.
|
|
144
|
-
|
|
145
|
-
**Decision 4 — Invitations:** Magic link + 6-digit code in same email. AES-256-GCM encryption for codes. 7-day expiry. Sub-A: member accepting into new org gets explicit warning + atomic switch; owner is blocked until they transfer or delete current org.
|
|
146
|
-
|
|
147
|
-
**Decision 5 — Deletion:** Member removal is soft (`removed_at`). Org deletion is soft with 30-day grace; confirm by typing org name. Background sweep hard-deletes after grace period.
|
|
148
|
-
|
|
149
|
-
**Decision 6 — Email change:** Verification to new address + immediate notice to old. Cancel from old address triggers mandatory password reset. Rate limit: 3 per user per 24h.
|
|
150
|
-
|
|
151
|
-
**Decision 7 — Ownership transfer:** Target must already be admin. Confirm by typing org name (initiator) and email (recipient). Atomic role swap on accept. Locks: no second transfer, no org delete, no party removal.
|
|
152
|
-
|
|
153
|
-
**Decision 8 — Super-admin:** Flag-gated (default OFF). Seeded from `TM_SUPER_ADMIN_EMAIL` env. Cannot impersonate, cannot modify product data, cannot set passwords. All actions require reason text.
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## v0.0.1 — 2026-04-28
|
|
158
|
-
|
|
159
|
-
Initial stub. Module scaffolding only — no business logic.
|
package/src/client/api.ts
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
PublicOrg,
|
|
3
|
-
PublicMember,
|
|
4
|
-
PendingInvitation,
|
|
5
|
-
CurrentMembership,
|
|
6
|
-
OwnershipTransfer,
|
|
7
|
-
AuditEvent,
|
|
8
|
-
SuperAdminOrgSummary,
|
|
9
|
-
OrgRole,
|
|
10
|
-
ApiError,
|
|
11
|
-
} from './types.js';
|
|
12
|
-
|
|
13
|
-
export let TM_API_BASE = '/api/team';
|
|
14
|
-
|
|
15
|
-
export function setTmApiBase(base: string): void {
|
|
16
|
-
TM_API_BASE = base;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function fetchTm<T>(
|
|
20
|
-
path: string,
|
|
21
|
-
options: RequestInit = {}
|
|
22
|
-
): Promise<T> {
|
|
23
|
-
const res = await fetch(`${TM_API_BASE}${path}`, {
|
|
24
|
-
...options,
|
|
25
|
-
headers: {
|
|
26
|
-
'Content-Type': 'application/json',
|
|
27
|
-
...(options.headers ?? {}),
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
if (res.status === 204) {
|
|
32
|
-
return undefined as unknown as T;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const data = await res.json().catch(() => ({ error: res.statusText }));
|
|
36
|
-
|
|
37
|
-
if (!res.ok) {
|
|
38
|
-
const err = data as ApiError;
|
|
39
|
-
const msg = err.details ? `${err.error}: ${err.details.join(', ')}` : err.error;
|
|
40
|
-
throw new Error(msg ?? `HTTP ${res.status}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return data as T;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ─── Orgs ────────────────────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
export async function getOrg(orgId: number): Promise<PublicOrg> {
|
|
49
|
-
return fetchTm<PublicOrg>(`/orgs/${orgId}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function updateOrg(
|
|
53
|
-
orgId: number,
|
|
54
|
-
data: { name?: string; slug?: string }
|
|
55
|
-
): Promise<PublicOrg> {
|
|
56
|
-
return fetchTm<PublicOrg>(`/orgs/${orgId}`, {
|
|
57
|
-
method: 'PATCH',
|
|
58
|
-
body: JSON.stringify(data),
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function deleteOrg(orgId: number, confirmName: string): Promise<void> {
|
|
63
|
-
return fetchTm<void>(`/orgs/${orgId}`, {
|
|
64
|
-
method: 'DELETE',
|
|
65
|
-
body: JSON.stringify({ confirmName }),
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export async function listMembers(
|
|
70
|
-
orgId: number,
|
|
71
|
-
opts?: { includeFormer?: boolean }
|
|
72
|
-
): Promise<PublicMember[]> {
|
|
73
|
-
const qs = opts?.includeFormer ? '?includeFormer=true' : '';
|
|
74
|
-
return fetchTm<PublicMember[]>(`/orgs/${orgId}/members${qs}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function removeMember(
|
|
78
|
-
orgId: number,
|
|
79
|
-
userId: number,
|
|
80
|
-
reason?: string
|
|
81
|
-
): Promise<void> {
|
|
82
|
-
return fetchTm<void>(`/orgs/${orgId}/members/${userId}`, {
|
|
83
|
-
method: 'DELETE',
|
|
84
|
-
body: JSON.stringify({ reason }),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function changeMemberRole(
|
|
89
|
-
orgId: number,
|
|
90
|
-
userId: number,
|
|
91
|
-
newRole: OrgRole
|
|
92
|
-
): Promise<PublicMember> {
|
|
93
|
-
return fetchTm<PublicMember>(`/orgs/${orgId}/members/${userId}/role`, {
|
|
94
|
-
method: 'PATCH',
|
|
95
|
-
body: JSON.stringify({ role: newRole }),
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ─── Invitations ─────────────────────────────────────────────────────────────
|
|
100
|
-
|
|
101
|
-
export async function listInvitations(orgId: number): Promise<PendingInvitation[]> {
|
|
102
|
-
return fetchTm<PendingInvitation[]>(`/orgs/${orgId}/invitations`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function createInvitation(
|
|
106
|
-
orgId: number,
|
|
107
|
-
data: { email: string; role: OrgRole }
|
|
108
|
-
): Promise<PendingInvitation> {
|
|
109
|
-
return fetchTm<PendingInvitation>(`/orgs/${orgId}/invitations`, {
|
|
110
|
-
method: 'POST',
|
|
111
|
-
body: JSON.stringify(data),
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export async function revokeInvitation(orgId: number, invitationId: number): Promise<void> {
|
|
116
|
-
return fetchTm<void>(`/orgs/${orgId}/invitations/${invitationId}`, {
|
|
117
|
-
method: 'DELETE',
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export async function resendInvitation(orgId: number, invitationId: number): Promise<void> {
|
|
122
|
-
return fetchTm<void>(`/orgs/${orgId}/invitations/${invitationId}/resend`, {
|
|
123
|
-
method: 'POST',
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export async function getInvitationCode(
|
|
128
|
-
orgId: number,
|
|
129
|
-
invitationId: number
|
|
130
|
-
): Promise<{ code: string }> {
|
|
131
|
-
return fetchTm<{ code: string }>(`/orgs/${orgId}/invitations/${invitationId}/code`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export async function acceptInvitationByToken(
|
|
135
|
-
token: string
|
|
136
|
-
): Promise<{ orgId: number; role: OrgRole }> {
|
|
137
|
-
return fetchTm<{ orgId: number; role: OrgRole }>(`/invitations/accept`, {
|
|
138
|
-
method: 'POST',
|
|
139
|
-
body: JSON.stringify({ token }),
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export async function acceptInvitationByCode(
|
|
144
|
-
email: string,
|
|
145
|
-
code: string
|
|
146
|
-
): Promise<{ orgId: number; role: OrgRole }> {
|
|
147
|
-
return fetchTm<{ orgId: number; role: OrgRole }>(`/invitations/accept-code`, {
|
|
148
|
-
method: 'POST',
|
|
149
|
-
body: JSON.stringify({ email, code }),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ─── Me / self-service ───────────────────────────────────────────────────────
|
|
154
|
-
|
|
155
|
-
export async function getMyMembership(): Promise<CurrentMembership> {
|
|
156
|
-
return fetchTm<CurrentMembership>(`/me/membership`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function requestEmailChange(newEmail: string): Promise<void> {
|
|
160
|
-
return fetchTm<void>(`/me/email-change`, {
|
|
161
|
-
method: 'POST',
|
|
162
|
-
body: JSON.stringify({ newEmail }),
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export async function verifyEmailChange(token: string): Promise<void> {
|
|
167
|
-
return fetchTm<void>(`/me/email-change/verify`, {
|
|
168
|
-
method: 'POST',
|
|
169
|
-
body: JSON.stringify({ token }),
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export async function cancelEmailChange(token: string): Promise<void> {
|
|
174
|
-
return fetchTm<void>(`/me/email-change/cancel`, {
|
|
175
|
-
method: 'POST',
|
|
176
|
-
body: JSON.stringify({ token }),
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export async function requestPasswordReset(email: string): Promise<void> {
|
|
181
|
-
return fetchTm<void>(`/password-reset/request`, {
|
|
182
|
-
method: 'POST',
|
|
183
|
-
body: JSON.stringify({ email }),
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export async function resetPassword(token: string, newPassword: string): Promise<void> {
|
|
188
|
-
return fetchTm<void>(`/password-reset/confirm`, {
|
|
189
|
-
method: 'POST',
|
|
190
|
-
body: JSON.stringify({ token, newPassword }),
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ─── Ownership transfer ───────────────────────────────────────────────────────
|
|
195
|
-
|
|
196
|
-
export async function getPendingTransfer(orgId: number): Promise<OwnershipTransfer | null> {
|
|
197
|
-
return fetchTm<OwnershipTransfer | null>(`/orgs/${orgId}/transfer`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export async function initiateTransfer(
|
|
201
|
-
orgId: number,
|
|
202
|
-
toUserId: number
|
|
203
|
-
): Promise<OwnershipTransfer> {
|
|
204
|
-
return fetchTm<OwnershipTransfer>(`/orgs/${orgId}/transfer`, {
|
|
205
|
-
method: 'POST',
|
|
206
|
-
body: JSON.stringify({ toUserId }),
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export async function acceptTransfer(orgId: number): Promise<void> {
|
|
211
|
-
return fetchTm<void>(`/orgs/${orgId}/transfer/accept`, {
|
|
212
|
-
method: 'POST',
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export async function cancelTransfer(orgId: number): Promise<void> {
|
|
217
|
-
return fetchTm<void>(`/orgs/${orgId}/transfer/cancel`, {
|
|
218
|
-
method: 'POST',
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ─── Audit log ───────────────────────────────────────────────────────────────
|
|
223
|
-
|
|
224
|
-
export async function getAuditLog(
|
|
225
|
-
orgId: number,
|
|
226
|
-
opts?: { page?: number; limit?: number; action?: string }
|
|
227
|
-
): Promise<{ events: AuditEvent[]; total: number; page: number }> {
|
|
228
|
-
const params = new URLSearchParams();
|
|
229
|
-
if (opts?.page !== undefined) params.set('page', String(opts.page));
|
|
230
|
-
if (opts?.limit !== undefined) params.set('limit', String(opts.limit));
|
|
231
|
-
if (opts?.action) params.set('action', opts.action);
|
|
232
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
233
|
-
return fetchTm<{ events: AuditEvent[]; total: number; page: number }>(
|
|
234
|
-
`/orgs/${orgId}/audit-log${qs}`
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// ─── Super-admin ─────────────────────────────────────────────────────────────
|
|
239
|
-
|
|
240
|
-
export async function adminListOrgs(): Promise<SuperAdminOrgSummary[]> {
|
|
241
|
-
return fetchTm<SuperAdminOrgSummary[]>(`/admin/orgs`);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export async function adminGetOrg(
|
|
245
|
-
orgId: number
|
|
246
|
-
): Promise<SuperAdminOrgSummary & { members: PublicMember[] }> {
|
|
247
|
-
return fetchTm<SuperAdminOrgSummary & { members: PublicMember[] }>(`/admin/orgs/${orgId}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export async function adminRestoreOrg(orgId: number): Promise<void> {
|
|
251
|
-
return fetchTm<void>(`/admin/orgs/${orgId}/restore`, { method: 'POST' });
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export async function adminAppointOwner(
|
|
255
|
-
orgId: number,
|
|
256
|
-
targetUserId: number,
|
|
257
|
-
reason: string
|
|
258
|
-
): Promise<void> {
|
|
259
|
-
return fetchTm<void>(`/admin/orgs/${orgId}/appoint-owner`, {
|
|
260
|
-
method: 'POST',
|
|
261
|
-
body: JSON.stringify({ targetUserId, reason }),
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export async function adminHardDeleteOrg(orgId: number, legalBasis: string): Promise<void> {
|
|
266
|
-
return fetchTm<void>(`/admin/orgs/${orgId}/hard-delete`, {
|
|
267
|
-
method: 'DELETE',
|
|
268
|
-
body: JSON.stringify({ legalBasis }),
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export async function adminAddMember(
|
|
273
|
-
orgId: number,
|
|
274
|
-
userId: number,
|
|
275
|
-
role: OrgRole,
|
|
276
|
-
reason: string
|
|
277
|
-
): Promise<void> {
|
|
278
|
-
return fetchTm<void>(`/admin/orgs/${orgId}/members`, {
|
|
279
|
-
method: 'POST',
|
|
280
|
-
body: JSON.stringify({ userId, role, reason }),
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export async function adminRemoveMember(
|
|
285
|
-
orgId: number,
|
|
286
|
-
userId: number,
|
|
287
|
-
reason: string
|
|
288
|
-
): Promise<void> {
|
|
289
|
-
return fetchTm<void>(`/admin/orgs/${orgId}/members/${userId}`, {
|
|
290
|
-
method: 'DELETE',
|
|
291
|
-
body: JSON.stringify({ reason }),
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export async function adminLockUser(userId: number, reason: string): Promise<void> {
|
|
296
|
-
return fetchTm<void>(`/admin/users/${userId}/lock`, {
|
|
297
|
-
method: 'POST',
|
|
298
|
-
body: JSON.stringify({ reason }),
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export async function adminUnlockUser(userId: number, reason: string): Promise<void> {
|
|
303
|
-
return fetchTm<void>(`/admin/users/${userId}/unlock`, {
|
|
304
|
-
method: 'POST',
|
|
305
|
-
body: JSON.stringify({ reason }),
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export async function adminResetPassword(userId: number, reason: string): Promise<void> {
|
|
310
|
-
return fetchTm<void>(`/admin/users/${userId}/reset-password`, {
|
|
311
|
-
method: 'POST',
|
|
312
|
-
body: JSON.stringify({ reason }),
|
|
313
|
-
});
|
|
314
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { AuditEvent } from '../types.js';
|
|
3
|
-
|
|
4
|
-
interface AuditEventRowProps {
|
|
5
|
-
event: AuditEvent;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function humanizeAction(action: string): string {
|
|
9
|
-
return action
|
|
10
|
-
.replace(/_/g, ' ')
|
|
11
|
-
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function formatTimestamp(iso: string): string {
|
|
15
|
-
return new Date(iso).toLocaleString(undefined, {
|
|
16
|
-
year: 'numeric',
|
|
17
|
-
month: 'short',
|
|
18
|
-
day: 'numeric',
|
|
19
|
-
hour: '2-digit',
|
|
20
|
-
minute: '2-digit',
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function AuditEventRow({ event }: AuditEventRowProps) {
|
|
25
|
-
const isAdminAction = event.actor_type === 'super_admin';
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<tr className="border-b border-slate-100 last:border-0 hover:bg-slate-50 transition-colors">
|
|
29
|
-
<td className="py-3 px-4">
|
|
30
|
-
<div className="flex items-center gap-2">
|
|
31
|
-
<span className={`text-sm font-medium ${isAdminAction ? 'text-purple-700' : 'text-slate-900'}`}>
|
|
32
|
-
{event.actor_display_name}
|
|
33
|
-
</span>
|
|
34
|
-
{isAdminAction && (
|
|
35
|
-
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-700">
|
|
36
|
-
Support
|
|
37
|
-
</span>
|
|
38
|
-
)}
|
|
39
|
-
</div>
|
|
40
|
-
</td>
|
|
41
|
-
<td className="py-3 px-4">
|
|
42
|
-
<span className="text-sm text-slate-700">{humanizeAction(event.action)}</span>
|
|
43
|
-
</td>
|
|
44
|
-
<td className="py-3 px-4">
|
|
45
|
-
{event.target_type && event.target_id ? (
|
|
46
|
-
<span className="text-xs text-slate-500">
|
|
47
|
-
{event.target_type} #{event.target_id}
|
|
48
|
-
</span>
|
|
49
|
-
) : (
|
|
50
|
-
<span className="text-slate-300">—</span>
|
|
51
|
-
)}
|
|
52
|
-
</td>
|
|
53
|
-
<td className="py-3 px-4 text-xs text-slate-500 whitespace-nowrap">
|
|
54
|
-
{formatTimestamp(event.created_at)}
|
|
55
|
-
</td>
|
|
56
|
-
</tr>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
interface CascadeItem {
|
|
4
|
-
label: string;
|
|
5
|
-
count: number;
|
|
6
|
-
description: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface CascadePreviewProps {
|
|
10
|
-
items: CascadeItem[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function CascadePreview({ items }: CascadePreviewProps) {
|
|
14
|
-
if (items.length === 0) return null;
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4">
|
|
18
|
-
<p className="text-sm font-semibold text-amber-800 mb-3">
|
|
19
|
-
The following will be affected:
|
|
20
|
-
</p>
|
|
21
|
-
<ul className="space-y-2">
|
|
22
|
-
{items.map((item, i) => (
|
|
23
|
-
<li key={i} className="flex items-start gap-3">
|
|
24
|
-
<span className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-amber-200 text-xs font-bold text-amber-800">
|
|
25
|
-
{item.count}
|
|
26
|
-
</span>
|
|
27
|
-
<div>
|
|
28
|
-
<span className="text-sm font-medium text-amber-900">{item.label}</span>
|
|
29
|
-
<p className="text-xs text-amber-700">{item.description}</p>
|
|
30
|
-
</div>
|
|
31
|
-
</li>
|
|
32
|
-
))}
|
|
33
|
-
</ul>
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
}
|