@checkstack/auth-frontend 0.1.0 → 0.3.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.
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback } from "react";
1
+ import React, { useState, useEffect } from "react";
2
2
  import {
3
3
  Card,
4
4
  CardHeader,
@@ -26,11 +26,12 @@ import {
26
26
  Settings,
27
27
  Lock,
28
28
  } from "lucide-react";
29
- import { useApi, rpcApiRef, permissionApiRef } from "@checkstack/frontend-api";
30
29
  import {
31
- AuthApi,
32
- permissions as authPermissions,
33
- } from "@checkstack/auth-common";
30
+ useApi,
31
+ usePluginClient,
32
+ accessApiRef,
33
+ } from "@checkstack/frontend-api";
34
+ import { AuthApi, authAccess } from "@checkstack/auth-common";
34
35
 
35
36
  interface TeamAccess {
36
37
  teamId: string;
@@ -71,145 +72,152 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
71
72
  compact = false,
72
73
  onChange,
73
74
  }) => {
74
- const rpcApi = useApi(rpcApiRef);
75
- const permissionApi = useApi(permissionApiRef);
76
- const authClient = rpcApi.forPlugin(AuthApi);
75
+ const accessApi = useApi(accessApiRef);
76
+ const authClient = usePluginClient(AuthApi);
77
77
  const toast = useToast();
78
78
 
79
- const { allowed: canManageTeams } = permissionApi.usePermission(
80
- authPermissions.teamsManage.id
79
+ const { allowed: canManageTeams } = accessApi.useAccess(
80
+ authAccess.teams.manage
81
81
  );
82
82
 
83
83
  const [expanded, setExpanded] = useState(initialExpanded);
84
- const [loading, setLoading] = useState(false);
85
- const [accessList, setAccessList] = useState<TeamAccess[]>([]);
86
- const [teams, setTeams] = useState<Team[]>([]);
87
84
  const [selectedTeamId, setSelectedTeamId] = useState("");
88
- const [adding, setAdding] = useState(false);
89
- const [teamOnly, setTeamOnly] = useState(false);
90
85
 
91
- const loadData = useCallback(async () => {
92
- setLoading(true);
93
- try {
94
- const [accessData, teamsData, settingsData] = await Promise.all([
95
- authClient.getResourceTeamAccess({ resourceType, resourceId }),
96
- authClient.getTeams(),
97
- authClient.getResourceAccessSettings({ resourceType, resourceId }),
98
- ]);
99
- setAccessList(accessData);
100
- setTeams(teamsData);
101
- setTeamOnly(settingsData.teamOnly);
102
- } catch (error) {
103
- toast.error(
104
- error instanceof Error ? error.message : "Failed to load team access"
105
- );
106
- } finally {
107
- setLoading(false);
108
- }
109
- }, [authClient, resourceType, resourceId, toast]);
86
+ // Query: Team access for this resource
87
+ const {
88
+ data: accessList = [],
89
+ isLoading: accessLoading,
90
+ refetch: refetchAccess,
91
+ } = authClient.getResourceTeamAccess.useQuery(
92
+ { resourceType, resourceId },
93
+ { enabled: expanded && !!resourceId }
94
+ );
110
95
 
111
- useEffect(() => {
112
- if (expanded && resourceId) {
113
- loadData();
114
- }
115
- }, [expanded, resourceId, loadData]);
96
+ // Query: All teams
97
+ const { data: teams = [], isLoading: teamsLoading } =
98
+ authClient.getTeams.useQuery({}, { enabled: expanded && !!resourceId });
116
99
 
117
- const handleAddTeam = async () => {
118
- if (!selectedTeamId) return;
100
+ // Query: Resource access settings (teamOnly flag)
101
+ const {
102
+ data: settings,
103
+ isLoading: settingsLoading,
104
+ refetch: refetchSettings,
105
+ } = authClient.getResourceAccessSettings.useQuery(
106
+ { resourceType, resourceId },
107
+ { enabled: expanded && !!resourceId }
108
+ );
119
109
 
120
- setAdding(true);
121
- try {
122
- await authClient.setResourceTeamAccess({
123
- resourceType,
124
- resourceId,
125
- teamId: selectedTeamId,
126
- canRead: true,
127
- canManage: false,
128
- });
129
- toast.success("Team access granted");
110
+ const loading = accessLoading || teamsLoading || settingsLoading;
111
+ const teamOnly = settings?.teamOnly ?? false;
112
+
113
+ // Mutations
114
+ const setAccessMutation = authClient.setResourceTeamAccess.useMutation({
115
+ onSuccess: () => {
116
+ toast.success("Team access updated");
130
117
  setSelectedTeamId("");
131
- await loadData();
118
+ void refetchAccess();
132
119
  onChange?.();
133
- } catch (error) {
120
+ },
121
+ onError: (error) => {
134
122
  toast.error(
135
- error instanceof Error ? error.message : "Failed to add team access"
123
+ error instanceof Error ? error.message : "Failed to update access"
136
124
  );
137
- } finally {
138
- setAdding(false);
139
- }
140
- };
125
+ },
126
+ });
141
127
 
142
- const handleUpdateAccess = async (
143
- teamId: string,
144
- updates: { canRead?: boolean; canManage?: boolean }
145
- ) => {
146
- try {
147
- await authClient.setResourceTeamAccess({
148
- resourceType,
149
- resourceId,
150
- teamId,
151
- ...updates,
152
- });
153
- await loadData();
128
+ const removeAccessMutation = authClient.removeResourceTeamAccess.useMutation({
129
+ onSuccess: () => {
130
+ toast.success("Team access removed");
131
+ void refetchAccess();
154
132
  onChange?.();
155
- } catch (error) {
133
+ },
134
+ onError: (error) => {
156
135
  toast.error(
157
- error instanceof Error ? error.message : "Failed to update access"
136
+ error instanceof Error ? error.message : "Failed to remove access"
158
137
  );
159
- }
160
- };
138
+ },
139
+ });
161
140
 
162
- const handleUpdateSettings = async (newTeamOnly: boolean) => {
163
- try {
164
- await authClient.setResourceAccessSettings({
165
- resourceType,
166
- resourceId,
167
- teamOnly: newTeamOnly,
168
- });
169
- setTeamOnly(newTeamOnly);
141
+ const setSettingsMutation = authClient.setResourceAccessSettings.useMutation({
142
+ onSuccess: () => {
143
+ void refetchSettings();
170
144
  onChange?.();
171
- } catch (error) {
145
+ },
146
+ onError: (error) => {
172
147
  toast.error(
173
148
  error instanceof Error ? error.message : "Failed to update settings"
174
149
  );
175
- }
150
+ },
151
+ });
152
+
153
+ // Reset teamOnly when all teams removed
154
+ useEffect(() => {
155
+ // This is handled reactively when mutations complete
156
+ }, []);
157
+
158
+ const handleAddTeam = () => {
159
+ if (!selectedTeamId) return;
160
+ setAccessMutation.mutate({
161
+ resourceType,
162
+ resourceId,
163
+ teamId: selectedTeamId,
164
+ canRead: true,
165
+ canManage: false,
166
+ });
167
+ };
168
+
169
+ const handleUpdateAccess = (
170
+ teamId: string,
171
+ updates: { canRead?: boolean; canManage?: boolean }
172
+ ) => {
173
+ const currentAccess = (accessList as TeamAccess[]).find(
174
+ (a) => a.teamId === teamId
175
+ );
176
+ setAccessMutation.mutate({
177
+ resourceType,
178
+ resourceId,
179
+ teamId,
180
+ canRead: updates.canRead ?? currentAccess?.canRead ?? true,
181
+ canManage: updates.canManage ?? currentAccess?.canManage ?? false,
182
+ });
183
+ };
184
+
185
+ const handleUpdateSettings = (newTeamOnly: boolean) => {
186
+ setSettingsMutation.mutate({
187
+ resourceType,
188
+ resourceId,
189
+ teamOnly: newTeamOnly,
190
+ });
176
191
  };
177
192
 
178
- const handleRemoveAccess = async (teamId: string) => {
179
- try {
180
- await authClient.removeResourceTeamAccess({
193
+ const handleRemoveAccess = (teamId: string) => {
194
+ removeAccessMutation.mutate({
195
+ resourceType,
196
+ resourceId,
197
+ teamId,
198
+ });
199
+
200
+ // Check if this was the last team - if so, reset teamOnly
201
+ const remainingTeams = (accessList as TeamAccess[]).filter(
202
+ (a) => a.teamId !== teamId
203
+ );
204
+ if (remainingTeams.length === 0 && teamOnly) {
205
+ setSettingsMutation.mutate({
181
206
  resourceType,
182
207
  resourceId,
183
- teamId,
208
+ teamOnly: false,
184
209
  });
185
-
186
- // Check if this was the last team - if so, clear settings too
187
- const remainingTeams = accessList.filter((a) => a.teamId !== teamId);
188
- if (remainingTeams.length === 0 && teamOnly) {
189
- // Reset teamOnly when no teams have access
190
- await authClient.setResourceAccessSettings({
191
- resourceType,
192
- resourceId,
193
- teamOnly: false,
194
- });
195
- setTeamOnly(false);
196
- }
197
-
198
- toast.success("Team access removed");
199
- await loadData();
200
- onChange?.();
201
- } catch (error) {
202
- toast.error(
203
- error instanceof Error ? error.message : "Failed to remove access"
204
- );
205
210
  }
206
211
  };
207
212
 
208
213
  // Get teams that don't already have access
209
- const availableTeams = teams.filter(
210
- (t) => !accessList.some((a) => a.teamId === t.id)
214
+ const typedAccessList = accessList as TeamAccess[];
215
+ const availableTeams = (teams as Team[]).filter(
216
+ (t) => !typedAccessList.some((a) => a.teamId === t.id)
211
217
  );
212
218
 
219
+ const adding = setAccessMutation.isPending;
220
+
213
221
  // Compact summary mode
214
222
  if (!expanded) {
215
223
  return (
@@ -223,9 +231,9 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
223
231
  >
224
232
  <Users2 className="h-4 w-4" />
225
233
  <span>Team Access</span>
226
- {accessList.length > 0 && (
234
+ {typedAccessList.length > 0 && (
227
235
  <Badge variant="secondary" className="ml-1">
228
- {accessList.length}
236
+ {typedAccessList.length}
229
237
  </Badge>
230
238
  )}
231
239
  </Button>
@@ -259,7 +267,7 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
259
267
  ) : (
260
268
  <>
261
269
  {/* Resource-level Team Only setting */}
262
- {accessList.length > 0 && (
270
+ {typedAccessList.length > 0 && (
263
271
  <div className="flex items-center justify-between p-2 bg-muted/30 rounded-md">
264
272
  <div className="flex items-center gap-2">
265
273
  <Lock className="h-4 w-4 text-muted-foreground" />
@@ -270,7 +278,7 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
270
278
  Team Only
271
279
  </Label>
272
280
  <span className="text-xs text-muted-foreground">
273
- (Bypass global permissions)
281
+ (Bypass global accesss)
274
282
  </span>
275
283
  </div>
276
284
  <Toggle
@@ -318,12 +326,12 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
318
326
 
319
327
  {/* Access list */}
320
328
  <div className="space-y-2">
321
- {accessList.length === 0 ? (
329
+ {typedAccessList.length === 0 ? (
322
330
  <p className="text-sm text-muted-foreground text-center py-2">
323
- No team restrictions. All users with permission can access.
331
+ No team restrictions. All users with access can access.
324
332
  </p>
325
333
  ) : (
326
- accessList.map((access) => (
334
+ typedAccessList.map((access) => (
327
335
  <div
328
336
  key={access.teamId}
329
337
  className="flex items-center justify-between p-2 bg-muted/50 rounded-md"
@@ -462,7 +470,7 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
462
470
  )}
463
471
 
464
472
  {/* Resource-level Team Only setting */}
465
- {accessList.length > 0 && (
473
+ {typedAccessList.length > 0 && (
466
474
  <div className="flex items-center justify-between p-3 bg-muted/30 rounded-lg">
467
475
  <div className="flex items-center gap-2">
468
476
  <Lock className="h-4 w-4 text-muted-foreground" />
@@ -471,8 +479,8 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
471
479
  Team Only Mode
472
480
  </Label>
473
481
  <p className="text-xs text-muted-foreground">
474
- When enabled, only team members can access (global
475
- permissions bypassed)
482
+ When enabled, only team members can access (global access
483
+ bypassed)
476
484
  </p>
477
485
  </div>
478
486
  </div>
@@ -485,14 +493,14 @@ export const TeamAccessEditor: React.FC<TeamAccessEditorProps> = ({
485
493
  )}
486
494
 
487
495
  {/* Access list */}
488
- {accessList.length === 0 ? (
496
+ {typedAccessList.length === 0 ? (
489
497
  <p className="text-sm text-muted-foreground text-center py-4 bg-muted/30 rounded-lg">
490
498
  No team restrictions configured. All users with appropriate
491
- permissions can access this resource.
499
+ access can view this resource.
492
500
  </p>
493
501
  ) : (
494
502
  <div className="border rounded-lg divide-y">
495
- {accessList.map((access) => (
503
+ {typedAccessList.map((access) => (
496
504
  <div
497
505
  key={access.teamId}
498
506
  className="p-3 flex items-center justify-between"