@checkstack/auth-frontend 0.2.0 → 0.3.1

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